patch-recorder 0.1.0 → 0.2.0
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/README.md +1 -75
- package/dist/arrays.d.ts +2 -2
- package/dist/arrays.d.ts.map +1 -1
- package/dist/arrays.js +12 -20
- package/dist/arrays.js.map +1 -1
- package/dist/index.d.ts +1 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -34
- package/dist/index.js.map +1 -1
- package/dist/maps.d.ts +2 -2
- package/dist/maps.d.ts.map +1 -1
- package/dist/maps.js +4 -20
- package/dist/maps.js.map +1 -1
- package/dist/optimizer.d.ts +16 -1
- package/dist/optimizer.d.ts.map +1 -1
- package/dist/optimizer.js +114 -15
- package/dist/optimizer.js.map +1 -1
- package/dist/patches.d.ts +5 -5
- package/dist/patches.d.ts.map +1 -1
- package/dist/patches.js +5 -5
- package/dist/patches.js.map +1 -1
- package/dist/proxy.d.ts +2 -2
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +24 -52
- package/dist/proxy.js.map +1 -1
- package/dist/sets.d.ts +2 -2
- package/dist/sets.d.ts.map +1 -1
- package/dist/sets.js +4 -20
- package/dist/sets.js.map +1 -1
- package/dist/types.d.ts +14 -33
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +28 -13
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +76 -20
- package/dist/utils.js.map +1 -1
- package/package.json +2 -1
- package/src/arrays.ts +22 -30
- package/src/index.ts +9 -54
- package/src/maps.ts +12 -30
- package/src/optimizer.ts +146 -28
- package/src/patches.ts +20 -21
- package/src/proxy.ts +31 -60
- package/src/sets.ts +11 -30
- package/src/types.ts +16 -40
- package/src/utils.ts +81 -28
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS;;;;CAIZ,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEjE
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS;;;;CAIZ,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;AAEnF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,eAAe,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;AAE9D,MAAM,MAAM,KAAK,GAAG;IACnB,IAAI,EAAE,SAAS,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC;AAE9B,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACpC;;OAEG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,YAAY,IAAI,CAAC,CAAC;AAE9C,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,YAAY;IACpD,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,oBAAoB,CAAC;IAC9B;;OAEG;IACH,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACjC"}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GetItemIdConfig, GetItemIdFunction, PatchPath } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Type guard to check if a value is a plain object (not null, not array, not Map/Set)
|
|
4
4
|
*/
|
|
@@ -18,9 +18,7 @@ export declare function isSet(value: unknown): value is Set<unknown>;
|
|
|
18
18
|
/**
|
|
19
19
|
* Format a path array to either array format or JSON Pointer string format
|
|
20
20
|
*/
|
|
21
|
-
export declare function formatPath(path:
|
|
22
|
-
internalPatchesOptions: PatchesOptions;
|
|
23
|
-
}): string | (string | number)[];
|
|
21
|
+
export declare function formatPath(path: PatchPath): string;
|
|
24
22
|
/**
|
|
25
23
|
* Check if two values are deeply equal
|
|
26
24
|
*/
|
|
@@ -31,13 +29,30 @@ export declare function isEqual(a: unknown, b: unknown): boolean;
|
|
|
31
29
|
*/
|
|
32
30
|
export declare function cloneIfNeeded<T>(value: T): T;
|
|
33
31
|
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
export declare function findGetItemIdFn(path:
|
|
32
|
+
* Find a getItemId function for a given path.
|
|
33
|
+
* The function is looked up by traversing the getItemId config object
|
|
34
|
+
* using the parent path (all elements except the last one).
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // For path ['items', 3] with config { items: (item) => item.id }
|
|
38
|
+
* // Returns the function (item) => item.id
|
|
39
|
+
*/
|
|
40
|
+
export declare function findGetItemIdFn(path: PatchPath, getItemIdConfig: GetItemIdConfig | undefined): GetItemIdFunction | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Convert a path array or string to a string key for optimized lookup.
|
|
43
|
+
* Uses null character (\x00) as delimiter since it's unlikely in property names.
|
|
44
|
+
* This is significantly faster than JSON.stringify for the common case.
|
|
45
|
+
*
|
|
46
|
+
* @param path - The path array or string to convert
|
|
47
|
+
* @returns A string key representation of the path
|
|
48
|
+
*/
|
|
49
|
+
export declare function pathToKey(path: PatchPath): string;
|
|
50
|
+
/**
|
|
51
|
+
* Convert a string key back to a path array.
|
|
52
|
+
* This is the inverse of pathToKey.
|
|
53
|
+
*
|
|
54
|
+
* @param key - The string key to convert
|
|
55
|
+
* @returns The path array
|
|
56
|
+
*/
|
|
57
|
+
export declare function keyToPath(key: string): (string | number)[];
|
|
43
58
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,eAAe,EAAE,iBAAiB,EAAE,SAAS,EAAC,MAAM,YAAY,CAAC;AAE9E;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,EAAE,CAE1D;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAEpE;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAE3D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAMlD;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CA2CvD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAmC5C;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC9B,IAAI,EAAE,SAAS,EACf,eAAe,EAAE,eAAe,GAAG,SAAS,GAC1C,iBAAiB,GAAG,SAAS,CA6C/B;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CA4BjD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAc1D"}
|
package/dist/utils.js
CHANGED
|
@@ -29,19 +29,12 @@ export function isSet(value) {
|
|
|
29
29
|
/**
|
|
30
30
|
* Format a path array to either array format or JSON Pointer string format
|
|
31
31
|
*/
|
|
32
|
-
export function formatPath(path
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Convert to JSON Pointer string format (RFC 6901)
|
|
37
|
-
if (path.length === 0) {
|
|
38
|
-
return '';
|
|
39
|
-
}
|
|
40
|
-
return '/' + path
|
|
41
|
-
.map((part) => String(part).replace(/~/g, '~0').replace(/\//g, '~1'))
|
|
42
|
-
.join('/');
|
|
32
|
+
export function formatPath(path) {
|
|
33
|
+
// Convert to JSON Pointer string format (RFC 6901)
|
|
34
|
+
if (path.length === 0) {
|
|
35
|
+
return '';
|
|
43
36
|
}
|
|
44
|
-
return path;
|
|
37
|
+
return '/' + path.map((part) => String(part).replace(/~/g, '~0').replace(/\//g, '~1')).join('/');
|
|
45
38
|
}
|
|
46
39
|
/**
|
|
47
40
|
* Check if two values are deeply equal
|
|
@@ -128,14 +121,14 @@ export function cloneIfNeeded(value) {
|
|
|
128
121
|
return cloned;
|
|
129
122
|
}
|
|
130
123
|
/**
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
124
|
+
* Find a getItemId function for a given path.
|
|
125
|
+
* The function is looked up by traversing the getItemId config object
|
|
126
|
+
* using the parent path (all elements except the last one).
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* // For path ['items', 3] with config { items: (item) => item.id }
|
|
130
|
+
* // Returns the function (item) => item.id
|
|
131
|
+
*/
|
|
139
132
|
export function findGetItemIdFn(path, getItemIdConfig) {
|
|
140
133
|
if (!getItemIdConfig || path.length === 0) {
|
|
141
134
|
return undefined;
|
|
@@ -160,6 +153,10 @@ export function findGetItemIdFn(path, getItemIdConfig) {
|
|
|
160
153
|
if (current === undefined || typeof current !== 'object') {
|
|
161
154
|
return undefined;
|
|
162
155
|
}
|
|
156
|
+
if (typeof key === 'object' || typeof key === 'symbol') {
|
|
157
|
+
// there is no way to match an object or symbol key in the config
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
163
160
|
current = current[key];
|
|
164
161
|
}
|
|
165
162
|
// current should now be a function or undefined
|
|
@@ -168,4 +165,63 @@ export function findGetItemIdFn(path, getItemIdConfig) {
|
|
|
168
165
|
}
|
|
169
166
|
return undefined;
|
|
170
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Convert a path array or string to a string key for optimized lookup.
|
|
170
|
+
* Uses null character (\x00) as delimiter since it's unlikely in property names.
|
|
171
|
+
* This is significantly faster than JSON.stringify for the common case.
|
|
172
|
+
*
|
|
173
|
+
* @param path - The path array or string to convert
|
|
174
|
+
* @returns A string key representation of the path
|
|
175
|
+
*/
|
|
176
|
+
export function pathToKey(path) {
|
|
177
|
+
// If path is already a string, use it directly
|
|
178
|
+
if (typeof path === 'string') {
|
|
179
|
+
return path;
|
|
180
|
+
}
|
|
181
|
+
// Otherwise convert array to string
|
|
182
|
+
if (path.length === 0) {
|
|
183
|
+
return '';
|
|
184
|
+
}
|
|
185
|
+
if (path.length === 1) {
|
|
186
|
+
const elem = path[0];
|
|
187
|
+
if (typeof elem === 'symbol') {
|
|
188
|
+
return elem.toString();
|
|
189
|
+
}
|
|
190
|
+
if (typeof elem === 'object') {
|
|
191
|
+
return JSON.stringify(elem);
|
|
192
|
+
}
|
|
193
|
+
return String(elem);
|
|
194
|
+
}
|
|
195
|
+
return path.map((elem) => {
|
|
196
|
+
if (typeof elem === 'symbol') {
|
|
197
|
+
return elem.toString();
|
|
198
|
+
}
|
|
199
|
+
if (typeof elem === 'object') {
|
|
200
|
+
return JSON.stringify(elem);
|
|
201
|
+
}
|
|
202
|
+
return String(elem);
|
|
203
|
+
}).join('\x00');
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Convert a string key back to a path array.
|
|
207
|
+
* This is the inverse of pathToKey.
|
|
208
|
+
*
|
|
209
|
+
* @param key - The string key to convert
|
|
210
|
+
* @returns The path array
|
|
211
|
+
*/
|
|
212
|
+
export function keyToPath(key) {
|
|
213
|
+
if (key === '') {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
if (key.indexOf('\x00') === -1) {
|
|
217
|
+
// No delimiter, single element
|
|
218
|
+
// Try to parse as number for consistency
|
|
219
|
+
const num = Number(key);
|
|
220
|
+
return isNaN(num) ? [key] : [num];
|
|
221
|
+
}
|
|
222
|
+
return key.split('\x00').map((part) => {
|
|
223
|
+
const num = Number(part);
|
|
224
|
+
return isNaN(num) ? part : num;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
171
227
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACtC,OAAO,CACN,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACrB,CAAC,CAAC,KAAK,YAAY,GAAG,CAAC;QACvB,CAAC,CAAC,KAAK,YAAY,GAAG,CAAC,CACvB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,KAAc;IACrC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,KAAK,CAAC,KAAc;IACnC,OAAO,KAAK,YAAY,GAAG,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,KAAK,CAAC,KAAc;IACnC,OAAO,KAAK,YAAY,GAAG,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patch-recorder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Record JSON patches (RFC 6902) from mutations applied to objects, arrays, Maps, and Sets via a proxy interface.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"patch",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"dev": "as-soon -w src pnpm build",
|
|
56
56
|
"test": "vitest run",
|
|
57
57
|
"test:watch": "vitest",
|
|
58
|
+
"bench": "vitest bench",
|
|
58
59
|
"benchmark": "tsx benchmark/index.ts"
|
|
59
60
|
}
|
|
60
61
|
}
|
package/src/arrays.ts
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
import type {RecorderState} from './types.js';
|
|
1
|
+
import type {NonPrimitive, PatchPath, RecorderState} from './types.js';
|
|
2
2
|
import {generateAddPatch, generateDeletePatch, generateReplacePatch} from './patches.js';
|
|
3
3
|
import {createProxy} from './proxy.js';
|
|
4
4
|
|
|
5
5
|
// Module-level Sets for O(1) lookup instead of O(n) array includes
|
|
6
|
-
const MUTATING_METHODS = new Set([
|
|
7
|
-
'push',
|
|
8
|
-
'pop',
|
|
9
|
-
'shift',
|
|
10
|
-
'unshift',
|
|
11
|
-
'splice',
|
|
12
|
-
'sort',
|
|
13
|
-
'reverse',
|
|
14
|
-
]);
|
|
6
|
+
const MUTATING_METHODS = new Set(['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']);
|
|
15
7
|
|
|
16
8
|
const NON_MUTATING_METHODS = new Set([
|
|
17
9
|
'map',
|
|
@@ -38,27 +30,27 @@ const NON_MUTATING_METHODS = new Set([
|
|
|
38
30
|
* Handle array method calls and property access
|
|
39
31
|
*/
|
|
40
32
|
export function handleArrayGet(
|
|
41
|
-
|
|
33
|
+
array: unknown[],
|
|
42
34
|
prop: string,
|
|
43
|
-
path:
|
|
44
|
-
state: RecorderState<
|
|
35
|
+
path: PatchPath,
|
|
36
|
+
state: RecorderState<NonPrimitive>,
|
|
45
37
|
): any {
|
|
46
38
|
// Mutating methods
|
|
47
39
|
if (MUTATING_METHODS.has(prop)) {
|
|
48
|
-
return (...args:
|
|
40
|
+
return (...args: unknown[]) => {
|
|
49
41
|
// Optimized: only copy what's needed for each method
|
|
50
|
-
const oldLength =
|
|
51
|
-
let oldValue:
|
|
42
|
+
const oldLength = array.length;
|
|
43
|
+
let oldValue: unknown[] | null = null;
|
|
52
44
|
|
|
53
45
|
// Only create full copy for sort/reverse which need the entire old array
|
|
54
46
|
if (prop === 'sort' || prop === 'reverse') {
|
|
55
|
-
oldValue = [...
|
|
47
|
+
oldValue = [...array];
|
|
56
48
|
}
|
|
57
49
|
|
|
58
|
-
const result = (Array.prototype as any)[prop].apply(
|
|
50
|
+
const result = (Array.prototype as any)[prop].apply(array, args);
|
|
59
51
|
|
|
60
52
|
// Generate patches based on the method
|
|
61
|
-
generateArrayPatches(state,
|
|
53
|
+
generateArrayPatches(state, array, prop, args, result, path, oldValue, oldLength);
|
|
62
54
|
|
|
63
55
|
return result;
|
|
64
56
|
};
|
|
@@ -66,15 +58,15 @@ export function handleArrayGet(
|
|
|
66
58
|
|
|
67
59
|
// Non-mutating methods - just return them bound to the array
|
|
68
60
|
if (NON_MUTATING_METHODS.has(prop)) {
|
|
69
|
-
return (Array.prototype as any)[prop].bind(
|
|
61
|
+
return (Array.prototype as any)[prop].bind(array);
|
|
70
62
|
}
|
|
71
63
|
|
|
72
64
|
// Property access
|
|
73
65
|
if (prop === 'length') {
|
|
74
|
-
return
|
|
66
|
+
return array.length;
|
|
75
67
|
}
|
|
76
68
|
|
|
77
|
-
const value =
|
|
69
|
+
const value = array[prop as any];
|
|
78
70
|
|
|
79
71
|
// For numeric properties (array indices), check if the value is an object/array
|
|
80
72
|
// If so, return a proxy to enable nested mutation tracking
|
|
@@ -92,13 +84,13 @@ export function handleArrayGet(
|
|
|
92
84
|
* Generate patches for array mutations
|
|
93
85
|
*/
|
|
94
86
|
function generateArrayPatches(
|
|
95
|
-
state: RecorderState<
|
|
96
|
-
|
|
87
|
+
state: RecorderState<NonPrimitive>,
|
|
88
|
+
array: unknown[],
|
|
97
89
|
method: string,
|
|
98
|
-
args:
|
|
90
|
+
args: unknown[],
|
|
99
91
|
result: any,
|
|
100
|
-
path:
|
|
101
|
-
|
|
92
|
+
path: PatchPath,
|
|
93
|
+
oldArray: unknown[] | null,
|
|
102
94
|
oldLength: number,
|
|
103
95
|
) {
|
|
104
96
|
switch (method) {
|
|
@@ -116,7 +108,7 @@ function generateArrayPatches(
|
|
|
116
108
|
case 'pop': {
|
|
117
109
|
if (state.options.arrayLengthAssignment !== false) {
|
|
118
110
|
// Generate length replace patch (mutative uses this instead of remove)
|
|
119
|
-
generateReplacePatch(state, [...path, 'length'],
|
|
111
|
+
generateReplacePatch(state, [...path, 'length'], array.length, oldLength);
|
|
120
112
|
} else {
|
|
121
113
|
// When arrayLengthAssignment is false, generate remove patch for last element
|
|
122
114
|
generateDeletePatch(state, [...path, oldLength - 1], result);
|
|
@@ -140,7 +132,7 @@ function generateArrayPatches(
|
|
|
140
132
|
}
|
|
141
133
|
|
|
142
134
|
case 'splice': {
|
|
143
|
-
const [start, deleteCount = 0, ...addItems] = args;
|
|
135
|
+
const [start, deleteCount = 0, ...addItems] = args as number[];
|
|
144
136
|
const actualStart = start < 0 ? Math.max(oldLength + start, 0) : Math.min(start, oldLength);
|
|
145
137
|
const actualDeleteCount = Math.min(deleteCount, oldLength - actualStart);
|
|
146
138
|
const minCount = Math.min(actualDeleteCount, addItems.length);
|
|
@@ -172,7 +164,7 @@ function generateArrayPatches(
|
|
|
172
164
|
case 'reverse': {
|
|
173
165
|
// These reorder the entire array - generate full replace
|
|
174
166
|
// oldValue contains the array before the mutation
|
|
175
|
-
generateReplacePatch(state, path,
|
|
167
|
+
generateReplacePatch(state, path, array, oldArray);
|
|
176
168
|
break;
|
|
177
169
|
}
|
|
178
170
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import {createProxy} from './proxy.js';
|
|
2
2
|
import {compressPatches} from './optimizer.js';
|
|
3
|
-
import type {
|
|
4
|
-
NonPrimitive,
|
|
5
|
-
Draft,
|
|
6
|
-
RecordPatchesOptions,
|
|
7
|
-
Patches,
|
|
8
|
-
Patch,
|
|
9
|
-
Operation,
|
|
10
|
-
} from './types.js';
|
|
3
|
+
import type {NonPrimitive, Draft, RecordPatchesOptions, Patches} from './types.js';
|
|
11
4
|
|
|
12
5
|
/**
|
|
13
6
|
* Record JSON patches from mutations applied to an object, array, Map, or Set.
|
|
@@ -26,24 +19,18 @@ import type {
|
|
|
26
19
|
* console.log(state.user.name); // 'Jane' (mutated in place!)
|
|
27
20
|
* console.log(patches); // [{ op: 'replace', path: ['user', 'name'], value: 'Jane' }]
|
|
28
21
|
*/
|
|
29
|
-
export function recordPatches<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
): Patches<true> {
|
|
34
|
-
const internalPatchesOptions = {
|
|
35
|
-
pathAsArray: options.pathAsArray ?? true,
|
|
36
|
-
arrayLengthAssignment: options.arrayLengthAssignment ?? true,
|
|
37
|
-
};
|
|
38
|
-
|
|
22
|
+
export function recordPatches<
|
|
23
|
+
T extends NonPrimitive,
|
|
24
|
+
PatchesOption extends RecordPatchesOptions = {},
|
|
25
|
+
>(state: T, mutate: (state: Draft<T>) => void, options?: PatchesOption): Patches {
|
|
39
26
|
const recorderState = {
|
|
40
|
-
|
|
27
|
+
state,
|
|
41
28
|
patches: [],
|
|
42
29
|
basePath: [],
|
|
43
30
|
options: {
|
|
44
31
|
...options,
|
|
45
|
-
internalPatchesOptions,
|
|
46
32
|
},
|
|
33
|
+
proxyCache: new WeakMap(),
|
|
47
34
|
};
|
|
48
35
|
|
|
49
36
|
// Create proxy
|
|
@@ -53,43 +40,11 @@ export function recordPatches<T extends NonPrimitive>(
|
|
|
53
40
|
mutate(proxy);
|
|
54
41
|
|
|
55
42
|
// Return patches (optionally compressed)
|
|
56
|
-
if (options
|
|
43
|
+
if (options?.compressPatches !== false) {
|
|
57
44
|
return compressPatches(recorderState.patches);
|
|
58
45
|
}
|
|
59
46
|
|
|
60
|
-
return recorderState.patches as Patches
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Mutative-compatible API for easy switching between mutative and patch-recorder.
|
|
66
|
-
* Returns [state, patches] tuple like mutative does.
|
|
67
|
-
*
|
|
68
|
-
* Unlike mutative, this mutates the original object in place (state === originalState).
|
|
69
|
-
* The returned state is the same reference as the input state for API compatibility.
|
|
70
|
-
*
|
|
71
|
-
* @param state - The state to mutate and record patches from
|
|
72
|
-
* @param mutate - A function that receives a draft of the state and applies mutations
|
|
73
|
-
* @param options - Configuration options (enablePatches is forced but ignored - patches are always returned)
|
|
74
|
-
* @returns Tuple [state, patches] where state is the mutated state (same reference as input)
|
|
75
|
-
*
|
|
76
|
-
* @example
|
|
77
|
-
* const state = { user: { name: 'John' } };
|
|
78
|
-
* const [nextState, patches] = create(state, (draft) => {
|
|
79
|
-
* draft.user.name = 'Jane';
|
|
80
|
-
* }, {enabledPatches: true});
|
|
81
|
-
* console.log(nextState === state); // true (mutated in place!)
|
|
82
|
-
* console.log(patches); // [{ op: 'replace', path: ['user', 'name'], value: 'Jane' }]
|
|
83
|
-
*/
|
|
84
|
-
export function create<T extends NonPrimitive>(
|
|
85
|
-
state: T,
|
|
86
|
-
mutate: (state: Draft<T>) => void,
|
|
87
|
-
options: RecordPatchesOptions & {enablePatches: true} = {enablePatches: true},
|
|
88
|
-
): [T, Patches<true>] {
|
|
89
|
-
// Extract enablePatches but ignore it (patches are always returned)
|
|
90
|
-
const {enablePatches, ...recordPatchesOptions} = options;
|
|
91
|
-
const patches = recordPatches(state, mutate, recordPatchesOptions);
|
|
92
|
-
return [state, patches];
|
|
47
|
+
return recorderState.patches as Patches;
|
|
93
48
|
}
|
|
94
49
|
|
|
95
50
|
// Re-export types
|
package/src/maps.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import type {RecorderState} from './types.js';
|
|
1
|
+
import type {PatchPath, RecorderState} from './types.js';
|
|
2
2
|
import {createProxy} from './proxy.js';
|
|
3
|
-
import {Operation} from './types.js';
|
|
4
3
|
import {generateAddPatch, generateDeletePatch, generateReplacePatch} from './patches.js';
|
|
5
|
-
import {cloneIfNeeded
|
|
4
|
+
import {cloneIfNeeded} from './utils.js';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Handle property access on Map objects
|
|
9
8
|
* Wraps mutating methods (set, delete, clear) to generate patches
|
|
10
9
|
*/
|
|
11
|
-
export function handleMapGet
|
|
12
|
-
obj: Map<
|
|
10
|
+
export function handleMapGet(
|
|
11
|
+
obj: Map<any, any>,
|
|
13
12
|
prop: string | symbol,
|
|
14
|
-
path:
|
|
13
|
+
path: PatchPath,
|
|
15
14
|
state: RecorderState<any>,
|
|
16
15
|
): any {
|
|
17
|
-
//
|
|
16
|
+
// Handle symbol properties - return the property value directly
|
|
17
|
+
// Symbol methods like Symbol.iterator should work normally
|
|
18
18
|
if (typeof prop === 'symbol') {
|
|
19
19
|
return (obj as any)[prop];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// Mutating methods
|
|
23
23
|
if (prop === 'set') {
|
|
24
|
-
return (key:
|
|
25
|
-
// Check if key
|
|
26
|
-
const existed =
|
|
24
|
+
return (key: any, value: any) => {
|
|
25
|
+
// Check if key exists BEFORE mutation (current state, not original)
|
|
26
|
+
const existed = obj.has(key);
|
|
27
27
|
const oldValue = obj.get(key);
|
|
28
28
|
const result = obj.set(key, value);
|
|
29
29
|
|
|
@@ -43,7 +43,7 @@ export function handleMapGet<K = any, V = any>(
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
if (prop === 'delete') {
|
|
46
|
-
return (key:
|
|
46
|
+
return (key: any) => {
|
|
47
47
|
const oldValue = obj.get(key);
|
|
48
48
|
const result = obj.delete(key);
|
|
49
49
|
|
|
@@ -71,7 +71,7 @@ export function handleMapGet<K = any, V = any>(
|
|
|
71
71
|
|
|
72
72
|
// Non-mutating methods
|
|
73
73
|
if (prop === 'get') {
|
|
74
|
-
return (key:
|
|
74
|
+
return (key: any) => {
|
|
75
75
|
const value = obj.get(key);
|
|
76
76
|
|
|
77
77
|
// If the value is an object, return a proxy for nested mutation tracking
|
|
@@ -98,21 +98,3 @@ export function handleMapGet<K = any, V = any>(
|
|
|
98
98
|
return (obj as any)[prop];
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
/**
|
|
102
|
-
* Navigate to the original Map at the given path and check if a key exists
|
|
103
|
-
* This is needed to check if a key existed before mutations
|
|
104
|
-
*/
|
|
105
|
-
function keyExistsInOriginal(original: any, path: (string | number)[], key: any): boolean {
|
|
106
|
-
let current = original;
|
|
107
|
-
for (const part of path) {
|
|
108
|
-
if (current == null) return false;
|
|
109
|
-
current = current[part];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// If we reached a Map, check if the key exists
|
|
113
|
-
if (current instanceof Map) {
|
|
114
|
-
return current.has(key);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return false;
|
|
118
|
-
}
|