@dabble/patches 0.2.10 → 0.2.11

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 (87) hide show
  1. package/dist/json-patch/patchProxy.d.ts +41 -0
  2. package/dist/json-patch/patchProxy.js +125 -0
  3. package/dist/json-patch/state.d.ts +2 -0
  4. package/dist/json-patch/state.js +8 -0
  5. package/dist/json-patch/transformPatch.d.ts +19 -0
  6. package/dist/json-patch/transformPatch.js +37 -0
  7. package/dist/json-patch/types.d.ts +52 -0
  8. package/dist/json-patch/types.js +1 -0
  9. package/dist/json-patch/utils/deepEqual.d.ts +1 -0
  10. package/dist/json-patch/utils/deepEqual.js +33 -0
  11. package/dist/json-patch/utils/exit.d.ts +2 -0
  12. package/dist/json-patch/utils/exit.js +4 -0
  13. package/dist/json-patch/utils/get.d.ts +2 -0
  14. package/dist/json-patch/utils/get.js +6 -0
  15. package/dist/json-patch/utils/getOpData.d.ts +2 -0
  16. package/dist/json-patch/utils/getOpData.js +10 -0
  17. package/dist/json-patch/utils/getType.d.ts +3 -0
  18. package/dist/json-patch/utils/getType.js +6 -0
  19. package/dist/json-patch/utils/index.d.ts +14 -0
  20. package/dist/json-patch/utils/index.js +14 -0
  21. package/dist/json-patch/utils/log.d.ts +2 -0
  22. package/dist/json-patch/utils/log.js +7 -0
  23. package/dist/json-patch/utils/ops.d.ts +14 -0
  24. package/dist/json-patch/utils/ops.js +103 -0
  25. package/dist/json-patch/utils/paths.d.ts +9 -0
  26. package/dist/json-patch/utils/paths.js +53 -0
  27. package/dist/json-patch/utils/pluck.d.ts +5 -0
  28. package/dist/json-patch/utils/pluck.js +30 -0
  29. package/dist/json-patch/utils/shallowCopy.d.ts +1 -0
  30. package/dist/json-patch/utils/shallowCopy.js +20 -0
  31. package/dist/json-patch/utils/softWrites.d.ts +7 -0
  32. package/dist/json-patch/utils/softWrites.js +18 -0
  33. package/dist/json-patch/utils/toArrayIndex.d.ts +1 -0
  34. package/dist/json-patch/utils/toArrayIndex.js +12 -0
  35. package/dist/json-patch/utils/toKeys.d.ts +1 -0
  36. package/dist/json-patch/utils/toKeys.js +15 -0
  37. package/dist/json-patch/utils/updateArrayIndexes.d.ts +5 -0
  38. package/dist/json-patch/utils/updateArrayIndexes.js +38 -0
  39. package/dist/json-patch/utils/updateArrayPath.d.ts +5 -0
  40. package/dist/json-patch/utils/updateArrayPath.js +45 -0
  41. package/dist/net/AbstractTransport.d.ts +47 -0
  42. package/dist/net/AbstractTransport.js +39 -0
  43. package/dist/net/PatchesSync.d.ts +47 -0
  44. package/dist/net/PatchesSync.js +289 -0
  45. package/dist/net/index.d.ts +9 -0
  46. package/dist/net/index.js +7 -0
  47. package/dist/net/protocol/JSONRPCClient.d.ts +55 -0
  48. package/dist/net/protocol/JSONRPCClient.js +106 -0
  49. package/dist/net/protocol/types.d.ts +142 -0
  50. package/dist/net/protocol/types.js +1 -0
  51. package/dist/net/types.d.ts +6 -0
  52. package/dist/net/types.js +1 -0
  53. package/dist/net/webrtc/WebRTCAwareness.d.ts +81 -0
  54. package/dist/net/webrtc/WebRTCAwareness.js +119 -0
  55. package/dist/net/webrtc/WebRTCTransport.d.ts +80 -0
  56. package/dist/net/webrtc/WebRTCTransport.js +157 -0
  57. package/dist/net/websocket/PatchesWebSocket.d.ts +107 -0
  58. package/dist/net/websocket/PatchesWebSocket.js +144 -0
  59. package/dist/net/websocket/SignalingService.d.ts +91 -0
  60. package/dist/net/websocket/SignalingService.js +140 -0
  61. package/dist/net/websocket/WebSocketTransport.d.ts +58 -0
  62. package/dist/net/websocket/WebSocketTransport.js +190 -0
  63. package/dist/net/websocket/onlineState.d.ts +9 -0
  64. package/dist/net/websocket/onlineState.js +18 -0
  65. package/dist/persist/InMemoryStore.d.ts +23 -0
  66. package/dist/persist/InMemoryStore.js +103 -0
  67. package/dist/persist/IndexedDBStore.d.ts +81 -0
  68. package/dist/persist/IndexedDBStore.js +377 -0
  69. package/dist/persist/PatchesStore.d.ts +38 -0
  70. package/dist/persist/PatchesStore.js +1 -0
  71. package/dist/persist/index.d.ts +3 -0
  72. package/dist/persist/index.js +3 -0
  73. package/dist/server/PatchesBranchManager.d.ts +40 -0
  74. package/dist/server/PatchesBranchManager.js +138 -0
  75. package/dist/server/PatchesHistoryManager.d.ts +43 -0
  76. package/dist/server/PatchesHistoryManager.js +59 -0
  77. package/dist/server/PatchesServer.d.ts +129 -0
  78. package/dist/server/PatchesServer.js +358 -0
  79. package/dist/server/index.d.ts +3 -0
  80. package/dist/server/index.js +3 -0
  81. package/dist/types.d.ts +164 -0
  82. package/dist/types.js +1 -0
  83. package/dist/utils/batching.d.ts +5 -0
  84. package/dist/utils/batching.js +38 -0
  85. package/dist/utils.d.ts +36 -0
  86. package/dist/utils.js +103 -0
  87. package/package.json +1 -1
@@ -0,0 +1,41 @@
1
+ import { JSONPatch } from './JSONPatch';
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,2 @@
1
+ import type { JSONPatchOpHandlerMap, Runner } from './types.js';
2
+ export declare function runWithObject(object: any, allTypes: JSONPatchOpHandlerMap, shouldCache: boolean, callback: Runner): any;
@@ -0,0 +1,8 @@
1
+ export function runWithObject(object, allTypes, shouldCache, callback) {
2
+ const state = {
3
+ root: { '': object },
4
+ types: allTypes,
5
+ cache: shouldCache ? new Set() : null,
6
+ };
7
+ return callback(state) || state.root[''];
8
+ }
@@ -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,2 @@
1
+ import type { ApplyJSONPatchOptions, JSONPatchOp, State } from '../types.js';
2
+ export declare function exit(state: State, object: any, patch: JSONPatchOp, opts: ApplyJSONPatchOptions): any;
@@ -0,0 +1,4 @@
1
+ export function exit(state, object, patch, opts) {
2
+ opts.error = patch;
3
+ return opts.partial && state.root ? state.root[''] : object;
4
+ }
@@ -0,0 +1,2 @@
1
+ import type { State } from '../types.js';
2
+ export declare function get(state: State, path: string): any;
@@ -0,0 +1,6 @@
1
+ import { getOpData } from './getOpData.js';
2
+ export function get(state, path) {
3
+ // eslint-disable-next-line no-unused-vars
4
+ const [keys, lastKey, target] = getOpData(state, path);
5
+ return target ? target[lastKey] : undefined;
6
+ }
@@ -0,0 +1,2 @@
1
+ import type { State } from '../types.js';
2
+ export declare function getOpData(state: State, path: string, createMissingObjects?: boolean): any[];
@@ -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" | "move" | "copy" | "test";
@@ -0,0 +1,6 @@
1
+ export function getType(state, patch) {
2
+ return state.types?.[patch.op];
3
+ }
4
+ export function getTypeLike(state, patch) {
5
+ return state.types?.[patch.op]?.like;
6
+ }
@@ -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,2 @@
1
+ export declare function verbose(value: boolean): void;
2
+ export declare function log(...rest: any[]): void;
@@ -0,0 +1,7 @@
1
+ let displayLogs = false;
2
+ export function verbose(value) {
3
+ displayLogs = value;
4
+ }
5
+ export function log(...rest) {
6
+ displayLogs && console.log(...rest);
7
+ }
@@ -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[];
@@ -0,0 +1,103 @@
1
+ import { getTypeLike } from './getType.js';
2
+ import { log } from './log.js';
3
+ import { isArrayPath } from './paths.js';
4
+ import { updateArrayIndexes } from './updateArrayIndexes.js';
5
+ /**
6
+ * Check whether this operation is an add operation of some sort (add, copy, move).
7
+ */
8
+ export function isAdd(state, op, pathName) {
9
+ const like = getTypeLike(state, op);
10
+ return (like === 'add' || like === 'copy' || like === 'move') && pathName === 'path';
11
+ }
12
+ /**
13
+ * Transforms an array of ops, returning the original if there is no change, filtering out ops that are dropped.
14
+ */
15
+ export function mapAndFilterOps(ops, iterator) {
16
+ let changed = false;
17
+ const mapped = [];
18
+ let shouldBreak = false;
19
+ let keepRest;
20
+ const breakAfter = (keep) => (shouldBreak = true) && (keepRest = keep);
21
+ for (let i = 0; i < ops.length; i++) {
22
+ const original = ops[i];
23
+ // If an op was copied or moved to the same path, it is a no-op and should be removed
24
+ if (original.from === original.path) {
25
+ if (!changed)
26
+ changed = true;
27
+ continue;
28
+ }
29
+ let value = iterator(original, i, breakAfter);
30
+ if (value && !Array.isArray(value) && value.from === value.path)
31
+ value = null;
32
+ if (!changed && value !== original)
33
+ changed = true;
34
+ if (Array.isArray(value))
35
+ mapped.push(...value);
36
+ else if (value)
37
+ mapped.push(value);
38
+ if (shouldBreak) {
39
+ if (keepRest)
40
+ mapped.push(...ops.slice(i + 1));
41
+ break;
42
+ }
43
+ }
44
+ return changed ? mapped : ops;
45
+ }
46
+ /**
47
+ * Remove operations that apply to a value which was removed.
48
+ */
49
+ export function updateRemovedOps(state, thisPath, otherOps, isRemove = false, updatableObject = false, opOp, customHandler) {
50
+ const softPrefixes = new Set();
51
+ return mapAndFilterOps(otherOps, (op, index, breakAfter) => {
52
+ const opLike = getTypeLike(state, op);
53
+ const canMergeCustom = customHandler && opOp === op.op;
54
+ if (thisPath === op.path && opLike !== 'remove' && !canMergeCustom && !op.soft) {
55
+ // Once an operation sets this value again, we can assume the following ops were working on that and not the
56
+ // old value so they can be kept
57
+ if (op.op !== 'test') {
58
+ breakAfter(true); // stop and keep the remaining ops as-is
59
+ }
60
+ return op;
61
+ }
62
+ const { path, from } = op;
63
+ if (path === thisPath && canMergeCustom) {
64
+ const customOp = customHandler(op);
65
+ if (customOp)
66
+ return customOp;
67
+ }
68
+ if (isRemove && !updatableObject && from === thisPath) {
69
+ // Because of the check above, moves and copies will only hit here when the "from" field matches
70
+ if (opLike === 'move') {
71
+ // We need the rest of the otherOps to be adjusted against this "move"
72
+ breakAfter();
73
+ return transformRemove(state, op.path, otherOps.slice(index + 1));
74
+ }
75
+ else if (opLike === 'copy') {
76
+ // We need future ops on the copied object to be removed
77
+ breakAfter();
78
+ let rest = transformRemove(state, thisPath, otherOps.slice(index + 1));
79
+ rest = transformRemove(state, op.path, rest);
80
+ return rest;
81
+ }
82
+ }
83
+ if (op.soft && path === thisPath) {
84
+ softPrefixes.add(path);
85
+ return null;
86
+ }
87
+ const samePath = (!updatableObject && path === thisPath) || (!softPrefixes.has(thisPath) && path.startsWith(`${thisPath}/`));
88
+ const sameFrom = (!updatableObject && from === thisPath) || (!softPrefixes.has(thisPath) && from?.startsWith(`${thisPath}/`));
89
+ if (samePath || sameFrom) {
90
+ log('Removing', op);
91
+ return null;
92
+ }
93
+ return op;
94
+ });
95
+ }
96
+ export function transformRemove(state, thisPath, otherOps, isRemove) {
97
+ if (isArrayPath(thisPath, state)) {
98
+ return updateArrayIndexes(state, thisPath, otherOps, -1, isRemove);
99
+ }
100
+ else {
101
+ return updateRemovedOps(state, thisPath, otherOps, isRemove);
102
+ }
103
+ }
@@ -0,0 +1,9 @@
1
+ import type { State } from '../types.js';
2
+ export declare function getPrefix(path: string): string;
3
+ export declare function getProp(path: string): string;
4
+ export declare function getPrefixAndProp(path: string): [string, string];
5
+ export declare function getPropAfter(path: string, index: number): string;
6
+ export declare function isArrayPath(path: string, state?: State): boolean;
7
+ export declare function getArrayPrefixAndIndex(state: State, path: string, pathLength?: number): [string, number];
8
+ export declare function getArrayIndex(state: State, path: string, pathLength?: number): number;
9
+ export declare function getIndexAndEnd(state: State, path: string | undefined, maxLength: number): number[];
@@ -0,0 +1,53 @@
1
+ import { getOpData } from './getOpData.js';
2
+ const arrayPathExp = /\/(0|[1-9]\d*)$/;
3
+ const EMPTY = [];
4
+ export function getPrefix(path) {
5
+ const lastSlash = path.lastIndexOf('/');
6
+ return path.slice(0, lastSlash + 1);
7
+ }
8
+ export function getProp(path) {
9
+ const lastSlash = path.lastIndexOf('/');
10
+ return path.slice(lastSlash + 1);
11
+ }
12
+ export function getPrefixAndProp(path) {
13
+ const prefix = getPrefix(path);
14
+ return [prefix, path.slice(prefix.length)];
15
+ }
16
+ export function getPropAfter(path, index) {
17
+ const lastSlash = path.indexOf('/', index);
18
+ return path.slice(index, lastSlash === -1 ? undefined : lastSlash);
19
+ }
20
+ export function isArrayPath(path, state) {
21
+ if (!arrayPathExp.test(path))
22
+ return false;
23
+ if (!state || !state.root || !state.root[''])
24
+ return true;
25
+ // Double-check if this is an array or not
26
+ const [_, __, target] = getOpData(state, path);
27
+ return Array.isArray(target) || target == null;
28
+ }
29
+ export function getArrayPrefixAndIndex(state, path, pathLength) {
30
+ if (pathLength)
31
+ path = path.slice(0, path.indexOf('/', pathLength));
32
+ if (!arrayPathExp.test(path))
33
+ return EMPTY;
34
+ const [_, __, target] = getOpData(state, path);
35
+ if (!Array.isArray(target))
36
+ return EMPTY;
37
+ const [prefix, indexStr] = getPrefixAndProp(path);
38
+ const index = parseInt(indexStr);
39
+ return [prefix, index];
40
+ }
41
+ export function getArrayIndex(state, path, pathLength) {
42
+ return getArrayPrefixAndIndex(state, path, pathLength)[1];
43
+ }
44
+ export function getIndexAndEnd(state, path, maxLength) {
45
+ if (!path)
46
+ return [];
47
+ const prop = getPropAfter(path, maxLength);
48
+ const end = maxLength + prop.length;
49
+ if (!isArrayPath(path.slice(0, end), state))
50
+ return [];
51
+ const index = parseInt(prop);
52
+ return [index, end];
53
+ }
@@ -0,0 +1,5 @@
1
+ import type { State } from '../types.js';
2
+ export declare const EMPTY: {};
3
+ export declare function pluck(state: State, keys: string[]): any;
4
+ export declare function pluckWithShallowCopy(state: State, keys: string[], createMissingObjects?: boolean): any;
5
+ export declare function getValue(state: State, value: any, addKey?: string, addValue?: any): any;
@@ -0,0 +1,30 @@
1
+ import { shallowCopy } from './shallowCopy.js';
2
+ export const EMPTY = {};
3
+ export function pluck(state, keys) {
4
+ let object = state.root;
5
+ for (let i = 0, imax = keys.length - 1; i < imax; i++) {
6
+ const key = keys[i];
7
+ if (!object[key]) {
8
+ return null;
9
+ }
10
+ object = object[key];
11
+ }
12
+ return object;
13
+ }
14
+ export function pluckWithShallowCopy(state, keys, createMissingObjects) {
15
+ let object = state.root;
16
+ for (let i = 0, imax = keys.length - 1; i < imax; i++) {
17
+ const key = keys[i];
18
+ object = object[key] = createMissingObjects && !object[key] ? getValue(state, EMPTY) : getValue(state, object[key]);
19
+ }
20
+ return object;
21
+ }
22
+ export function getValue(state, value, addKey, addValue) {
23
+ if (!state.cache?.has(value)) {
24
+ value = shallowCopy(value);
25
+ state.cache?.add(value);
26
+ }
27
+ if (addKey)
28
+ value[addKey] = addValue;
29
+ return value;
30
+ }
@@ -0,0 +1 @@
1
+ export declare function shallowCopy(obj: any): any;