@dabble/patches 0.7.2 → 0.7.4
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.
- package/dist/client/LWWBatcher.d.ts +84 -0
- package/dist/client/LWWBatcher.js +86 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/net/PatchesClient.d.ts +2 -2
- package/dist/net/PatchesClient.js +2 -2
- package/dist/net/protocol/types.d.ts +1 -0
- package/dist/server/LWWServer.d.ts +4 -4
- package/dist/server/LWWServer.js +3 -3
- package/dist/server/OTServer.d.ts +2 -2
- package/dist/server/OTServer.js +1 -1
- package/package.json +1 -1
|
@@ -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, timestamp?: number): 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>, timestamp?: number): 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,86 @@
|
|
|
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, timestamp = Date.now()) {
|
|
14
|
+
if (!Array.isArray(newOps)) {
|
|
15
|
+
newOps = newOps.ops;
|
|
16
|
+
}
|
|
17
|
+
if (newOps.length === 0) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (timestamp) {
|
|
21
|
+
newOps = newOps.map((op) => op.ts ? op : { ...op, ts: timestamp });
|
|
22
|
+
}
|
|
23
|
+
const existingOps = Array.from(this.ops.values());
|
|
24
|
+
const { opsToSave, pathsToDelete } = consolidateOps(existingOps, newOps);
|
|
25
|
+
for (const path of pathsToDelete) {
|
|
26
|
+
this.ops.delete(path);
|
|
27
|
+
}
|
|
28
|
+
for (const op of opsToSave) {
|
|
29
|
+
this.ops.set(op.path, op);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Captures operations using a mutator function (like LWWDoc.change).
|
|
34
|
+
* The mutator receives a JSONPatch instance and a type-safe path proxy.
|
|
35
|
+
*
|
|
36
|
+
* @param mutator Function that uses JSONPatch methods with type-safe paths
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* batcher.change((patch, doc) => {
|
|
41
|
+
* patch.increment(doc.counter, 5);
|
|
42
|
+
* patch.replace(doc.user.name, 'Alice');
|
|
43
|
+
* patch.bitSet(doc.flags, 0b0010);
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
change(mutator, timestamp) {
|
|
48
|
+
const patch = createJSONPatch(mutator);
|
|
49
|
+
if (patch.ops.length > 0) {
|
|
50
|
+
this.add(patch.ops, timestamp);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Returns the consolidated operations as a ChangeInput object and clears the batch.
|
|
55
|
+
*
|
|
56
|
+
* @param metadata Optional metadata to include in the ChangeInput
|
|
57
|
+
* @returns A ChangeInput with id, ops, and createdAt (no rev/baseRev)
|
|
58
|
+
*/
|
|
59
|
+
flush(metadata) {
|
|
60
|
+
const ops = Array.from(this.ops.values());
|
|
61
|
+
const change = createChange(ops, metadata);
|
|
62
|
+
this.clear();
|
|
63
|
+
return change;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Clears all batched operations without creating a ChangeInput.
|
|
67
|
+
*/
|
|
68
|
+
clear() {
|
|
69
|
+
this.ops.clear();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Returns true if the batch has no pending operations.
|
|
73
|
+
*/
|
|
74
|
+
isEmpty() {
|
|
75
|
+
return this.ops.size === 0;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Returns the current number of batched operations.
|
|
79
|
+
*/
|
|
80
|
+
get size() {
|
|
81
|
+
return this.ops.size;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
LWWBatcher
|
|
86
|
+
};
|
package/dist/client/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/client/index.js
CHANGED
|
@@ -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";
|
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,
|
|
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);
|
|
@@ -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,
|
|
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
|
|
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[],
|
|
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.
|
package/dist/server/LWWServer.js
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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);
|
package/dist/server/OTServer.js
CHANGED
|
@@ -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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dabble/patches",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
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": {
|