@dabble/patches 0.7.1 → 0.7.3

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 (50) hide show
  1. package/dist/client/IndexedDBStore.js +1 -1
  2. package/dist/client/LWWAlgorithm.js +3 -2
  3. package/dist/client/LWWBatcher.d.ts +84 -0
  4. package/dist/client/LWWBatcher.js +85 -0
  5. package/dist/client/LWWDoc.d.ts +3 -1
  6. package/dist/client/LWWDoc.js +4 -2
  7. package/dist/client/Patches.d.ts +5 -0
  8. package/dist/client/Patches.js +16 -2
  9. package/dist/client/index.d.ts +1 -0
  10. package/dist/client/index.js +1 -0
  11. package/dist/compression/index.js +1 -6
  12. package/dist/index.d.ts +1 -0
  13. package/dist/net/PatchesClient.d.ts +2 -2
  14. package/dist/net/PatchesClient.js +2 -2
  15. package/dist/net/index.d.ts +0 -1
  16. package/dist/net/protocol/JSONRPCServer.js +5 -3
  17. package/dist/net/protocol/types.d.ts +1 -0
  18. package/dist/net/webrtc/WebRTCAwareness.js +2 -3
  19. package/dist/net/websocket/WebSocketTransport.js +5 -2
  20. package/dist/server/LWWServer.d.ts +4 -4
  21. package/dist/server/LWWServer.js +3 -3
  22. package/dist/server/OTServer.d.ts +2 -2
  23. package/dist/server/OTServer.js +2 -3
  24. package/dist/shared/doc-manager.d.ts +89 -0
  25. package/dist/shared/doc-manager.js +126 -0
  26. package/dist/shared/utils.d.ts +22 -0
  27. package/dist/shared/utils.js +22 -0
  28. package/dist/solid/doc-manager.d.ts +3 -81
  29. package/dist/solid/doc-manager.js +1 -120
  30. package/dist/solid/index.d.ts +4 -2
  31. package/dist/solid/index.js +4 -0
  32. package/dist/solid/managed-docs.d.ts +63 -0
  33. package/dist/solid/managed-docs.js +105 -0
  34. package/dist/solid/primitives.d.ts +73 -68
  35. package/dist/solid/primitives.js +111 -4
  36. package/dist/solid/utils.d.ts +1 -0
  37. package/dist/solid/utils.js +6 -0
  38. package/dist/vue/composables.d.ts +67 -35
  39. package/dist/vue/composables.js +111 -4
  40. package/dist/vue/doc-manager.d.ts +3 -81
  41. package/dist/vue/doc-manager.js +1 -120
  42. package/dist/vue/index.d.ts +4 -2
  43. package/dist/vue/index.js +4 -0
  44. package/dist/vue/managed-docs.d.ts +66 -0
  45. package/dist/vue/managed-docs.js +101 -0
  46. package/dist/vue/utils.d.ts +1 -0
  47. package/dist/vue/utils.js +6 -0
  48. package/package.json +1 -1
  49. package/dist/net/types.d.ts +0 -8
  50. package/dist/net/types.js +0 -0
@@ -58,7 +58,7 @@ class IndexedDBStore {
58
58
  this.db.close();
59
59
  this.db = null;
60
60
  this.dbPromise = deferred();
61
- this.dbPromise.resolve(null);
61
+ this.dbPromise.reject(new Error("Store has been closed"));
62
62
  }
63
63
  }
64
64
  async deleteDB() {
@@ -26,7 +26,7 @@ class LWWAlgorithm {
26
26
  const committedRev = doc?.committedRev ?? await this.store.getCommittedRev(docId);
27
27
  const changes = [createChange(committedRev, committedRev + 1, timedOps, metadata)];
28
28
  if (doc) {
29
- doc.applyChanges(changes);
29
+ doc.applyChanges(changes, true);
30
30
  }
31
31
  return changes;
32
32
  }
@@ -52,7 +52,8 @@ class LWWAlgorithm {
52
52
  const localOps = [...sendingChange?.ops ?? [], ...pendingOps];
53
53
  const mergedChanges = mergeServerWithLocal(serverChanges, localOps);
54
54
  if (doc) {
55
- doc.applyChanges(mergedChanges);
55
+ const hasPending = localOps.length > 0;
56
+ doc.applyChanges(mergedChanges, hasPending);
56
57
  }
57
58
  return mergedChanges;
58
59
  }
@@ -0,0 +1,84 @@
1
+ import { JSONPatch } from '../json-patch/JSONPatch.js';
2
+ import { JSONPatchOp } from '../json-patch/types.js';
3
+ import { ChangeMutator, ChangeInput } from '../types.js';
4
+ import '@dabble/delta';
5
+
6
+ /**
7
+ * A utility for batching LWW operations before sending them to the server.
8
+ *
9
+ * Accumulates operations, consolidates them using LWW rules (@inc merging,
10
+ * last-write-wins for replace ops, etc.), and produces a ChangeInput when flushed.
11
+ *
12
+ * Useful for migration scripts and batch operations where you want to accumulate
13
+ * many changes efficiently without creating wasteful Change objects for each operation.
14
+ *
15
+ * @template T The type of the document being modified
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const batcher = new LWWBatcher<MyDocType>();
20
+ *
21
+ * // Option 1: Add ops directly
22
+ * batcher.add([
23
+ * { op: '@inc', path: '/counter', value: 5 },
24
+ * { op: '@inc', path: '/counter', value: 3 }
25
+ * ]);
26
+ *
27
+ * // Option 2: Use change() like LWWDoc
28
+ * batcher.change((patch, doc) => {
29
+ * patch.increment(doc.counter, 5);
30
+ * patch.replace(doc.name, 'Alice');
31
+ * });
32
+ *
33
+ * // Get consolidated change to send
34
+ * const changeInput = batcher.flush();
35
+ * // Returns: { id: '...', ops: [...consolidated...], createdAt: 123456789 }
36
+ * ```
37
+ */
38
+ declare class LWWBatcher<T extends object = object> {
39
+ private ops;
40
+ /**
41
+ * Adds operations to the batch.
42
+ * Operations are consolidated with existing ops using LWW rules.
43
+ *
44
+ * @param newOps Array of JSON Patch operations to add (timestamps optional)
45
+ */
46
+ add(newOps: JSONPatchOp[] | JSONPatch): void;
47
+ /**
48
+ * Captures operations using a mutator function (like LWWDoc.change).
49
+ * The mutator receives a JSONPatch instance and a type-safe path proxy.
50
+ *
51
+ * @param mutator Function that uses JSONPatch methods with type-safe paths
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * batcher.change((patch, doc) => {
56
+ * patch.increment(doc.counter, 5);
57
+ * patch.replace(doc.user.name, 'Alice');
58
+ * patch.bitSet(doc.flags, 0b0010);
59
+ * });
60
+ * ```
61
+ */
62
+ change(mutator: ChangeMutator<T>): void;
63
+ /**
64
+ * Returns the consolidated operations as a ChangeInput object and clears the batch.
65
+ *
66
+ * @param metadata Optional metadata to include in the ChangeInput
67
+ * @returns A ChangeInput with id, ops, and createdAt (no rev/baseRev)
68
+ */
69
+ flush(metadata?: Record<string, any>): ChangeInput;
70
+ /**
71
+ * Clears all batched operations without creating a ChangeInput.
72
+ */
73
+ clear(): void;
74
+ /**
75
+ * Returns true if the batch has no pending operations.
76
+ */
77
+ isEmpty(): boolean;
78
+ /**
79
+ * Returns the current number of batched operations.
80
+ */
81
+ get size(): number;
82
+ }
83
+
84
+ export { LWWBatcher };
@@ -0,0 +1,85 @@
1
+ import "../chunk-IZ2YBCUP.js";
2
+ import { consolidateOps } from "../algorithms/lww/consolidateOps.js";
3
+ import { createChange } from "../data/change.js";
4
+ import { createJSONPatch } from "../json-patch/createJSONPatch.js";
5
+ class LWWBatcher {
6
+ ops = /* @__PURE__ */ new Map();
7
+ /**
8
+ * Adds operations to the batch.
9
+ * Operations are consolidated with existing ops using LWW rules.
10
+ *
11
+ * @param newOps Array of JSON Patch operations to add (timestamps optional)
12
+ */
13
+ add(newOps) {
14
+ if (!Array.isArray(newOps)) {
15
+ newOps = newOps.ops;
16
+ }
17
+ if (newOps.length === 0) {
18
+ return;
19
+ }
20
+ const timestamp = Date.now();
21
+ const timedOps = newOps.map((op) => op.ts ? op : { ...op, ts: timestamp });
22
+ const existingOps = Array.from(this.ops.values());
23
+ const { opsToSave, pathsToDelete } = consolidateOps(existingOps, timedOps);
24
+ for (const path of pathsToDelete) {
25
+ this.ops.delete(path);
26
+ }
27
+ for (const op of opsToSave) {
28
+ this.ops.set(op.path, op);
29
+ }
30
+ }
31
+ /**
32
+ * Captures operations using a mutator function (like LWWDoc.change).
33
+ * The mutator receives a JSONPatch instance and a type-safe path proxy.
34
+ *
35
+ * @param mutator Function that uses JSONPatch methods with type-safe paths
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * batcher.change((patch, doc) => {
40
+ * patch.increment(doc.counter, 5);
41
+ * patch.replace(doc.user.name, 'Alice');
42
+ * patch.bitSet(doc.flags, 0b0010);
43
+ * });
44
+ * ```
45
+ */
46
+ change(mutator) {
47
+ const patch = createJSONPatch(mutator);
48
+ if (patch.ops.length > 0) {
49
+ this.add(patch.ops);
50
+ }
51
+ }
52
+ /**
53
+ * Returns the consolidated operations as a ChangeInput object and clears the batch.
54
+ *
55
+ * @param metadata Optional metadata to include in the ChangeInput
56
+ * @returns A ChangeInput with id, ops, and createdAt (no rev/baseRev)
57
+ */
58
+ flush(metadata) {
59
+ const ops = Array.from(this.ops.values());
60
+ const change = createChange(ops, metadata);
61
+ this.clear();
62
+ return change;
63
+ }
64
+ /**
65
+ * Clears all batched operations without creating a ChangeInput.
66
+ */
67
+ clear() {
68
+ this.ops.clear();
69
+ }
70
+ /**
71
+ * Returns true if the batch has no pending operations.
72
+ */
73
+ isEmpty() {
74
+ return this.ops.size === 0;
75
+ }
76
+ /**
77
+ * Returns the current number of batched operations.
78
+ */
79
+ get size() {
80
+ return this.ops.size;
81
+ }
82
+ }
83
+ export {
84
+ LWWBatcher
85
+ };
@@ -49,8 +49,10 @@ declare class LWWDoc<T extends object = object> extends BaseDoc<T> {
49
49
  * - `committedAt === 0`: Pending local change (marks hasPending = true)
50
50
  *
51
51
  * @param changes Array of changes to apply
52
+ * @param hasPending If provided, overrides the inferred pending state.
53
+ * Used by LWWAlgorithm which knows the true pending state from the store.
52
54
  */
53
- applyChanges(changes: Change[]): void;
55
+ applyChanges(changes: Change[], hasPending?: boolean): void;
54
56
  }
55
57
 
56
58
  export { LWWDoc };
@@ -57,8 +57,10 @@ class LWWDoc extends BaseDoc {
57
57
  * - `committedAt === 0`: Pending local change (marks hasPending = true)
58
58
  *
59
59
  * @param changes Array of changes to apply
60
+ * @param hasPending If provided, overrides the inferred pending state.
61
+ * Used by LWWAlgorithm which knows the true pending state from the store.
60
62
  */
61
- applyChanges(changes) {
63
+ applyChanges(changes, hasPending) {
62
64
  if (changes.length === 0) return;
63
65
  for (const change of changes) {
64
66
  for (const op of change.ops) {
@@ -75,7 +77,7 @@ class LWWDoc extends BaseDoc {
75
77
  }
76
78
  }
77
79
  this._committedRev = lastCommittedRev;
78
- this._hasPending = hasPendingChanges;
80
+ this._hasPending = hasPending ?? hasPendingChanges;
79
81
  this.onUpdate.emit(this._state);
80
82
  }
81
83
  }
@@ -50,6 +50,11 @@ declare class Patches {
50
50
  /** Emitted when a doc has pending changes ready to send */
51
51
  readonly onChange: Signal<(docId: string) => void>;
52
52
  constructor(opts: PatchesOptions);
53
+ /**
54
+ * Loads tracked docs from all registered algorithm stores.
55
+ * Extracted as a protected method so subclasses can override initialization behavior.
56
+ */
57
+ protected init(): void;
53
58
  /**
54
59
  * Gets an algorithm by name, throwing if not found.
55
60
  */
@@ -37,8 +37,22 @@ class Patches {
37
37
  throw new Error(`Default algorithm '${this.defaultAlgorithm}' not found in algorithms map`);
38
38
  }
39
39
  this.docOptions = opts.docOptions ?? {};
40
- this._getAlgorithm(this.defaultAlgorithm).listDocs().then((docs) => {
41
- this.trackDocs(docs.map(({ docId }) => docId));
40
+ this.init();
41
+ }
42
+ /**
43
+ * Loads tracked docs from all registered algorithm stores.
44
+ * Extracted as a protected method so subclasses can override initialization behavior.
45
+ */
46
+ init() {
47
+ const algorithms = Object.values(this.algorithms).filter(Boolean);
48
+ Promise.all(
49
+ algorithms.map(
50
+ (algorithm) => algorithm.listDocs().then((docs) => {
51
+ this.trackDocs(docs.map(({ docId }) => docId));
52
+ })
53
+ )
54
+ ).catch((err) => {
55
+ console.error("Failed to load tracked docs during initialization:", err);
42
56
  });
43
57
  }
44
58
  /**
@@ -7,6 +7,7 @@ export { InMemoryStore } from './InMemoryStore.js';
7
7
  export { LWWInMemoryStore } from './LWWInMemoryStore.js';
8
8
  export { LWWDoc } from './LWWDoc.js';
9
9
  export { LWWAlgorithm } from './LWWAlgorithm.js';
10
+ export { LWWBatcher } from './LWWBatcher.js';
10
11
  export { OTAlgorithm } from './OTAlgorithm.js';
11
12
  export { Patches, PatchesOptions } from './Patches.js';
12
13
  export { PatchesHistoryClient } from './PatchesHistoryClient.js';
@@ -7,6 +7,7 @@ export * from "./InMemoryStore.js";
7
7
  export * from "./LWWInMemoryStore.js";
8
8
  export * from "./LWWDoc.js";
9
9
  export * from "./LWWAlgorithm.js";
10
+ export * from "./LWWBatcher.js";
10
11
  export * from "./OTDoc.js";
11
12
  export * from "./OTAlgorithm.js";
12
13
  export * from "./Patches.js";
@@ -1,10 +1,5 @@
1
1
  import "../chunk-IZ2YBCUP.js";
2
- import {
3
- compressToBase64,
4
- compressToUint8Array,
5
- decompressFromBase64,
6
- decompressFromUint8Array
7
- } from "./lz.js";
2
+ import { compressToBase64, compressToUint8Array, decompressFromBase64, decompressFromUint8Array } from "./lz.js";
8
3
  const compressedSizeBase64 = (data) => {
9
4
  if (data === void 0) return 0;
10
5
  try {
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export { InMemoryStore } from './client/InMemoryStore.js';
8
8
  export { LWWInMemoryStore } from './client/LWWInMemoryStore.js';
9
9
  export { LWWDoc } from './client/LWWDoc.js';
10
10
  export { LWWAlgorithm } from './client/LWWAlgorithm.js';
11
+ export { LWWBatcher } from './client/LWWBatcher.js';
11
12
  export { OTAlgorithm } from './client/OTAlgorithm.js';
12
13
  export { Patches, PatchesOptions } from './client/Patches.js';
13
14
  export { PatchesHistoryClient } from './client/PatchesHistoryClient.js';
@@ -1,5 +1,5 @@
1
1
  import { Signal } from '../event-signal.js';
2
- import { Change, PatchesState, ChangeInput, CommitChangesOptions, DeleteDocOptions, EditableVersionMetadata, ListVersionsOptions, VersionMetadata, PatchesSnapshot } from '../types.js';
2
+ import { Change, CommitChangesOptions, PatchesState, ChangeInput, DeleteDocOptions, EditableVersionMetadata, ListVersionsOptions, VersionMetadata, PatchesSnapshot } from '../types.js';
3
3
  import { JSONRPCClient } from './protocol/JSONRPCClient.js';
4
4
  import { PatchesAPI, ClientTransport } from './protocol/types.js';
5
5
  import '../json-patch/JSONPatch.js';
@@ -16,7 +16,7 @@ declare class PatchesClient implements PatchesAPI {
16
16
  rpc: JSONRPCClient;
17
17
  transport: ClientTransport;
18
18
  /** Signal emitted when the server pushes document changes. */
19
- readonly onChangesCommitted: Signal<(docId: string, changes: Change[]) => void>;
19
+ readonly onChangesCommitted: Signal<(docId: string, changes: Change[], options?: CommitChangesOptions) => void>;
20
20
  /** Signal emitted when a document is deleted (either by another client or discovered on subscribe). */
21
21
  readonly onDocDeleted: Signal<(docId: string) => void>;
22
22
  /**
@@ -18,8 +18,8 @@ class PatchesClient {
18
18
  this.transport = transport;
19
19
  this.rpc = new JSONRPCClient(transport);
20
20
  this.rpc.on("changesCommitted", (params) => {
21
- const { docId, changes } = params;
22
- this.onChangesCommitted.emit(docId, changes);
21
+ const { docId, changes, options } = params;
22
+ this.onChangesCommitted.emit(docId, changes, options);
23
23
  });
24
24
  this.rpc.on("docDeleted", (params) => {
25
25
  this.onDocDeleted.emit(params.docId);
@@ -6,7 +6,6 @@ export { AccessLevel, ApiDefinition, ConnectionSignalSubscriber, JSONRPCServer,
6
6
  export { getAuthContext, getClientId } from './serverContext.js';
7
7
  export { AwarenessUpdateNotificationParams, ClientTransport, ConnectionState, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, ListOptions, Message, PatchesAPI, PatchesNotificationParams, ServerTransport, SignalNotificationParams } from './protocol/types.js';
8
8
  export { rpcError, rpcNotification, rpcResponse } from './protocol/utils.js';
9
- export { PatchesState, SyncingState } from './types.js';
10
9
  export { Access, AuthContext, AuthorizationProvider, allowAll, assertNotDeleted, denyAll } from './websocket/AuthorizationProvider.js';
11
10
  export { onlineState } from './websocket/onlineState.js';
12
11
  export { PatchesWebSocket } from './websocket/PatchesWebSocket.js';
@@ -152,9 +152,11 @@ class JSONRPCServer {
152
152
  }
153
153
  const args = Array.isArray(params) ? params : params === void 0 ? [] : [params];
154
154
  setAuthContext(ctx);
155
- const response = handler(...args);
156
- clearAuthContext();
157
- return response;
155
+ try {
156
+ return await handler(...args);
157
+ } finally {
158
+ clearAuthContext();
159
+ }
158
160
  }
159
161
  }
160
162
  export {
@@ -140,6 +140,7 @@ interface PatchesAPI {
140
140
  interface PatchesNotificationParams {
141
141
  docId: string;
142
142
  changes: Change[];
143
+ options?: CommitChangesOptions;
143
144
  }
144
145
  interface AwarenessUpdateNotificationParams {
145
146
  docId: string;
@@ -64,9 +64,8 @@ class WebRTCAwareness {
64
64
  * @param value - The new local awareness state to set and broadcast
65
65
  */
66
66
  set localState(value) {
67
- value.id = this.myId;
68
- this._localState = value;
69
- this.transport.send(JSON.stringify(value));
67
+ this._localState = { ...value, id: this.myId };
68
+ this.transport.send(JSON.stringify(this._localState));
70
69
  }
71
70
  /**
72
71
  * Handles a new peer connection by sending the local state to the new peer.
@@ -169,9 +169,12 @@ class WebSocketTransport {
169
169
  if (!this.onlineUnsubscriber) {
170
170
  this.onlineUnsubscriber = onlineState.onOnlineChange((isOnline) => {
171
171
  if (isOnline && this.shouldBeConnected && !this.connecting && this.state !== "connected") {
172
- const { resolve, reject } = this.connectionDeferred;
172
+ const oldDeferred = this.connectionDeferred;
173
173
  this.connectionDeferred = null;
174
- this.connect().then(resolve, reject);
174
+ const connectPromise = this.connect();
175
+ if (oldDeferred) {
176
+ connectPromise.then(oldDeferred.resolve, oldDeferred.reject);
177
+ }
175
178
  } else if (!isOnline && this.ws) {
176
179
  this.ws.close();
177
180
  }
@@ -1,6 +1,6 @@
1
1
  import { Signal } from '../event-signal.js';
2
2
  import { ApiDefinition } from '../net/protocol/JSONRPCServer.js';
3
- import { Change, DeleteDocOptions, PatchesState, ChangeInput, CommitChangesOptions, ChangeMutator, EditableVersionMetadata } from '../types.js';
3
+ import { Change, CommitChangesOptions, DeleteDocOptions, PatchesState, ChangeInput, ChangeMutator, EditableVersionMetadata } from '../types.js';
4
4
  import { PatchesServer } from './PatchesServer.js';
5
5
  import { LWWStoreBackend } from './types.js';
6
6
  import '../net/websocket/AuthorizationProvider.js';
@@ -55,7 +55,7 @@ declare class LWWServer implements PatchesServer {
55
55
  readonly store: LWWStoreBackend;
56
56
  private readonly snapshotInterval;
57
57
  /** Notifies listeners whenever a batch of changes is successfully committed. */
58
- readonly onChangesCommitted: Signal<(docId: string, changes: Change[], originClientId?: string) => void>;
58
+ readonly onChangesCommitted: Signal<(docId: string, changes: Change[], options?: CommitChangesOptions, originClientId?: string) => void>;
59
59
  /** Notifies listeners when a document is deleted. */
60
60
  readonly onDocDeleted: Signal<(docId: string, options?: DeleteDocOptions, originClientId?: string) => void>;
61
61
  constructor(store: LWWStoreBackend, options?: LWWServerOptions);
@@ -86,10 +86,10 @@ declare class LWWServer implements PatchesServer {
86
86
  *
87
87
  * @param docId - The document ID.
88
88
  * @param changes - The changes to commit (always 1 for LWW).
89
- * @param _options - Optional commit options (ignored for LWW).
89
+ * @param options - Optional commit options (ignored for LWW).
90
90
  * @returns Array containing 0-1 changes with catchup ops and new rev.
91
91
  */
92
- commitChanges(docId: string, changes: ChangeInput[], _options?: CommitChangesOptions): Promise<Change[]>;
92
+ commitChanges(docId: string, changes: ChangeInput[], options?: CommitChangesOptions): Promise<Change[]>;
93
93
  /**
94
94
  * Delete a document and emit deletion signal.
95
95
  * Creates a tombstone if the store supports it.
@@ -77,10 +77,10 @@ class LWWServer {
77
77
  *
78
78
  * @param docId - The document ID.
79
79
  * @param changes - The changes to commit (always 1 for LWW).
80
- * @param _options - Optional commit options (ignored for LWW).
80
+ * @param options - Optional commit options (ignored for LWW).
81
81
  * @returns Array containing 0-1 changes with catchup ops and new rev.
82
82
  */
83
- async commitChanges(docId, changes, _options) {
83
+ async commitChanges(docId, changes, options) {
84
84
  if (changes.length === 0) {
85
85
  return [];
86
86
  }
@@ -117,7 +117,7 @@ class LWWServer {
117
117
  id: change.id,
118
118
  committedAt: serverNow
119
119
  });
120
- await this.onChangesCommitted.emit(docId, [broadcastChange], getClientId());
120
+ await this.onChangesCommitted.emit(docId, [broadcastChange], options, getClientId());
121
121
  } catch (error) {
122
122
  console.error(`Failed to notify clients about committed changes for doc ${docId}:`, error);
123
123
  }
@@ -1,7 +1,7 @@
1
1
  import { Signal } from '../event-signal.js';
2
2
  import { PatchesServer } from './PatchesServer.js';
3
3
  import { OTStoreBackend } from './types.js';
4
- import { Change, DeleteDocOptions, PatchesState, ChangeInput, CommitChangesOptions, ChangeMutator, EditableVersionMetadata } from '../types.js';
4
+ import { Change, CommitChangesOptions, DeleteDocOptions, PatchesState, ChangeInput, ChangeMutator, EditableVersionMetadata } from '../types.js';
5
5
  import { ApiDefinition } from '../net/protocol/JSONRPCServer.js';
6
6
  import '../json-patch/JSONPatch.js';
7
7
  import '@dabble/delta';
@@ -48,7 +48,7 @@ declare class OTServer implements PatchesServer {
48
48
  private readonly maxStorageBytes?;
49
49
  readonly store: OTStoreBackend;
50
50
  /** Notifies listeners whenever a batch of changes is *successfully* committed. */
51
- readonly onChangesCommitted: Signal<(docId: string, changes: Change[], originClientId?: string) => void>;
51
+ readonly onChangesCommitted: Signal<(docId: string, changes: Change[], options?: CommitChangesOptions, originClientId?: string) => void>;
52
52
  /** Notifies listeners when a document is deleted. */
53
53
  readonly onDocDeleted: Signal<(docId: string, options?: DeleteDocOptions, originClientId?: string) => void>;
54
54
  constructor(store: OTStoreBackend, options?: OTServerOptions);
@@ -74,7 +74,7 @@ class OTServer {
74
74
  );
75
75
  if (newChanges.length > 0) {
76
76
  try {
77
- await this.onChangesCommitted.emit(docId, newChanges, getClientId());
77
+ await this.onChangesCommitted.emit(docId, newChanges, options, getClientId());
78
78
  } catch (error) {
79
79
  console.error(`Failed to notify clients about committed changes for doc ${docId}:`, error);
80
80
  }
@@ -127,8 +127,7 @@ class OTServer {
127
127
  async captureCurrentVersion(docId, metadata) {
128
128
  assertVersionMetadata(metadata);
129
129
  const { state: initialState, changes } = await getSnapshotAtRevision(this.store, docId);
130
- let state = initialState;
131
- state = applyChanges(state, changes);
130
+ const state = applyChanges(initialState, changes);
132
131
  const version = await createVersion(this.store, docId, state, changes, metadata);
133
132
  if (!version) {
134
133
  return null;
@@ -0,0 +1,89 @@
1
+ import { Patches } from '../client/Patches.js';
2
+ import { a as PatchesDoc } from '../BaseDoc-DkP3tUhT.js';
3
+ import '../event-signal.js';
4
+ import '../json-patch/types.js';
5
+ import '../types.js';
6
+ import '../json-patch/JSONPatch.js';
7
+ import '@dabble/delta';
8
+ import '../client/ClientAlgorithm.js';
9
+ import '../client/PatchesStore.js';
10
+
11
+ /**
12
+ * Reference counting manager for PatchesDoc instances.
13
+ *
14
+ * Tracks how many components are using each document and only opens/closes
15
+ * documents when the reference count goes to/from zero.
16
+ *
17
+ * This prevents the footgun where multiple components open the same doc but the
18
+ * first one to unmount closes it for everyone else.
19
+ */
20
+ declare class DocManager {
21
+ private refCounts;
22
+ private pendingOps;
23
+ /**
24
+ * Opens a document with reference counting.
25
+ *
26
+ * - If this is the first reference, calls patches.openDoc()
27
+ * - If doc is already open, returns existing instance and increments count
28
+ * - Handles concurrent opens to the same doc safely
29
+ *
30
+ * @param patches - Patches instance
31
+ * @param docId - Document ID to open
32
+ * @returns Promise resolving to PatchesDoc instance
33
+ */
34
+ openDoc<T extends object>(patches: Patches, docId: string): Promise<PatchesDoc<T>>;
35
+ /**
36
+ * Closes a document with reference counting.
37
+ *
38
+ * - Decrements the reference count
39
+ * - Only calls patches.closeDoc() when count reaches zero
40
+ * - Safe to call even if doc was never opened
41
+ *
42
+ * @param patches - Patches instance
43
+ * @param docId - Document ID to close
44
+ * @param untrack - Whether to also untrack the doc when closing (default: false)
45
+ */
46
+ closeDoc(patches: Patches, docId: string, untrack?: boolean): Promise<void>;
47
+ /**
48
+ * Increments the reference count for a document without opening it.
49
+ *
50
+ * Used in explicit mode to track usage and prevent premature closes
51
+ * from autoClose mode.
52
+ *
53
+ * @param docId - Document ID
54
+ */
55
+ incrementRefCount(docId: string): void;
56
+ /**
57
+ * Decrements the reference count for a document without closing it.
58
+ *
59
+ * Used in explicit mode to release usage tracking.
60
+ *
61
+ * @param docId - Document ID
62
+ */
63
+ decrementRefCount(docId: string): void;
64
+ /**
65
+ * Gets the current reference count for a document.
66
+ *
67
+ * Useful for debugging or advanced use cases.
68
+ *
69
+ * @param docId - Document ID
70
+ * @returns Current reference count (0 if not tracked)
71
+ */
72
+ getRefCount(docId: string): number;
73
+ /**
74
+ * Clears all reference counts without closing documents.
75
+ *
76
+ * Use with caution - this is mainly for testing or cleanup scenarios
77
+ * where you want to reset the manager state.
78
+ */
79
+ reset(): void;
80
+ }
81
+ /**
82
+ * Gets or creates a DocManager for a Patches instance.
83
+ *
84
+ * @param patches - Patches instance
85
+ * @returns DocManager for this Patches instance
86
+ */
87
+ declare function getDocManager(patches: Patches): DocManager;
88
+
89
+ export { DocManager, getDocManager };