@dabble/patches 0.8.12 → 0.8.13

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.
@@ -123,12 +123,6 @@ 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>;
132
126
  /**
133
127
  * Internal handler for doc changes. Called when doc.onChange emits ops.
134
128
  * Serializes calls per docId to prevent concurrent handleDocChange from
@@ -197,17 +197,6 @@ 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
- }
211
200
  /**
212
201
  * Internal handler for doc changes. Called when doc.onChange emits ops.
213
202
  * Serializes calls per docId to prevent concurrent handleDocChange from
@@ -26,8 +26,8 @@ interface PatchesBranchClientOptions {
26
26
  * (offline-first, local store handles caching/pending/tombstones). The API shape
27
27
  * determines merge behavior:
28
28
  *
29
- * - `BranchAPI` has `mergeBranch` — server performs the merge
30
- * - `BranchClientStore` has `updateBranch` client merges locally, updates `lastMergedRev`
29
+ * - `BranchAPI` — server performs the merge via `mergeBranch`
30
+ * - `BranchClientStore` merge is not supported; call the server merge endpoint directly
31
31
  */
32
32
  declare class PatchesBranchClient {
33
33
  private readonly api;
@@ -81,16 +81,15 @@ declare class PatchesBranchClient {
81
81
  /**
82
82
  * Merge a branch's changes back into this document.
83
83
  *
84
- * Online (BranchAPI with `mergeBranch`): server performs the merge.
85
- * Offline (BranchClientStore with `updateBranch`): client reads branch changes,
86
- * re-stamps them with `batchId: branchId`, submits via algorithm.handleDocChange
87
- * on the source doc, then updates `lastMergedRev` locally.
84
+ * Requires a `BranchAPI` (online mode) — the server performs the merge.
85
+ * Throws if the API is a `BranchClientStore` (offline-first mode) because
86
+ * client stores don't maintain full change history needed for correct merging.
87
+ * Offline-first consumers should call the server merge endpoint directly.
88
88
  */
89
89
  mergeBranch(branchId: string): Promise<void>;
90
90
  /** Clear state */
91
91
  clear(): void;
92
92
  private _createBranchOffline;
93
- private _mergeBranchLocally;
94
93
  }
95
94
 
96
95
  export { PatchesBranchClient, type PatchesBranchClientOptions };
@@ -2,6 +2,7 @@ import "../chunk-IZ2YBCUP.js";
2
2
  import { store } from "easy-signal";
3
3
  import { breakChanges } from "../algorithms/ot/shared/changeBatching.js";
4
4
  import { createChange } from "../data/change.js";
5
+ const OFFLINE_MERGE_ERROR = "Branch merging requires a server connection. Use a BranchAPI or call the server merge endpoint directly.";
5
6
  class PatchesBranchClient {
6
7
  constructor(id, api, patches, options) {
7
8
  this.api = api;
@@ -87,18 +88,17 @@ class PatchesBranchClient {
87
88
  /**
88
89
  * Merge a branch's changes back into this document.
89
90
  *
90
- * Online (BranchAPI with `mergeBranch`): server performs the merge.
91
- * Offline (BranchClientStore with `updateBranch`): client reads branch changes,
92
- * re-stamps them with `batchId: branchId`, submits via algorithm.handleDocChange
93
- * on the source doc, then updates `lastMergedRev` locally.
91
+ * Requires a `BranchAPI` (online mode) — the server performs the merge.
92
+ * Throws if the API is a `BranchClientStore` (offline-first mode) because
93
+ * client stores don't maintain full change history needed for correct merging.
94
+ * Offline-first consumers should call the server merge endpoint directly.
94
95
  */
95
96
  async mergeBranch(branchId) {
96
- if (!this.isOffline) {
97
- await this.api.mergeBranch(branchId);
98
- await this.listBranches();
99
- return;
97
+ if (this.isOffline) {
98
+ throw new Error(OFFLINE_MERGE_ERROR);
100
99
  }
101
- await this._mergeBranchLocally(branchId);
100
+ await this.api.mergeBranch(branchId);
101
+ await this.listBranches();
102
102
  }
103
103
  /** Clear state */
104
104
  clear() {
@@ -148,29 +148,6 @@ class PatchesBranchClient {
148
148
  this.patches.onChange.emit(branchDocId);
149
149
  return branchDocId;
150
150
  }
151
- async _mergeBranchLocally(branchId) {
152
- const offlineApi = this.api;
153
- const branch = this.branches.state.find((b) => b.id === branchId);
154
- if (!branch) throw new Error(`Branch ${branchId} not found`);
155
- const sourceDocId = branch.docId;
156
- const algorithmName = this.options?.algorithm ?? this.patches.defaultAlgorithm;
157
- const algorithm = this.patches.algorithms[algorithmName];
158
- if (!algorithm?.listChanges) {
159
- throw new Error("Offline merge requires an algorithm with listChanges support");
160
- }
161
- const startAfter = branch.lastMergedRev ?? (branch.contentStartRev ?? 2) - 1;
162
- const branchChanges = await algorithm.listChanges(branchId, { startAfter });
163
- if (branchChanges.length === 0) return;
164
- const lastBranchRev = branchChanges[branchChanges.length - 1].rev;
165
- for (const change of branchChanges) {
166
- await this.patches.submitDocChange(sourceDocId, change.ops, { batchId: branchId });
167
- }
168
- await offlineApi.updateBranch(branchId, { lastMergedRev: lastBranchRev });
169
- this.patches.onChange.emit(sourceDocId);
170
- this.branches.state = this.branches.state.map(
171
- (b) => b.id === branchId ? { ...b, lastMergedRev: lastBranchRev } : b
172
- );
173
- }
174
151
  }
175
152
  export {
176
153
  PatchesBranchClient
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dabble/patches",
3
- "version": "0.8.12",
3
+ "version": "0.8.13",
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": {