@dabble/patches 0.1.1

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 (120) hide show
  1. package/README.md +632 -0
  2. package/dist/client/PatchDoc.d.ts +85 -0
  3. package/dist/client/PatchDoc.js +299 -0
  4. package/dist/client/index.d.ts +2 -0
  5. package/dist/client/index.js +1 -0
  6. package/dist/event-signal.d.ts +31 -0
  7. package/dist/event-signal.js +40 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +1 -0
  10. package/dist/json-patch/JSONPatch.d.ts +126 -0
  11. package/dist/json-patch/JSONPatch.js +221 -0
  12. package/dist/json-patch/applyPatch.d.ts +11 -0
  13. package/dist/json-patch/applyPatch.js +37 -0
  14. package/dist/json-patch/composePatch.d.ts +2 -0
  15. package/dist/json-patch/composePatch.js +38 -0
  16. package/dist/json-patch/createJSONPatch.d.ts +35 -0
  17. package/dist/json-patch/createJSONPatch.js +41 -0
  18. package/dist/json-patch/index.d.ts +9 -0
  19. package/dist/json-patch/index.js +8 -0
  20. package/dist/json-patch/invertPatch.d.ts +2 -0
  21. package/dist/json-patch/invertPatch.js +31 -0
  22. package/dist/json-patch/ops/add.d.ts +2 -0
  23. package/dist/json-patch/ops/add.js +52 -0
  24. package/dist/json-patch/ops/bitmask.d.ts +14 -0
  25. package/dist/json-patch/ops/bitmask.js +48 -0
  26. package/dist/json-patch/ops/copy.d.ts +2 -0
  27. package/dist/json-patch/ops/copy.js +34 -0
  28. package/dist/json-patch/ops/increment.d.ts +5 -0
  29. package/dist/json-patch/ops/increment.js +21 -0
  30. package/dist/json-patch/ops/index.d.ts +22 -0
  31. package/dist/json-patch/ops/index.js +25 -0
  32. package/dist/json-patch/ops/move.d.ts +2 -0
  33. package/dist/json-patch/ops/move.js +211 -0
  34. package/dist/json-patch/ops/remove.d.ts +2 -0
  35. package/dist/json-patch/ops/remove.js +31 -0
  36. package/dist/json-patch/ops/replace.d.ts +2 -0
  37. package/dist/json-patch/ops/replace.js +44 -0
  38. package/dist/json-patch/ops/test.d.ts +2 -0
  39. package/dist/json-patch/ops/test.js +22 -0
  40. package/dist/json-patch/ops/text.d.ts +2 -0
  41. package/dist/json-patch/ops/text.js +57 -0
  42. package/dist/json-patch/patchProxy.d.ts +41 -0
  43. package/dist/json-patch/patchProxy.js +125 -0
  44. package/dist/json-patch/state.d.ts +2 -0
  45. package/dist/json-patch/state.js +8 -0
  46. package/dist/json-patch/transformPatch.d.ts +19 -0
  47. package/dist/json-patch/transformPatch.js +37 -0
  48. package/dist/json-patch/types.d.ts +52 -0
  49. package/dist/json-patch/types.js +1 -0
  50. package/dist/json-patch/utils/deepEqual.d.ts +1 -0
  51. package/dist/json-patch/utils/deepEqual.js +33 -0
  52. package/dist/json-patch/utils/exit.d.ts +2 -0
  53. package/dist/json-patch/utils/exit.js +4 -0
  54. package/dist/json-patch/utils/get.d.ts +2 -0
  55. package/dist/json-patch/utils/get.js +6 -0
  56. package/dist/json-patch/utils/getOpData.d.ts +2 -0
  57. package/dist/json-patch/utils/getOpData.js +10 -0
  58. package/dist/json-patch/utils/getType.d.ts +3 -0
  59. package/dist/json-patch/utils/getType.js +6 -0
  60. package/dist/json-patch/utils/index.d.ts +14 -0
  61. package/dist/json-patch/utils/index.js +14 -0
  62. package/dist/json-patch/utils/log.d.ts +2 -0
  63. package/dist/json-patch/utils/log.js +7 -0
  64. package/dist/json-patch/utils/ops.d.ts +14 -0
  65. package/dist/json-patch/utils/ops.js +103 -0
  66. package/dist/json-patch/utils/paths.d.ts +9 -0
  67. package/dist/json-patch/utils/paths.js +53 -0
  68. package/dist/json-patch/utils/pluck.d.ts +5 -0
  69. package/dist/json-patch/utils/pluck.js +30 -0
  70. package/dist/json-patch/utils/shallowCopy.d.ts +1 -0
  71. package/dist/json-patch/utils/shallowCopy.js +20 -0
  72. package/dist/json-patch/utils/softWrites.d.ts +7 -0
  73. package/dist/json-patch/utils/softWrites.js +18 -0
  74. package/dist/json-patch/utils/toArrayIndex.d.ts +1 -0
  75. package/dist/json-patch/utils/toArrayIndex.js +12 -0
  76. package/dist/json-patch/utils/toKeys.d.ts +1 -0
  77. package/dist/json-patch/utils/toKeys.js +15 -0
  78. package/dist/json-patch/utils/updateArrayIndexes.d.ts +5 -0
  79. package/dist/json-patch/utils/updateArrayIndexes.js +38 -0
  80. package/dist/json-patch/utils/updateArrayPath.d.ts +5 -0
  81. package/dist/json-patch/utils/updateArrayPath.js +45 -0
  82. package/dist/net/AbstractTransport.d.ts +47 -0
  83. package/dist/net/AbstractTransport.js +37 -0
  84. package/dist/net/PatchesOfflineFirst.d.ts +3 -0
  85. package/dist/net/PatchesOfflineFirst.js +3 -0
  86. package/dist/net/PatchesRealtime.d.ts +90 -0
  87. package/dist/net/PatchesRealtime.js +257 -0
  88. package/dist/net/index.d.ts +9 -0
  89. package/dist/net/index.js +8 -0
  90. package/dist/net/protocol/JSONRPCClient.d.ts +55 -0
  91. package/dist/net/protocol/JSONRPCClient.js +106 -0
  92. package/dist/net/protocol/types.d.ts +142 -0
  93. package/dist/net/protocol/types.js +1 -0
  94. package/dist/net/webrtc/WebRTCAwareness.d.ts +81 -0
  95. package/dist/net/webrtc/WebRTCAwareness.js +119 -0
  96. package/dist/net/webrtc/WebRTCTransport.d.ts +80 -0
  97. package/dist/net/webrtc/WebRTCTransport.js +157 -0
  98. package/dist/net/websocket/PatchesWebSocket.d.ts +107 -0
  99. package/dist/net/websocket/PatchesWebSocket.js +144 -0
  100. package/dist/net/websocket/SignalingService.d.ts +91 -0
  101. package/dist/net/websocket/SignalingService.js +140 -0
  102. package/dist/net/websocket/WebSocketTransport.d.ts +47 -0
  103. package/dist/net/websocket/WebSocketTransport.js +138 -0
  104. package/dist/persist/IndexedDBStore.d.ts +72 -0
  105. package/dist/persist/IndexedDBStore.js +283 -0
  106. package/dist/persist/index.d.ts +2 -0
  107. package/dist/persist/index.js +1 -0
  108. package/dist/server/BranchManager.d.ts +40 -0
  109. package/dist/server/BranchManager.js +138 -0
  110. package/dist/server/HistoryManager.d.ts +63 -0
  111. package/dist/server/HistoryManager.js +92 -0
  112. package/dist/server/PatchServer.d.ts +129 -0
  113. package/dist/server/PatchServer.js +358 -0
  114. package/dist/server/index.d.ts +4 -0
  115. package/dist/server/index.js +3 -0
  116. package/dist/types.d.ts +158 -0
  117. package/dist/types.js +1 -0
  118. package/dist/utils.d.ts +36 -0
  119. package/dist/utils.js +83 -0
  120. package/package.json +78 -0
@@ -0,0 +1,25 @@
1
+ import { add } from './add.js';
2
+ import { bit } from './bitmask.js';
3
+ import { copy } from './copy.js';
4
+ import { increment } from './increment.js';
5
+ import { move } from './move.js';
6
+ import { remove } from './remove.js';
7
+ import { replace } from './replace.js';
8
+ import { test } from './test.js';
9
+ import { text } from './text.js';
10
+ export * from './bitmask.js';
11
+ export { add, bit, copy, increment, move, remove, replace, test };
12
+ export function getTypes(custom) {
13
+ return {
14
+ test,
15
+ add,
16
+ remove,
17
+ replace,
18
+ copy,
19
+ move,
20
+ '@inc': increment,
21
+ '@bit': bit,
22
+ '@txt': text,
23
+ ...custom,
24
+ };
25
+ }
@@ -0,0 +1,2 @@
1
+ import type { JSONPatchOpHandler } from '../types.js';
2
+ export declare const move: JSONPatchOpHandler;
@@ -0,0 +1,211 @@
1
+ import { getOpData } from '../utils/getOpData.js';
2
+ import { getTypeLike } from '../utils/getType.js';
3
+ import { log } from '../utils/log.js';
4
+ import { isAdd, mapAndFilterOps, updateRemovedOps } from '../utils/ops.js';
5
+ import { getArrayPrefixAndIndex, getIndexAndEnd, isArrayPath } from '../utils/paths.js';
6
+ import { getValue, pluckWithShallowCopy } from '../utils/pluck.js';
7
+ import { toArrayIndex } from '../utils/toArrayIndex.js';
8
+ import { updateArrayIndexes } from '../utils/updateArrayIndexes.js';
9
+ import { add } from './add.js';
10
+ export const move = {
11
+ like: 'move',
12
+ apply(state, path, from) {
13
+ if (path === from)
14
+ return;
15
+ let value;
16
+ const [keys, lastKey, target] = getOpData(state, from);
17
+ if (target === null) {
18
+ return `[op:move] path not found: ${from}`;
19
+ }
20
+ if (Array.isArray(target)) {
21
+ const index = toArrayIndex(target, lastKey);
22
+ if (target.length <= index) {
23
+ return `[op:move] invalid array index: ${path}`;
24
+ }
25
+ value = target[index];
26
+ pluckWithShallowCopy(state, keys, true).splice(index, 1);
27
+ }
28
+ else {
29
+ value = target[lastKey];
30
+ delete pluckWithShallowCopy(state, keys, true)[lastKey];
31
+ }
32
+ return add.apply(state, path, value);
33
+ },
34
+ invert(_state, { path, from }) {
35
+ return { op: 'move', from: path, path: '' + from };
36
+ },
37
+ transform(state, thisOp, otherOps) {
38
+ log('Transforming', otherOps, 'against "move"', thisOp);
39
+ let removed = false;
40
+ const { from, path } = thisOp;
41
+ if (from === path)
42
+ return otherOps;
43
+ const [fromPrefix, fromIndex] = getArrayPrefixAndIndex(state, from);
44
+ const [pathPrefix, pathIndex] = getArrayPrefixAndIndex(state, path);
45
+ const isPathArray = pathPrefix !== undefined;
46
+ const isSameArray = isPathArray && pathPrefix === fromPrefix;
47
+ /*
48
+ A move needs to do a "remove" and an "add" at once with `from` and `path`. If it is being moved from one location in
49
+ an array to another in the same array, this needs to be handled special.
50
+
51
+ 1. Ops that were added to where the move lands when not an array should be removed just like with an add/copy
52
+ 2. Ops that were added to where the move came from should be translated to the new path
53
+ 3. Ops that are in an array with the moved item after need to be adjusted up or down
54
+ 3a. But, ops that were translated to the new path shouldn't get adjusted up or down by these adjustments
55
+ */
56
+ // A move removes the value from one place then adds it to another, update the paths and add a marker to them so
57
+ // they won't be altered by `updateArrayIndexes`, then remove the markers afterwards
58
+ otherOps = mapAndFilterOps(otherOps, otherOp => {
59
+ if (removed) {
60
+ return otherOp;
61
+ }
62
+ const opLike = getTypeLike(state, otherOp);
63
+ if (opLike === 'remove' && from === otherOp.path) {
64
+ // Once an operation removes the moved value, the following ops should be working on the old location and not
65
+ // not the new one. Allow the following operations (which may include add/remove) to affect the old location
66
+ removed = true;
67
+ }
68
+ const original = otherOp;
69
+ otherOp = updateMovePath(state, otherOp, 'path', from, path, original);
70
+ otherOp = updateMovePath(state, otherOp, 'from', from, path, original);
71
+ return otherOp;
72
+ });
73
+ // Remove/adjust items that were affected by this item moving (those that actually moved because of it will not
74
+ // be affected because they have a temporary $ marker prefix that will keep them from doing so)
75
+ if (isSameArray) {
76
+ // need special logic when a move is within one array
77
+ otherOps = updateArrayIndexesForMove(state, fromPrefix, fromIndex, pathIndex, otherOps);
78
+ }
79
+ else {
80
+ // if a move is not within one array, treat it as a remove then add
81
+ if (isArrayPath(from, state)) {
82
+ otherOps = updateArrayIndexes(state, from, otherOps, -1);
83
+ }
84
+ else {
85
+ otherOps = updateRemovedOps(state, from, otherOps);
86
+ }
87
+ if (isArrayPath(path, state)) {
88
+ otherOps = updateArrayIndexes(state, path, otherOps, 1);
89
+ }
90
+ else {
91
+ otherOps = updateRemovedOps(state, path, otherOps);
92
+ }
93
+ }
94
+ // Remove the move markers added with `updateMovePath`
95
+ return mapAndFilterOps(otherOps, removeMoveMarkers);
96
+ },
97
+ };
98
+ /**
99
+ * Update paths for a move operation, adding a marker so the path will not be altered by array updates.
100
+ */
101
+ function updateMovePath(state, op, pathName, from, to, original) {
102
+ const path = op[pathName];
103
+ if (!path)
104
+ return op; // No adjustment needed on a property that doesn't exist
105
+ // If a value is being added or copied to the old location it should not be adjusted
106
+ if (isAdd(state, op, pathName) && op.path === from) {
107
+ return op;
108
+ }
109
+ // If this path needs to be changed due to a move operation, change it, but prefix it with a $ temporarily so when we
110
+ // adjust the array indexes to account for this change, we aren't changing this path we JUST set. We will remove the
111
+ // $ prefix right after we adjust arrays affected by this move.
112
+ if (path === from || path.indexOf(from + '/') === 0) {
113
+ if (op === original)
114
+ op = Object.assign({}, op);
115
+ log('Moving', op, 'from', from, 'to', to);
116
+ // Add a marker "$" so this path will not be double-updated by array index updates
117
+ op[pathName] = '$' + path.replace(from, to);
118
+ }
119
+ return op;
120
+ }
121
+ /**
122
+ * Update array indexes to account for values being added or removed from an array. If the path is not an array index
123
+ * or if nothing is changed then the original array is returned.
124
+ */
125
+ function updateArrayIndexesForMove(state, prefix, fromIndex, pathIndex, otherOps) {
126
+ // Check ops for any that need to be replaced
127
+ log(`Shifting array indexes for a move between ${prefix}/${fromIndex} and ${prefix}/${pathIndex}`);
128
+ return mapAndFilterOps(otherOps, otherOp => {
129
+ // check for items from the same array that will be affected
130
+ const fromUpdate = updateArrayPathForMove(state, otherOp, 'from', prefix, fromIndex, pathIndex);
131
+ const pathUpdate = updateArrayPathForMove(state, otherOp, 'path', prefix, fromIndex, pathIndex);
132
+ if (!fromUpdate || !pathUpdate)
133
+ return null;
134
+ if (fromUpdate !== otherOp || pathUpdate !== otherOp) {
135
+ otherOp = { ...otherOp, path: pathUpdate.path };
136
+ if (fromUpdate.from)
137
+ otherOp.from = fromUpdate.from;
138
+ }
139
+ return otherOp;
140
+ });
141
+ }
142
+ /**
143
+ * Get the adjusted path if it is higher, or undefined if not.
144
+ */
145
+ function updateArrayPathForMove(state, otherOp, pathName, prefix, from, to) {
146
+ const path = otherOp[pathName];
147
+ if (!path || !path.startsWith(prefix))
148
+ return otherOp;
149
+ const min = Math.min(from, to);
150
+ const max = Math.max(from, to);
151
+ const [otherIndex, end] = getIndexAndEnd(state, path, prefix.length);
152
+ if (otherIndex === undefined)
153
+ return otherOp; // if a prop on an array is being set, for e.g.
154
+ const isFinalProp = end === path.length;
155
+ const opLike = getTypeLike(state, otherOp);
156
+ // If this index is not within the movement boundary, don't touch it
157
+ if (otherIndex < min || otherIndex > max) {
158
+ return otherOp;
159
+ }
160
+ // If the index touches the boundary on an unaffected side, don't touch it
161
+ if (isFinalProp && isAdd(state, otherOp, pathName)) {
162
+ /*
163
+ if the move is from low to high (min is a remove, max is an add) then
164
+ use the remove logic with an add
165
+
166
+ if the move is from high to low (min is an add, max is a remove) then
167
+ use the add logic at the bottom
168
+ */
169
+ if (otherIndex === min) {
170
+ if (min === from) {
171
+ // treat like a remove
172
+ return otherOp;
173
+ }
174
+ else {
175
+ // treat like an add
176
+ return otherOp;
177
+ }
178
+ }
179
+ else if (otherIndex === max) {
180
+ if (max === from) {
181
+ // treat like a remove
182
+ const fromIndex = getIndexAndEnd(state, otherOp.from, prefix.length)[0];
183
+ if (opLike === 'move' && pathName === 'path' && to <= fromIndex && fromIndex < from)
184
+ return otherOp;
185
+ // continue
186
+ }
187
+ else {
188
+ // treat like an add
189
+ return otherOp;
190
+ }
191
+ }
192
+ }
193
+ const modifier = from === min ? -1 : 1;
194
+ const newPath = prefix + (otherIndex + modifier) + path.slice(end);
195
+ return getValue(state, otherOp, pathName, newPath);
196
+ }
197
+ /**
198
+ * Remove any move markers placed during updateMovePath. This occurs in-place since these objects have already been
199
+ * cloned.
200
+ */
201
+ function removeMoveMarkers(op) {
202
+ if (op.path[0] === '$') {
203
+ op.path = op.path.slice(1);
204
+ }
205
+ if (op.from && op.from[0] === '$') {
206
+ op.from = op.from.slice(1);
207
+ }
208
+ if (op.from === op.path)
209
+ return null;
210
+ return op;
211
+ }
@@ -0,0 +1,2 @@
1
+ import type { JSONPatchOpHandler } from '../types.js';
2
+ export declare const remove: JSONPatchOpHandler;
@@ -0,0 +1,31 @@
1
+ import { getOpData } from '../utils/getOpData.js';
2
+ import { log } from '../utils/log.js';
3
+ import { transformRemove } from '../utils/ops.js';
4
+ import { pluckWithShallowCopy } from '../utils/pluck.js';
5
+ import { toArrayIndex } from '../utils/toArrayIndex.js';
6
+ export const remove = {
7
+ like: 'remove',
8
+ apply(state, path) {
9
+ const [keys, lastKey, target] = getOpData(state, path);
10
+ if (target === null) {
11
+ return;
12
+ }
13
+ if (Array.isArray(target)) {
14
+ const index = toArrayIndex(target, lastKey);
15
+ if (target.length <= index) {
16
+ return '[op:remove] invalid array index: ' + path;
17
+ }
18
+ pluckWithShallowCopy(state, keys).splice(index, 1);
19
+ }
20
+ else {
21
+ delete pluckWithShallowCopy(state, keys)[lastKey];
22
+ }
23
+ },
24
+ invert(_state, { path }, value) {
25
+ return { op: 'add', path, value };
26
+ },
27
+ transform(state, thisOp, otherOps) {
28
+ log('Transforming', otherOps, 'against "remove"', thisOp);
29
+ return transformRemove(state, thisOp.path, otherOps, true);
30
+ },
31
+ };
@@ -0,0 +1,2 @@
1
+ import type { JSONPatchOpHandler } from '../types.js';
2
+ export declare const replace: JSONPatchOpHandler;
@@ -0,0 +1,44 @@
1
+ import { deepEqual } from '../utils/deepEqual.js';
2
+ import { getOpData } from '../utils/getOpData.js';
3
+ import { log } from '../utils/log.js';
4
+ import { updateRemovedOps } from '../utils/ops.js';
5
+ import { pluckWithShallowCopy } from '../utils/pluck.js';
6
+ import { toArrayIndex } from '../utils/toArrayIndex.js';
7
+ export const replace = {
8
+ like: 'replace',
9
+ apply(state, path, value) {
10
+ if (typeof value === 'undefined') {
11
+ return '[op:replace] require value, but got undefined';
12
+ }
13
+ const [keys, lastKey, target] = getOpData(state, path, true);
14
+ if (target === null) {
15
+ return `[op:replace] path not found: ${path}`;
16
+ }
17
+ if (Array.isArray(target)) {
18
+ const index = toArrayIndex(target, lastKey);
19
+ if (target.length <= index) {
20
+ return `[op:replace] invalid array index: ${path}`;
21
+ }
22
+ if (!deepEqual(target[index], value)) {
23
+ pluckWithShallowCopy(state, keys, true).splice(index, 1, value);
24
+ }
25
+ }
26
+ else {
27
+ if (!deepEqual(target[lastKey], value)) {
28
+ pluckWithShallowCopy(state, keys, true)[lastKey] = value;
29
+ }
30
+ }
31
+ },
32
+ invert(_state, { path }, value, changedObj) {
33
+ if (path.endsWith('/-'))
34
+ path = path.replace('-', changedObj.length);
35
+ return value === undefined ? { op: 'remove', path } : { op: 'replace', path, value };
36
+ },
37
+ transform(state, thisOp, otherOps) {
38
+ log('Transforming ', otherOps, ' against "replace"', thisOp);
39
+ return updateRemovedOps(state, thisOp.path, otherOps);
40
+ },
41
+ compose(_state, _value1, value2) {
42
+ return value2;
43
+ },
44
+ };
@@ -0,0 +1,2 @@
1
+ import type { JSONPatchOpHandler } from '../types.js';
2
+ export declare const test: JSONPatchOpHandler;
@@ -0,0 +1,22 @@
1
+ import { deepEqual } from '../utils/deepEqual.js';
2
+ import { getOpData } from '../utils/getOpData.js';
3
+ export const test = {
4
+ like: 'test',
5
+ apply(state, path, expected) {
6
+ const [, lastKey, target] = getOpData(state, path);
7
+ if (target === null) {
8
+ return `[op:test] path not found: ${path}`;
9
+ }
10
+ if (!deepEqual(target[lastKey], expected)) {
11
+ const a = JSON.stringify(target[lastKey]);
12
+ const b = JSON.stringify(expected);
13
+ return `[op:test] not matched: ${a} ${b}`;
14
+ }
15
+ },
16
+ invert() {
17
+ return undefined;
18
+ },
19
+ transform(_state, _other, ops) {
20
+ return ops;
21
+ },
22
+ };
@@ -0,0 +1,2 @@
1
+ import type { JSONPatchOpHandler } from '../types.js';
2
+ export declare const text: JSONPatchOpHandler;
@@ -0,0 +1,57 @@
1
+ import { Delta } from '@dabble/delta';
2
+ import { replace } from '../ops/replace.js';
3
+ import { get } from '../utils/get.js';
4
+ import { log } from '../utils/log.js';
5
+ import { updateRemovedOps } from '../utils/ops.js';
6
+ export const text = {
7
+ like: 'replace',
8
+ apply(state, path, value) {
9
+ const delta = Array.isArray(value) ? new Delta(value) : value;
10
+ if (!delta || !Array.isArray(delta.ops)) {
11
+ return 'Invalid delta';
12
+ }
13
+ let existingData = get(state, path);
14
+ let doc;
15
+ if (Array.isArray(existingData)) {
16
+ if (existingData.length && existingData[0].insert) {
17
+ doc = new Delta(existingData);
18
+ }
19
+ }
20
+ else if (existingData && existingData.ops) {
21
+ doc = new Delta(existingData.ops);
22
+ }
23
+ if (!doc) {
24
+ doc = new Delta().insert('\n');
25
+ }
26
+ doc = doc.compose(delta);
27
+ if (hasInvalidOps(doc)) {
28
+ return 'Invalid text delta provided for this text document';
29
+ }
30
+ return replace.apply(state, path, doc);
31
+ },
32
+ transform(state, thisOp, otherOps) {
33
+ log('Transforming ', otherOps, ' against "@txt"', thisOp);
34
+ return updateRemovedOps(state, thisOp.path, otherOps, false, true, thisOp.op, op => {
35
+ if (op.path !== thisOp.path)
36
+ return null; // If a subpath, it is overwritten
37
+ if (!op.value || !Array.isArray(op.value))
38
+ return null; // If not a delta, it is overwritten
39
+ const thisDelta = new Delta(thisOp.value);
40
+ let otherDelta = new Delta(op.value);
41
+ otherDelta = thisDelta.transform(otherDelta, true);
42
+ return { ...op, value: otherDelta.ops };
43
+ });
44
+ },
45
+ invert(state, { path, value }, oldValue, changedObj) {
46
+ if (path.endsWith('/-'))
47
+ path = path.replace('-', changedObj.length);
48
+ const delta = new Delta(value);
49
+ return oldValue === undefined ? { op: 'remove', path } : { op: '@txt', path, value: delta.invert(oldValue) };
50
+ },
51
+ compose(state, delta1, delta2) {
52
+ return new Delta(delta1).compose(new Delta(delta2));
53
+ },
54
+ };
55
+ function hasInvalidOps(doc) {
56
+ return doc.ops.some(op => typeof op.insert !== 'string' && (typeof op.insert !== 'object' || op.insert === null));
57
+ }
@@ -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
+ }