@dabble/patches 0.2.10 → 0.2.12
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/Patches.d.ts +9 -1
- package/dist/client/Patches.js +9 -1
- package/dist/client/PatchesDoc.d.ts +13 -1
- package/dist/client/PatchesDoc.js +17 -3
- package/dist/json-patch/createJSONPatch.d.ts +1 -1
- package/dist/json-patch/createJSONPatch.js +2 -2
- package/dist/json-patch/patchProxy.d.ts +41 -0
- package/dist/json-patch/patchProxy.js +125 -0
- package/dist/json-patch/state.d.ts +2 -0
- package/dist/json-patch/state.js +8 -0
- package/dist/json-patch/transformPatch.d.ts +19 -0
- package/dist/json-patch/transformPatch.js +37 -0
- package/dist/json-patch/types.d.ts +52 -0
- package/dist/json-patch/types.js +1 -0
- package/dist/json-patch/utils/deepEqual.d.ts +1 -0
- package/dist/json-patch/utils/deepEqual.js +33 -0
- package/dist/json-patch/utils/exit.d.ts +2 -0
- package/dist/json-patch/utils/exit.js +4 -0
- package/dist/json-patch/utils/get.d.ts +2 -0
- package/dist/json-patch/utils/get.js +6 -0
- package/dist/json-patch/utils/getOpData.d.ts +2 -0
- package/dist/json-patch/utils/getOpData.js +10 -0
- package/dist/json-patch/utils/getType.d.ts +3 -0
- package/dist/json-patch/utils/getType.js +6 -0
- package/dist/json-patch/utils/index.d.ts +14 -0
- package/dist/json-patch/utils/index.js +14 -0
- package/dist/json-patch/utils/log.d.ts +2 -0
- package/dist/json-patch/utils/log.js +7 -0
- package/dist/json-patch/utils/ops.d.ts +14 -0
- package/dist/json-patch/utils/ops.js +103 -0
- package/dist/json-patch/utils/paths.d.ts +9 -0
- package/dist/json-patch/utils/paths.js +53 -0
- package/dist/json-patch/utils/pluck.d.ts +5 -0
- package/dist/json-patch/utils/pluck.js +30 -0
- package/dist/json-patch/utils/shallowCopy.d.ts +1 -0
- package/dist/json-patch/utils/shallowCopy.js +20 -0
- package/dist/json-patch/utils/softWrites.d.ts +7 -0
- package/dist/json-patch/utils/softWrites.js +18 -0
- package/dist/json-patch/utils/toArrayIndex.d.ts +1 -0
- package/dist/json-patch/utils/toArrayIndex.js +12 -0
- package/dist/json-patch/utils/toKeys.d.ts +1 -0
- package/dist/json-patch/utils/toKeys.js +15 -0
- package/dist/json-patch/utils/updateArrayIndexes.d.ts +5 -0
- package/dist/json-patch/utils/updateArrayIndexes.js +38 -0
- package/dist/json-patch/utils/updateArrayPath.d.ts +5 -0
- package/dist/json-patch/utils/updateArrayPath.js +45 -0
- package/dist/net/AbstractTransport.d.ts +47 -0
- package/dist/net/AbstractTransport.js +39 -0
- package/dist/net/PatchesSync.d.ts +52 -0
- package/dist/net/PatchesSync.js +293 -0
- package/dist/net/index.d.ts +9 -0
- package/dist/net/index.js +7 -0
- package/dist/net/protocol/JSONRPCClient.d.ts +55 -0
- package/dist/net/protocol/JSONRPCClient.js +106 -0
- package/dist/net/protocol/types.d.ts +142 -0
- package/dist/net/protocol/types.js +1 -0
- package/dist/net/types.d.ts +6 -0
- package/dist/net/types.js +1 -0
- package/dist/net/webrtc/WebRTCAwareness.d.ts +81 -0
- package/dist/net/webrtc/WebRTCAwareness.js +119 -0
- package/dist/net/webrtc/WebRTCTransport.d.ts +80 -0
- package/dist/net/webrtc/WebRTCTransport.js +157 -0
- package/dist/net/websocket/PatchesWebSocket.d.ts +107 -0
- package/dist/net/websocket/PatchesWebSocket.js +144 -0
- package/dist/net/websocket/SignalingService.d.ts +91 -0
- package/dist/net/websocket/SignalingService.js +140 -0
- package/dist/net/websocket/WebSocketTransport.d.ts +58 -0
- package/dist/net/websocket/WebSocketTransport.js +190 -0
- package/dist/net/websocket/onlineState.d.ts +9 -0
- package/dist/net/websocket/onlineState.js +18 -0
- package/dist/persist/InMemoryStore.d.ts +23 -0
- package/dist/persist/InMemoryStore.js +103 -0
- package/dist/persist/IndexedDBStore.d.ts +81 -0
- package/dist/persist/IndexedDBStore.js +377 -0
- package/dist/persist/PatchesStore.d.ts +38 -0
- package/dist/persist/PatchesStore.js +1 -0
- package/dist/persist/index.d.ts +3 -0
- package/dist/persist/index.js +3 -0
- package/dist/server/PatchesBranchManager.d.ts +40 -0
- package/dist/server/PatchesBranchManager.js +138 -0
- package/dist/server/PatchesHistoryManager.d.ts +43 -0
- package/dist/server/PatchesHistoryManager.js +59 -0
- package/dist/server/PatchesServer.d.ts +129 -0
- package/dist/server/PatchesServer.js +358 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +3 -0
- package/dist/types.d.ts +166 -0
- package/dist/types.js +1 -0
- package/dist/utils/batching.d.ts +3 -0
- package/dist/utils/batching.js +41 -0
- package/dist/utils/breakChange.d.ts +10 -0
- package/dist/utils/breakChange.js +303 -0
- package/dist/utils/getJSONByteSize.d.ts +2 -0
- package/dist/utils/getJSONByteSize.js +13 -0
- package/dist/utils.d.ts +36 -0
- package/dist/utils.js +103 -0
- package/package.json +1 -1
package/dist/client/Patches.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { type Unsubscriber } from '../event-signal.js';
|
|
2
2
|
import type { PatchesStore } from '../persist/PatchesStore.js';
|
|
3
3
|
import type { Change } from '../types.js';
|
|
4
|
-
import { PatchesDoc } from './PatchesDoc.js';
|
|
4
|
+
import { PatchesDoc, type PatchesDocOptions } from './PatchesDoc.js';
|
|
5
5
|
export interface PatchesOptions {
|
|
6
6
|
/** Persistence layer instance (e.g., new IndexedDBStore('my-db') or new InMemoryStore()). */
|
|
7
7
|
store: PatchesStore;
|
|
8
8
|
/** Initial metadata to attach to changes from this client (merged with per-doc metadata). */
|
|
9
9
|
metadata?: Record<string, any>;
|
|
10
|
+
/** Document-level options to pass to each PatchesDoc instance */
|
|
11
|
+
docOptions?: PatchesDocOptions;
|
|
10
12
|
}
|
|
11
13
|
interface ManagedDoc<T extends object> {
|
|
12
14
|
doc: PatchesDoc<T>;
|
|
@@ -20,6 +22,7 @@ interface ManagedDoc<T extends object> {
|
|
|
20
22
|
export declare class Patches {
|
|
21
23
|
protected options: PatchesOptions;
|
|
22
24
|
protected docs: Map<string, ManagedDoc<any>>;
|
|
25
|
+
readonly docOptions: PatchesDocOptions;
|
|
23
26
|
readonly store: PatchesStore;
|
|
24
27
|
readonly trackedDocs: Set<string>;
|
|
25
28
|
readonly onError: import("../event-signal.js").Signal<(error: Error, context?: {
|
|
@@ -99,6 +102,11 @@ export declare class Patches {
|
|
|
99
102
|
* Should be called when shutting down the client.
|
|
100
103
|
*/
|
|
101
104
|
close(): void;
|
|
105
|
+
/**
|
|
106
|
+
* Updates document options that will be applied to all new documents
|
|
107
|
+
* @param options - Options to merge with current docOptions
|
|
108
|
+
*/
|
|
109
|
+
updateDocOptions(options: Partial<PatchesDocOptions>): void;
|
|
102
110
|
/**
|
|
103
111
|
* Sets up a listener for local changes on a PatchesDoc, saving pending changes to the store.
|
|
104
112
|
* @param docId - The document ID being managed.
|
package/dist/client/Patches.js
CHANGED
|
@@ -36,6 +36,7 @@ export class Patches {
|
|
|
36
36
|
};
|
|
37
37
|
this.options = opts;
|
|
38
38
|
this.store = opts.store;
|
|
39
|
+
this.docOptions = opts.docOptions ?? {};
|
|
39
40
|
this.store.listDocs().then(docs => {
|
|
40
41
|
this.trackDocs(docs.map(({ docId }) => docId));
|
|
41
42
|
});
|
|
@@ -90,7 +91,7 @@ export class Patches {
|
|
|
90
91
|
const snapshot = await this.store.getDoc(docId);
|
|
91
92
|
const initialState = (snapshot?.state ?? {});
|
|
92
93
|
const mergedMetadata = { ...this.options.metadata, ...opts.metadata };
|
|
93
|
-
const doc = new PatchesDoc(initialState, mergedMetadata);
|
|
94
|
+
const doc = new PatchesDoc(initialState, mergedMetadata, this.docOptions);
|
|
94
95
|
doc.setId(docId);
|
|
95
96
|
if (snapshot) {
|
|
96
97
|
doc.import(snapshot);
|
|
@@ -191,6 +192,13 @@ export class Patches {
|
|
|
191
192
|
// Close store connection
|
|
192
193
|
void this.store.close();
|
|
193
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Updates document options that will be applied to all new documents
|
|
197
|
+
* @param options - Options to merge with current docOptions
|
|
198
|
+
*/
|
|
199
|
+
updateDocOptions(options) {
|
|
200
|
+
Object.assign(this.docOptions, options);
|
|
201
|
+
}
|
|
194
202
|
// --- Internal Handlers ---
|
|
195
203
|
/**
|
|
196
204
|
* Sets up a listener for local changes on a PatchesDoc, saving pending changes to the store.
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { type Unsubscriber } from '../event-signal.js';
|
|
2
2
|
import type { JSONPatch } from '../json-patch/JSONPatch.js';
|
|
3
3
|
import type { Change, PatchesSnapshot } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Options for creating a PatchesDoc instance
|
|
6
|
+
*/
|
|
7
|
+
export interface PatchesDocOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Maximum size in bytes for a single payload (network message).
|
|
10
|
+
* Changes exceeding this will be split into multiple smaller changes.
|
|
11
|
+
*/
|
|
12
|
+
maxPayloadBytes?: number;
|
|
13
|
+
}
|
|
4
14
|
/**
|
|
5
15
|
* Represents a document synchronized using JSON patches.
|
|
6
16
|
* Manages committed state, pending (local-only) changes, and
|
|
@@ -14,6 +24,7 @@ export declare class PatchesDoc<T extends object = object> {
|
|
|
14
24
|
protected _pendingChanges: Change[];
|
|
15
25
|
protected _sendingChanges: Change[];
|
|
16
26
|
protected _changeMetadata: Record<string, any>;
|
|
27
|
+
protected readonly _maxPayloadBytes?: number;
|
|
17
28
|
/** Subscribe to be notified before local state changes. */
|
|
18
29
|
readonly onBeforeChange: import("../event-signal.js").Signal<(change: Change) => void>;
|
|
19
30
|
/** Subscribe to be notified after local state changes are applied. */
|
|
@@ -24,8 +35,9 @@ export declare class PatchesDoc<T extends object = object> {
|
|
|
24
35
|
* Creates an instance of PatchesDoc.
|
|
25
36
|
* @param initialState Optional initial state.
|
|
26
37
|
* @param initialMetadata Optional metadata to add to generated changes.
|
|
38
|
+
* @param options Additional options for the document.
|
|
27
39
|
*/
|
|
28
|
-
constructor(initialState?: T, initialMetadata?: Record<string, any
|
|
40
|
+
constructor(initialState?: T, initialMetadata?: Record<string, any>, options?: PatchesDocOptions);
|
|
29
41
|
/** The unique identifier for this document, once assigned. */
|
|
30
42
|
get id(): string | null;
|
|
31
43
|
/** Current local state (committed + sending + pending). */
|
|
@@ -2,6 +2,7 @@ import { createId } from 'crypto-id';
|
|
|
2
2
|
import { signal } from '../event-signal.js';
|
|
3
3
|
import { createJSONPatch } from '../json-patch/createJSONPatch.js';
|
|
4
4
|
import { applyChanges, rebaseChanges } from '../utils.js';
|
|
5
|
+
import { breakChange } from '../utils/breakChange.js';
|
|
5
6
|
/**
|
|
6
7
|
* Represents a document synchronized using JSON patches.
|
|
7
8
|
* Manages committed state, pending (local-only) changes, and
|
|
@@ -12,8 +13,9 @@ export class PatchesDoc {
|
|
|
12
13
|
* Creates an instance of PatchesDoc.
|
|
13
14
|
* @param initialState Optional initial state.
|
|
14
15
|
* @param initialMetadata Optional metadata to add to generated changes.
|
|
16
|
+
* @param options Additional options for the document.
|
|
15
17
|
*/
|
|
16
|
-
constructor(initialState = {}, initialMetadata = {}) {
|
|
18
|
+
constructor(initialState = {}, initialMetadata = {}, options = {}) {
|
|
17
19
|
this._id = null;
|
|
18
20
|
this._pendingChanges = [];
|
|
19
21
|
this._sendingChanges = [];
|
|
@@ -28,6 +30,7 @@ export class PatchesDoc {
|
|
|
28
30
|
this._state = structuredClone(initialState);
|
|
29
31
|
this._committedRev = 0;
|
|
30
32
|
this._changeMetadata = initialMetadata;
|
|
33
|
+
this._maxPayloadBytes = options.maxPayloadBytes;
|
|
31
34
|
}
|
|
32
35
|
/** The unique identifier for this document, once assigned. */
|
|
33
36
|
get id() {
|
|
@@ -113,8 +116,19 @@ export class PatchesDoc {
|
|
|
113
116
|
this.onBeforeChange.emit(change);
|
|
114
117
|
// Apply to local state immediately
|
|
115
118
|
this._state = patch.apply(this._state);
|
|
116
|
-
this.
|
|
117
|
-
|
|
119
|
+
if (this._maxPayloadBytes) {
|
|
120
|
+
// Check if the change needs to be split due to size
|
|
121
|
+
const changes = breakChange(change, this._maxPayloadBytes);
|
|
122
|
+
// Emit events for each change piece
|
|
123
|
+
for (const piece of changes) {
|
|
124
|
+
this._pendingChanges.push(piece);
|
|
125
|
+
this.onChange.emit(piece);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this._pendingChanges.push(change);
|
|
130
|
+
this.onChange.emit(change);
|
|
131
|
+
}
|
|
118
132
|
this.onUpdate.emit(this._state);
|
|
119
133
|
return change;
|
|
120
134
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { JSONPatch } from './JSONPatch';
|
|
2
|
-
import { createPatchProxy } from './patchProxy';
|
|
1
|
+
import { JSONPatch } from './JSONPatch.js';
|
|
2
|
+
import { createPatchProxy } from './patchProxy.js';
|
|
3
3
|
/**
|
|
4
4
|
* Creates a `JSONPatch` instance by tracking changes made to a proxy object within an updater function.
|
|
5
5
|
*
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { JSONPatch } from './JSONPatch.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a proxy object that can be used in two ways:
|
|
4
|
+
*
|
|
5
|
+
* 1. **Path Generation:** When used without a `JSONPatch` instance, accessing properties
|
|
6
|
+
* on the proxy generates a JSON Pointer path string via `toString()`. This allows
|
|
7
|
+
* for type-safe path creation when using `JSONPatch` methods directly.
|
|
8
|
+
* ```ts
|
|
9
|
+
* const patch = new JSONPatch();
|
|
10
|
+
* const proxy = createPatchProxy<MyType>();
|
|
11
|
+
* patch.text(proxy.content, new Delta().insert('text')); // Path is '/content'
|
|
12
|
+
* patch.increment(proxy.counter, 5); // Path is '/counter'
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* 2. **Automatic Patch Generation:** When created with a target object and a `JSONPatch`
|
|
16
|
+
* instance, modifying the proxy (setting properties, calling array methods like
|
|
17
|
+
* `push`, `splice`, etc.) automatically generates the corresponding JSON Patch
|
|
18
|
+
* operations and adds them to the provided `patch` instance.
|
|
19
|
+
* ```ts
|
|
20
|
+
* const patch = new JSONPatch();
|
|
21
|
+
* const myObj = { name: { first: 'Alice' }, tags: ['a'] };
|
|
22
|
+
* const proxy = createPatchProxy(myObj, patch);
|
|
23
|
+
* proxy.name.first = 'Bob'; // Generates replace op
|
|
24
|
+
* proxy.tags.push('b'); // Generates add op
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* The proxy behaves like the original value in most contexts due to the `valueOf` trap.
|
|
28
|
+
* For optional properties, use the non-null assertion operator (!) when setting values:
|
|
29
|
+
* ```ts
|
|
30
|
+
* interface User { middleName?: string }
|
|
31
|
+
* const proxy = createPatchProxy<User>(user, patch);
|
|
32
|
+
* proxy.middleName! = 'John'; // Assert middleName exists before setting
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @template T The type of the object to proxy.
|
|
36
|
+
* @param target The target object (required for automatic patch generation mode).
|
|
37
|
+
* @param patch The `JSONPatch` instance to add generated operations to (required for automatic patch generation mode).
|
|
38
|
+
* @returns A proxy object of type T.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createPatchProxy<T>(): T;
|
|
41
|
+
export declare function createPatchProxy<T>(target: T, patch: JSONPatch): T;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// We use a function as the target so that `push` and other array methods can be called without error.
|
|
2
|
+
const proxyFodder = {};
|
|
3
|
+
export function createPatchProxy(target, patch) {
|
|
4
|
+
// Call the internal implementation
|
|
5
|
+
return createPatchProxyInternal(target, patch);
|
|
6
|
+
}
|
|
7
|
+
// Internal implementation with the path parameter
|
|
8
|
+
function createPatchProxyInternal(target, patch, path = '') {
|
|
9
|
+
// Always use an empty function as the proxy target
|
|
10
|
+
// This allows us to proxy any type of value, including primitives and undefined,
|
|
11
|
+
// and enables calling array methods like push/splice directly on array proxies.
|
|
12
|
+
return new Proxy(proxyFodder, {
|
|
13
|
+
get(_, prop) {
|
|
14
|
+
// Return the value directly for symbol properties (not relevant for JSON paths)
|
|
15
|
+
if (typeof prop === 'symbol') {
|
|
16
|
+
return target?.[prop];
|
|
17
|
+
}
|
|
18
|
+
// Handle toString specially to make properties work as PathLike
|
|
19
|
+
if (prop === 'toString') {
|
|
20
|
+
return function () {
|
|
21
|
+
return path;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Handle valueOf to make the proxy behave like the original value in most contexts
|
|
25
|
+
if (prop === 'valueOf') {
|
|
26
|
+
return function () {
|
|
27
|
+
return target;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// --- Array Method Interception ---
|
|
31
|
+
if (Array.isArray(target)) {
|
|
32
|
+
switch (prop) {
|
|
33
|
+
case 'push':
|
|
34
|
+
return (...items) => {
|
|
35
|
+
const index = target.length;
|
|
36
|
+
for (let i = 0; i < items.length; i++) {
|
|
37
|
+
patch?.add(`${path}/${index + i}`, items[i]);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
case 'pop':
|
|
41
|
+
return () => {
|
|
42
|
+
const index = target.length - 1;
|
|
43
|
+
if (index >= 0) {
|
|
44
|
+
patch?.remove(`${path}/${index}`);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
case 'shift':
|
|
48
|
+
return () => {
|
|
49
|
+
if (target.length > 0) {
|
|
50
|
+
patch?.remove(`${path}/0`);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
case 'unshift':
|
|
54
|
+
return (...items) => {
|
|
55
|
+
for (let i = 0; i < items.length; i++) {
|
|
56
|
+
patch?.add(`${path}/${i}`, items[i]);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
case 'splice':
|
|
60
|
+
return (start, deleteCount, ...items) => {
|
|
61
|
+
const actualStart = start < 0 ? Math.max(target.length + start, 0) : Math.min(start, target.length);
|
|
62
|
+
const actualDeleteCount = Math.min(Math.max(deleteCount === undefined ? target.length - actualStart : deleteCount, 0), target.length - actualStart);
|
|
63
|
+
// Remove deleted elements
|
|
64
|
+
for (let i = 0; i < actualDeleteCount; i++) {
|
|
65
|
+
patch?.remove(`${path}/${actualStart}`); // Path automatically adjusts for subsequent removes
|
|
66
|
+
}
|
|
67
|
+
// Add new elements
|
|
68
|
+
for (let i = 0; i < items.length; i++) {
|
|
69
|
+
patch?.add(`${path}/${actualStart + i}`, items[i]);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// --- End Array Method Interception ---
|
|
75
|
+
// Create a proxy for the property value if it's not an intercepted array method
|
|
76
|
+
// This handles objects, primitives, and undefined values uniformly
|
|
77
|
+
// Call the internal implementation recursively
|
|
78
|
+
return createPatchProxyInternal(target?.[prop], patch, `${path}/${String(prop)}`);
|
|
79
|
+
},
|
|
80
|
+
set(_, prop, value) {
|
|
81
|
+
// Ignore setting the 'length' property on arrays directly
|
|
82
|
+
if (Array.isArray(target) && prop === 'length') {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (target?.[prop] === value)
|
|
86
|
+
return true;
|
|
87
|
+
const patchPath = `${path}/${String(prop)}`;
|
|
88
|
+
if (value === undefined) {
|
|
89
|
+
patch?.remove(patchPath);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
patch?.replace(patchPath, value);
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
},
|
|
96
|
+
deleteProperty(_, prop) {
|
|
97
|
+
if (target == null || prop in target) {
|
|
98
|
+
patch?.remove(`${path}/${String(prop)}`);
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
},
|
|
102
|
+
// Make the proxy appear to be the same type as the target
|
|
103
|
+
getPrototypeOf() {
|
|
104
|
+
return Object.getPrototypeOf(target);
|
|
105
|
+
},
|
|
106
|
+
// Support instanceof checks
|
|
107
|
+
isExtensible() {
|
|
108
|
+
return target != null && Object.isExtensible(target);
|
|
109
|
+
},
|
|
110
|
+
// Support Object.keys and other enumeration methods
|
|
111
|
+
ownKeys() {
|
|
112
|
+
return target != null && typeof target === 'object' ? Reflect.ownKeys(target) : [];
|
|
113
|
+
},
|
|
114
|
+
// Support property descriptor access
|
|
115
|
+
getOwnPropertyDescriptor(_, prop) {
|
|
116
|
+
if (target == null || typeof target !== 'object')
|
|
117
|
+
return undefined;
|
|
118
|
+
return Object.getOwnPropertyDescriptor(target, prop);
|
|
119
|
+
},
|
|
120
|
+
// Support Object.hasOwnProperty
|
|
121
|
+
has(_, prop) {
|
|
122
|
+
return target != null && typeof target === 'object' && prop in target;
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Based on work from
|
|
3
|
+
* https://github.com/Palindrom/JSONPatchOT
|
|
4
|
+
* (c) 2017 Tomek Wytrebowicz
|
|
5
|
+
*
|
|
6
|
+
* MIT license
|
|
7
|
+
* (c) 2022 Jacob Wright
|
|
8
|
+
*
|
|
9
|
+
*
|
|
10
|
+
* WARNING: using /array/- syntax to indicate the end of the array makes it impossible to transform arrays correctly in
|
|
11
|
+
* all situaions. Please avoid using this syntax when using Operational Transformations.
|
|
12
|
+
*/
|
|
13
|
+
import type { JSONPatchOp, JSONPatchOpHandlerMap } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Transform an array of JSON Patch operations against another array of JSON Patch operations. Returns a new array with
|
|
16
|
+
* transformed operations. Operations that change are cloned, making the results of this function immutable.
|
|
17
|
+
* `otherOps` are transformed over `thisOps` with thisOps considered to have happened first.
|
|
18
|
+
*/
|
|
19
|
+
export declare function transformPatch(obj: any, thisOps: JSONPatchOp[], otherOps: JSONPatchOp[], custom?: JSONPatchOpHandlerMap): JSONPatchOp[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Based on work from
|
|
3
|
+
* https://github.com/Palindrom/JSONPatchOT
|
|
4
|
+
* (c) 2017 Tomek Wytrebowicz
|
|
5
|
+
*
|
|
6
|
+
* MIT license
|
|
7
|
+
* (c) 2022 Jacob Wright
|
|
8
|
+
*
|
|
9
|
+
*
|
|
10
|
+
* WARNING: using /array/- syntax to indicate the end of the array makes it impossible to transform arrays correctly in
|
|
11
|
+
* all situaions. Please avoid using this syntax when using Operational Transformations.
|
|
12
|
+
*/
|
|
13
|
+
import { getTypes } from './ops/index.js';
|
|
14
|
+
import { runWithObject } from './state.js';
|
|
15
|
+
import { getType } from './utils/getType.js';
|
|
16
|
+
import { log } from './utils/log.js';
|
|
17
|
+
/**
|
|
18
|
+
* Transform an array of JSON Patch operations against another array of JSON Patch operations. Returns a new array with
|
|
19
|
+
* transformed operations. Operations that change are cloned, making the results of this function immutable.
|
|
20
|
+
* `otherOps` are transformed over `thisOps` with thisOps considered to have happened first.
|
|
21
|
+
*/
|
|
22
|
+
export function transformPatch(obj, thisOps, otherOps, custom) {
|
|
23
|
+
const types = getTypes(custom);
|
|
24
|
+
return runWithObject(obj, types, false, state => {
|
|
25
|
+
return thisOps.reduce((otherOps, thisOp) => {
|
|
26
|
+
// transform ops with patch operation
|
|
27
|
+
const handler = getType(state, thisOp)?.transform;
|
|
28
|
+
if (typeof handler === 'function') {
|
|
29
|
+
otherOps = handler(state, thisOp, otherOps);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
log('No function to transform against for', thisOp.op);
|
|
33
|
+
}
|
|
34
|
+
return otherOps;
|
|
35
|
+
}, otherOps);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface JSONPatchOpHandler {
|
|
2
|
+
like: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test';
|
|
3
|
+
apply(state: State, path: string, valueOrFrom: any): string | void;
|
|
4
|
+
transform(state: State, other: JSONPatchOp, ops: JSONPatchOp[]): JSONPatchOp[];
|
|
5
|
+
invert(state: State, op: JSONPatchOp, value: any, changedObj: any, isIndex: boolean): JSONPatchOp;
|
|
6
|
+
compose?(state: State, value1: any, value2: any): any;
|
|
7
|
+
}
|
|
8
|
+
export interface JSONPatchOpHandlerMap {
|
|
9
|
+
[key: string]: JSONPatchOpHandler;
|
|
10
|
+
}
|
|
11
|
+
export interface ApplyJSONPatchOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Do not reject patches if error occurs (partial patching)
|
|
14
|
+
*/
|
|
15
|
+
partial?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Throw an exception if an error occurs when patching
|
|
18
|
+
*/
|
|
19
|
+
strict?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Stop on error and return the original object (without throwing an exception)
|
|
22
|
+
*/
|
|
23
|
+
rigid?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Don't log errors when they occurs during patching, if strict is not true, errors will be logged if this is false
|
|
26
|
+
*/
|
|
27
|
+
silent?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Saves the patch that caused the error to this property of the options object
|
|
30
|
+
*/
|
|
31
|
+
error?: JSONPatchOp;
|
|
32
|
+
/**
|
|
33
|
+
* Apply changes at a given path prefix
|
|
34
|
+
*/
|
|
35
|
+
atPath?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface JSONPatchOp {
|
|
38
|
+
op: string;
|
|
39
|
+
path: string;
|
|
40
|
+
from?: string;
|
|
41
|
+
value?: any;
|
|
42
|
+
soft?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface Root {
|
|
45
|
+
'': any;
|
|
46
|
+
}
|
|
47
|
+
export type State = {
|
|
48
|
+
root: Root;
|
|
49
|
+
types: JSONPatchOpHandlerMap;
|
|
50
|
+
cache: Set<any> | null;
|
|
51
|
+
};
|
|
52
|
+
export type Runner = (state: State) => any;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function deepEqual(a: any, b: any): boolean;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function deepEqual(a, b) {
|
|
2
|
+
if (a === b) {
|
|
3
|
+
return true;
|
|
4
|
+
}
|
|
5
|
+
if (!(a && b) || typeof a !== 'object' || typeof b !== 'object') {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
if (a.length !== b.length) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (Array.isArray(a)) {
|
|
12
|
+
if (!Array.isArray(b)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
for (let i = 0, imax = a.length; i < imax; i++) {
|
|
16
|
+
if (!deepEqual(a[i], b[i])) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const aKeys = Object.keys(a);
|
|
23
|
+
if (aKeys.length !== Object.keys(b).length) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
for (let j = 0, jmax = aKeys.length; j < jmax; j++) {
|
|
27
|
+
const key = aKeys[j];
|
|
28
|
+
if (!deepEqual(a[key], b[key])) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EMPTY, pluck } from './pluck.js';
|
|
2
|
+
import { toKeys } from './toKeys.js';
|
|
3
|
+
export function getOpData(state, path, createMissingObjects) {
|
|
4
|
+
const keys = toKeys(path);
|
|
5
|
+
const lastKey = keys[keys.length - 1];
|
|
6
|
+
let target = pluck(state, keys);
|
|
7
|
+
if (createMissingObjects)
|
|
8
|
+
target = target || EMPTY;
|
|
9
|
+
return [keys, lastKey, target];
|
|
10
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { JSONPatchOp, State } from '../types.js';
|
|
2
|
+
export declare function getType(state: State, patch: JSONPatchOp): import("../types.js").JSONPatchOpHandler;
|
|
3
|
+
export declare function getTypeLike(state: State, patch: JSONPatchOp): "replace" | "add" | "remove" | "copy" | "move" | "test";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './deepEqual.js';
|
|
2
|
+
export * from './get.js';
|
|
3
|
+
export * from './getOpData.js';
|
|
4
|
+
export * from './getType.js';
|
|
5
|
+
export * from './log.js';
|
|
6
|
+
export * from './ops.js';
|
|
7
|
+
export * from './paths.js';
|
|
8
|
+
export * from './pluck.js';
|
|
9
|
+
export * from './shallowCopy.js';
|
|
10
|
+
export * from './softWrites.js';
|
|
11
|
+
export * from './toArrayIndex.js';
|
|
12
|
+
export * from './toKeys.js';
|
|
13
|
+
export * from './updateArrayIndexes.js';
|
|
14
|
+
export * from './updateArrayPath.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './deepEqual.js';
|
|
2
|
+
export * from './get.js';
|
|
3
|
+
export * from './getOpData.js';
|
|
4
|
+
export * from './getType.js';
|
|
5
|
+
export * from './log.js';
|
|
6
|
+
export * from './ops.js';
|
|
7
|
+
export * from './paths.js';
|
|
8
|
+
export * from './pluck.js';
|
|
9
|
+
export * from './shallowCopy.js';
|
|
10
|
+
export * from './softWrites.js';
|
|
11
|
+
export * from './toArrayIndex.js';
|
|
12
|
+
export * from './toKeys.js';
|
|
13
|
+
export * from './updateArrayIndexes.js';
|
|
14
|
+
export * from './updateArrayPath.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { JSONPatchOp, State } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Check whether this operation is an add operation of some sort (add, copy, move).
|
|
4
|
+
*/
|
|
5
|
+
export declare function isAdd(state: State, op: JSONPatchOp, pathName: 'from' | 'path'): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Transforms an array of ops, returning the original if there is no change, filtering out ops that are dropped.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mapAndFilterOps(ops: JSONPatchOp[], iterator: (op: JSONPatchOp, index: number, breakAfter: (keepRest?: boolean) => {}) => JSONPatchOp | JSONPatchOp[] | null): JSONPatchOp[];
|
|
10
|
+
/**
|
|
11
|
+
* Remove operations that apply to a value which was removed.
|
|
12
|
+
*/
|
|
13
|
+
export declare function updateRemovedOps(state: State, thisPath: string, otherOps: JSONPatchOp[], isRemove?: boolean, updatableObject?: boolean, opOp?: string, customHandler?: (op: JSONPatchOp) => any): JSONPatchOp[];
|
|
14
|
+
export declare function transformRemove(state: State, thisPath: string, otherOps: JSONPatchOp[], isRemove?: boolean): JSONPatchOp[];
|