@dabble/patches 0.5.18 → 0.5.19

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.
@@ -14,7 +14,15 @@ async function commitChanges(store, docId, changes, sessionTimeoutMillis, option
14
14
  const { state: initialState, rev: initialRev, changes: currentChanges } = await getSnapshotAtRevision(store, docId);
15
15
  const currentState = applyChanges(initialState, currentChanges);
16
16
  const currentRev = currentChanges.at(-1)?.rev ?? initialRev;
17
- const baseRev = changes[0].baseRev ?? currentRev;
17
+ let baseRev = changes[0].baseRev ?? currentRev;
18
+ const batchedContinuation = batchId && changes[0].rev > 1;
19
+ if (changes[0].baseRev === 0 && currentRev > 0 && !batchedContinuation) {
20
+ const hasRootOp = changes.some((c) => c.ops.some((op) => op.path === ""));
21
+ if (!hasRootOp) {
22
+ baseRev = currentRev;
23
+ changes.forEach((c) => c.baseRev = baseRev);
24
+ }
25
+ }
18
26
  const serverNow = getISO();
19
27
  let rev = baseRev + 1;
20
28
  changes.forEach((c) => {
@@ -34,8 +42,7 @@ async function commitChanges(store, docId, changes, sessionTimeoutMillis, option
34
42
  `Client baseRev (${baseRev}) is ahead of server revision (${currentRev}) for doc ${docId}. Client needs to reload the document.`
35
43
  );
36
44
  }
37
- const laterPartOfAnInitialBatch = batchId && changes[0].rev > 1;
38
- if (baseRev === 0 && currentRev > 0 && !laterPartOfAnInitialBatch && changes[0].ops[0]?.path === "") {
45
+ if (changes[0].baseRev === 0 && currentRev > 0 && !batchedContinuation && changes[0].ops[0]?.path === "") {
39
46
  throw new Error(
40
47
  `Document ${docId} already exists (rev ${currentRev}). Cannot apply root-level replace (path: '') with baseRev 0 - this would overwrite the existing document. Load the existing document first, or use nested paths instead of replacing at root.`
41
48
  );
@@ -91,10 +91,7 @@ class JSONRPCClient {
91
91
  }
92
92
  console.warn("Received unexpected message format:", message);
93
93
  } catch (error) {
94
- const parseError = new JSONRPCParseError(
95
- data,
96
- error instanceof Error ? error : new Error(String(error))
97
- );
94
+ const parseError = new JSONRPCParseError(data, error instanceof Error ? error : new Error(String(error)));
98
95
  console.error("Failed to parse incoming message:", data, error);
99
96
  this.rejectAllPending(parseError);
100
97
  }
@@ -43,7 +43,7 @@ class RPCServer {
43
43
  this.patches.onChangesCommitted((docId, changes, originClientId) => {
44
44
  this.rpc.notify("changesCommitted", { docId, changes }, originClientId);
45
45
  });
46
- this.patches.onDocDeleted((docId, originClientId) => {
46
+ this.patches.onDocDeleted((docId, options, originClientId) => {
47
47
  this.rpc.notify("docDeleted", { docId }, originClientId);
48
48
  });
49
49
  }
@@ -95,7 +95,7 @@ class RPCServer {
95
95
  async deleteDoc(params, ctx) {
96
96
  const { docId, options } = params;
97
97
  await this.assertWrite(ctx, docId, "deleteDoc", params);
98
- await this.patches.deleteDoc(docId, ctx?.clientId, options);
98
+ await this.patches.deleteDoc(docId, options, ctx?.clientId);
99
99
  }
100
100
  /**
101
101
  * Removes the tombstone for a deleted document, allowing it to be recreated.
@@ -1,6 +1,6 @@
1
1
  import { Signal } from '../event-signal.js';
2
2
  import { PatchesStoreBackend } from './types.js';
3
- import { Change, PatchesState, ChangeInput, CommitChangesOptions, ChangeMutator, DeleteDocOptions, EditableVersionMetadata } from '../types.js';
3
+ import { Change, DeleteDocOptions, PatchesState, ChangeInput, CommitChangesOptions, ChangeMutator, EditableVersionMetadata } from '../types.js';
4
4
  import { OpsCompressor } from '../compression/index.js';
5
5
  import '../json-patch/JSONPatch.js';
6
6
  import '@dabble/delta';
@@ -45,7 +45,7 @@ declare class PatchesServer {
45
45
  /** Notifies listeners whenever a batch of changes is *successfully* committed. */
46
46
  readonly onChangesCommitted: Signal<(docId: string, changes: Change[], originClientId?: string) => void>;
47
47
  /** Notifies listeners when a document is deleted. */
48
- readonly onDocDeleted: Signal<(docId: string, originClientId?: string) => void>;
48
+ readonly onDocDeleted: Signal<(docId: string, options?: DeleteDocOptions, originClientId?: string) => void>;
49
49
  constructor(store: PatchesStoreBackend, options?: PatchesServerOptions);
50
50
  /**
51
51
  * Get the state of a document at a specific revision (or the latest state if no revision is provided).
@@ -91,7 +91,7 @@ declare class PatchesServer {
91
91
  * @param originClientId - The ID of the client that initiated the delete operation.
92
92
  * @param options - Optional deletion settings (e.g., skipTombstone for testing).
93
93
  */
94
- deleteDoc(docId: string, originClientId?: string, options?: DeleteDocOptions): Promise<void>;
94
+ deleteDoc(docId: string, options?: DeleteDocOptions, originClientId?: string): Promise<void>;
95
95
  /**
96
96
  * Removes the tombstone for a deleted document, allowing it to be recreated.
97
97
  * @param docId The document ID.
@@ -103,7 +103,7 @@ class PatchesServer {
103
103
  * @param originClientId - The ID of the client that initiated the delete operation.
104
104
  * @param options - Optional deletion settings (e.g., skipTombstone for testing).
105
105
  */
106
- async deleteDoc(docId, originClientId, options) {
106
+ async deleteDoc(docId, options, originClientId) {
107
107
  if (this.store.createTombstone && !options?.skipTombstone) {
108
108
  const { rev: lastRev } = await this.getDoc(docId);
109
109
  await this.store.createTombstone({
@@ -114,7 +114,7 @@ class PatchesServer {
114
114
  });
115
115
  }
116
116
  await this.store.deleteDoc(docId);
117
- await this.onDocDeleted.emit(docId, originClientId);
117
+ await this.onDocDeleted.emit(docId, options, originClientId);
118
118
  }
119
119
  /**
120
120
  * Removes the tombstone for a deleted document, allowing it to be recreated.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dabble/patches",
3
- "version": "0.5.18",
3
+ "version": "0.5.19",
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": {