@dabble/patches 0.8.13 → 0.8.14

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.
@@ -162,6 +162,10 @@ class LWWInMemoryStore {
162
162
  const buf = this.docs.get(docId);
163
163
  if (!buf?.sendingChange) return;
164
164
  for (const op of buf.sendingChange.ops) {
165
+ const childPrefix = op.path + "/";
166
+ for (const key of buf.committedFields.keys()) {
167
+ if (key.startsWith(childPrefix)) buf.committedFields.delete(key);
168
+ }
165
169
  buf.committedFields.set(op.path, op.value);
166
170
  }
167
171
  if (buf.sendingChange.rev > buf.committedRev) {
@@ -176,6 +180,10 @@ class LWWInMemoryStore {
176
180
  const buf = this.getOrCreateBuffer(docId);
177
181
  for (const change of serverChanges) {
178
182
  for (const op of change.ops) {
183
+ const childPrefix = op.path + "/";
184
+ for (const key of buf.committedFields.keys()) {
185
+ if (key.startsWith(childPrefix)) buf.committedFields.delete(key);
186
+ }
179
187
  buf.committedFields.set(op.path, op.value);
180
188
  }
181
189
  }
@@ -245,7 +245,12 @@ const _LWWIndexedDBStore = class _LWWIndexedDBStore {
245
245
  await tx.complete();
246
246
  return;
247
247
  }
248
- await Promise.all(sending.change.ops.map((op) => committedOps.put({ ...op, docId })));
248
+ await Promise.all(
249
+ sending.change.ops.map(async (op) => {
250
+ await committedOps.delete([docId, op.path + "/"], [docId, op.path + "/\uFFFF"]);
251
+ await committedOps.put({ ...op, docId });
252
+ })
253
+ );
249
254
  const rev = sending.change.rev;
250
255
  if (rev !== void 0) {
251
256
  const docMeta = await docsStore.get(docId) ?? { docId, committedRev: 0, algorithm: "lww" };
@@ -262,7 +267,12 @@ const _LWWIndexedDBStore = class _LWWIndexedDBStore {
262
267
  "readwrite"
263
268
  );
264
269
  const allOps = serverChanges.flatMap((change) => change.ops);
265
- await Promise.all(allOps.map((op) => committedOps.put({ ...op, docId })));
270
+ await Promise.all(
271
+ allOps.map(async (op) => {
272
+ await committedOps.delete([docId, op.path + "/"], [docId, op.path + "/\uFFFF"]);
273
+ await committedOps.put({ ...op, docId });
274
+ })
275
+ );
266
276
  const lastCommittedRev = serverChanges.at(-1)?.rev;
267
277
  if (lastCommittedRev !== void 0) {
268
278
  const docMeta = await docsStore.get(docId) ?? { docId, committedRev: 0, algorithm: "lww" };
@@ -123,6 +123,12 @@ declare class Patches {
123
123
  * Should be called when shutting down the client.
124
124
  */
125
125
  close(): Promise<void>;
126
+ /**
127
+ * Submits ops for a document through the serialized change queue.
128
+ * Used by PatchesBranchClient to merge branch changes without racing
129
+ * against concurrent user edits on the same document.
130
+ */
131
+ submitDocChange(docId: string, ops: JSONPatchOp[], metadata?: Record<string, any>): Promise<void>;
126
132
  /**
127
133
  * Internal handler for doc changes. Called when doc.onChange emits ops.
128
134
  * Serializes calls per docId to prevent concurrent handleDocChange from
@@ -197,6 +197,17 @@ class Patches {
197
197
  this.onServerCommit.clear();
198
198
  this.onError.clear();
199
199
  }
200
+ /**
201
+ * Submits ops for a document through the serialized change queue.
202
+ * Used by PatchesBranchClient to merge branch changes without racing
203
+ * against concurrent user edits on the same document.
204
+ */
205
+ submitDocChange(docId, ops, metadata = {}) {
206
+ const managed = this.docs.get(docId);
207
+ const algorithm = this.getDocAlgorithm(docId) ?? this.algorithms[this.defaultAlgorithm];
208
+ if (!algorithm) throw new Error(`No algorithm found for document ${docId}`);
209
+ return this._handleDocChange(docId, ops, managed?.doc, algorithm, metadata);
210
+ }
200
211
  /**
201
212
  * Internal handler for doc changes. Called when doc.onChange emits ops.
202
213
  * Serializes calls per docId to prevent concurrent handleDocChange from
@@ -191,7 +191,15 @@ class PatchesSync extends (_a = ReadonlyStoreClass, _syncDoc_dec = [serialGate],
191
191
  for (const branch of creates) {
192
192
  if (!this.state.connected) break;
193
193
  try {
194
- const { docId: sourceDocId, branchedAtRev, createdAt: _1, modifiedAt: _2, pendingOp: _3, deleted: _4, ...metadata } = branch;
194
+ const {
195
+ docId: sourceDocId,
196
+ branchedAtRev,
197
+ createdAt: _1,
198
+ modifiedAt: _2,
199
+ pendingOp: _3,
200
+ deleted: _4,
201
+ ...metadata
202
+ } = branch;
195
203
  await branchApi.createBranch(sourceDocId, branchedAtRev, metadata);
196
204
  const synced = { ...branch, pendingOp: void 0 };
197
205
  delete synced.pendingOp;
@@ -205,7 +213,17 @@ class PatchesSync extends (_a = ReadonlyStoreClass, _syncDoc_dec = [serialGate],
205
213
  for (const branch of updates) {
206
214
  if (!this.state.connected) break;
207
215
  try {
208
- const { id: _id, docId: _did, branchedAtRev: _bar, createdAt: _ca, modifiedAt: _ma, contentStartRev: _csr, pendingOp: _po, deleted: _del, ...metadata } = branch;
216
+ const {
217
+ id: _id,
218
+ docId: _did,
219
+ branchedAtRev: _bar,
220
+ createdAt: _ca,
221
+ modifiedAt: _ma,
222
+ contentStartRev: _csr,
223
+ pendingOp: _po,
224
+ deleted: _del,
225
+ ...metadata
226
+ } = branch;
209
227
  await branchApi.updateBranch(branch.id, metadata);
210
228
  const synced = { ...branch, pendingOp: void 0 };
211
229
  delete synced.pendingOp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dabble/patches",
3
- "version": "0.8.13",
3
+ "version": "0.8.14",
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": {