patch-recorder 0.2.2 → 0.3.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 CHANGED
@@ -90,9 +90,9 @@ Records JSON patches from mutations applied to the state.
90
90
  #### Options
91
91
 
92
92
 
93
- - **`arrayLengthAssignment`** (boolean, default: `true`) - When `true`, includes length patches when array shrinks (pop, shift, splice delete). When `false`, omits length patches entirely. Aligned with mutative's behavior.
93
+ - **`arrayLengthAssignment`** (boolean, default: `true`) - When `true`, includes length patches when array shrinks (pop, shift, splice delete). When `false`, omits length patches entirely. Must be `false` when using `getItemId`. Aligned with mutative's behavior.
94
94
  - **`compressPatches`** (boolean, default: `true`) - Compress patches by merging redundant operations
95
- - **`getItemId`** (object, optional) - Configuration for extracting item IDs (see [Item ID Tracking](#item-id-tracking))
95
+ - **`getItemId`** (object, optional) - Configuration for extracting item IDs. **Requires `arrayLengthAssignment: false`**. (see [Item ID Tracking](#item-id-tracking))
96
96
 
97
97
 
98
98
  #### Returns
@@ -227,6 +227,10 @@ console.log(patches);
227
227
 
228
228
  When working with arrays, the patch path only tells you the index, not which item was affected. The `getItemId` option allows you to include item IDs in `remove` and `replace` patches, making it easier to track which items changed.
229
229
 
230
+ **Important:** `getItemId` requires `arrayLengthAssignment: false` because length patches (e.g., `{ op: 'replace', path: ['arr', 'length'], value: 2 }`) cannot include individual item IDs. This is enforced at both compile time and runtime.
231
+
232
+ **Note:** When `arrayLengthAssignment: false`, direct array length assignment (`arr.length = N`) generates individual remove/add patches instead of a length patch, allowing proper item ID tracking.
233
+
230
234
  ```typescript
231
235
  const state = {
232
236
  users: [
@@ -239,6 +243,7 @@ const state = {
239
243
  const patches = recordPatches(state, (state) => {
240
244
  state.users.splice(1, 1); // Remove Bob
241
245
  }, {
246
+ arrayLengthAssignment: false, // Required when using getItemId
242
247
  getItemId: {
243
248
  users: (user) => user.id // Extract ID from each user
244
249
  }
@@ -255,6 +260,7 @@ The `getItemId` option is an object that mirrors your data structure:
255
260
 
256
261
  ```typescript
257
262
  recordPatches(state, mutate, {
263
+ arrayLengthAssignment: false, // Required when using getItemId
258
264
  getItemId: {
259
265
  // Top-level arrays
260
266
  items: (item) => item.id,
@@ -288,6 +294,7 @@ const state = {
288
294
  const patches = recordPatches(state, (state) => {
289
295
  state.entityMap.delete('key1');
290
296
  }, {
297
+ arrayLengthAssignment: false, // Required when using getItemId
291
298
  getItemId: {
292
299
  entityMap: (entity) => entity.internalId
293
300
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,YAAY,EAAE,oBAAoB,EAAE,OAAO,EAAC,MAAM,YAAY,CAAC;AAE5E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAC5B,CAAC,SAAS,YAAY,EACtB,aAAa,SAAS,oBAAoB,GAAG,EAAE,EAC9C,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAuBxE;AAGD,YAAY,EAAC,YAAY,EAAE,oBAAoB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAC,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,YAAY,EAAE,oBAAoB,EAAE,OAAO,EAAC,MAAM,YAAY,CAAC;AAE5E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAC5B,CAAC,SAAS,YAAY,EACtB,aAAa,SAAS,oBAAoB,GAAG,EAAE,EAC9C,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CA6BxE;AAGD,YAAY,EAAC,YAAY,EAAE,oBAAoB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAC,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -18,13 +18,16 @@ import { compressPatches } from './optimizer.js';
18
18
  * console.log(patches); // [{ op: 'replace', path: ['user', 'name'], value: 'Jane' }]
19
19
  */
20
20
  export function recordPatches(state, mutate, options) {
21
+ // Runtime validation: getItemId requires arrayLengthAssignment: false
22
+ if (options?.getItemId && options?.arrayLengthAssignment !== false) {
23
+ throw new Error('getItemId requires arrayLengthAssignment: false. ' +
24
+ 'Length patches cannot include individual item IDs.');
25
+ }
21
26
  const recorderState = {
22
27
  state,
23
28
  patches: [],
24
29
  basePath: [],
25
- options: {
26
- ...options,
27
- },
30
+ options: options ?? {},
28
31
  proxyCache: new WeakMap(),
29
32
  };
30
33
  // Create proxy
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,YAAY,CAAC;AACvC,OAAO,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAG/C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,aAAa,CAG3B,KAAQ,EAAE,MAA0B,EAAE,OAAuB;IAC9D,MAAM,aAAa,GAAG;QACrB,KAAK;QACL,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE;YACR,GAAG,OAAO;SACV;QACD,UAAU,EAAE,IAAI,OAAO,EAAE;KACzB,CAAC;IAEF,eAAe;IACf,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,EAAE,EAAE,aAAa,CAAM,CAAC;IAEzD,kBAAkB;IAClB,MAAM,CAAC,KAAK,CAAC,CAAC;IAEd,yCAAyC;IACzC,IAAI,OAAO,EAAE,eAAe,KAAK,KAAK,EAAE,CAAC;QACxC,OAAO,eAAe,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,aAAa,CAAC,OAAkB,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,YAAY,CAAC;AACvC,OAAO,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAG/C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,aAAa,CAG3B,KAAQ,EAAE,MAA0B,EAAE,OAAuB;IAC9D,sEAAsE;IACtE,IAAI,OAAO,EAAE,SAAS,IAAI,OAAO,EAAE,qBAAqB,KAAK,KAAK,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CACd,mDAAmD;YAClD,oDAAoD,CACrD,CAAC;IACH,CAAC;IAED,MAAM,aAAa,GAAG;QACrB,KAAK;QACL,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,OAAO,IAAI,EAAE;QACtB,UAAU,EAAE,IAAI,OAAO,EAAE;KACzB,CAAC;IAEF,eAAe;IACf,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,EAAE,EAAE,aAAa,CAAM,CAAC;IAEzD,kBAAkB;IAClB,MAAM,CAAC,KAAK,CAAC,CAAC;IAEd,yCAAyC;IACzC,IAAI,OAAO,EAAE,eAAe,KAAK,KAAK,EAAE,CAAC;QACxC,OAAO,eAAe,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,aAAa,CAAC,OAAkB,CAAC;AACzC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAE,aAAa,EAAC,MAAM,YAAY,CAAC;AAOzD,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAC3C,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,aAAa,CAAC,GAAG,CAAC,GACvB,CAAC,CA2IH"}
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAE,aAAa,EAAC,MAAM,YAAY,CAAC;AAYzD,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAC3C,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,aAAa,CAAC,GAAG,CAAC,GACvB,CAAC,CA8KH"}
package/dist/proxy.js CHANGED
@@ -1,4 +1,4 @@
1
- import { generateSetPatch, generateDeletePatch, generateAddPatch, generateReplacePatch } from './patches.js';
1
+ import { generateSetPatch, generateDeletePatch, generateAddPatch, generateReplacePatch, } from './patches.js';
2
2
  import { isArray, isMap, isSet } from './utils.js';
3
3
  import { handleArrayGet } from './arrays.js';
4
4
  import { handleMapGet } from './maps.js';
@@ -58,6 +58,20 @@ export function createProxy(target, path, state) {
58
58
  if (Object.is(oldValue, value) && (value !== undefined || actuallyHasProperty)) {
59
59
  return true;
60
60
  }
61
+ // Special handling for array length with arrayLengthAssignment: false
62
+ // Must capture removed items BEFORE mutation
63
+ let removedItems = null;
64
+ if (isArrayType && prop === 'length' && state.options.arrayLengthAssignment === false) {
65
+ const arr = obj;
66
+ const newLength = value;
67
+ if (newLength < oldValue) {
68
+ // Capture items that will be removed before mutation
69
+ removedItems = [];
70
+ for (let i = newLength; i < oldValue; i++) {
71
+ removedItems.push(arr[i]);
72
+ }
73
+ }
74
+ }
61
75
  // Mutate original immediately
62
76
  obj[prop] = value;
63
77
  // Generate patch - use pre-mutation property existence check
@@ -65,8 +79,29 @@ export function createProxy(target, path, state) {
65
79
  generateAddPatch(state, propPath, value);
66
80
  }
67
81
  else if (isArrayType && prop === 'length') {
68
- // Use generateReplacePatch for array length to include oldValue
69
- generateReplacePatch(state, propPath, value, oldValue);
82
+ if (state.options.arrayLengthAssignment === false) {
83
+ // When arrayLengthAssignment is false, generate individual remove patches
84
+ // for each removed item (in reverse order)
85
+ const newLength = value;
86
+ if (removedItems) {
87
+ // Array was shrinking - generate remove patches for removed items
88
+ // Iterate in reverse to generate patches from end to start
89
+ for (let i = removedItems.length - 1; i >= 0; i--) {
90
+ const index = newLength + i;
91
+ generateDeletePatch(state, [...path, index], removedItems[i]);
92
+ }
93
+ }
94
+ else if (newLength > oldValue) {
95
+ // Array is growing - generate add patches for new undefined slots
96
+ for (let i = oldValue; i < newLength; i++) {
97
+ generateAddPatch(state, [...path, i], undefined);
98
+ }
99
+ }
100
+ }
101
+ else {
102
+ // Use generateReplacePatch for array length to include oldValue
103
+ generateReplacePatch(state, propPath, value, oldValue);
104
+ }
70
105
  }
71
106
  else {
72
107
  generateSetPatch(state, propPath, oldValue, value);
package/dist/proxy.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -32,15 +32,25 @@ export type Patch = {
32
32
  };
33
33
  export type Patches = Patch[];
34
34
  export type NonPrimitive = object | Array<unknown>;
35
- export interface RecordPatchesOptions {
36
- /**
37
- * Include array length in patches (default: true)
38
- */
39
- arrayLengthAssignment?: boolean;
35
+ /**
36
+ * Base options shared by all configurations
37
+ */
38
+ interface BaseRecordPatchesOptions {
40
39
  /**
41
40
  * Compress patches by merging redundant operations (default: true)
42
41
  */
43
42
  compressPatches?: boolean;
43
+ }
44
+ /**
45
+ * Options when using getItemId - requires arrayLengthAssignment to be false
46
+ * because length patches cannot include individual item IDs.
47
+ */
48
+ interface RecordPatchesOptionsWithItemId extends BaseRecordPatchesOptions {
49
+ /**
50
+ * Must be false when using getItemId.
51
+ * Length patches cannot include individual item IDs.
52
+ */
53
+ arrayLengthAssignment: false;
44
54
  /**
45
55
  * Configuration for extracting item IDs for remove/replace patches.
46
56
  * Maps paths to functions that extract IDs from item values.
@@ -48,6 +58,7 @@ export interface RecordPatchesOptions {
48
58
  * @example
49
59
  * ```typescript
50
60
  * recordPatches(state, mutate, {
61
+ * arrayLengthAssignment: false,
51
62
  * getItemId: {
52
63
  * items: (item) => item.id,
53
64
  * users: (user) => user.userId,
@@ -58,8 +69,29 @@ export interface RecordPatchesOptions {
58
69
  * });
59
70
  * ```
60
71
  */
61
- getItemId?: GetItemIdConfig;
72
+ getItemId: GetItemIdConfig;
62
73
  }
74
+ /**
75
+ * Options when not using getItemId - arrayLengthAssignment can be any value
76
+ */
77
+ interface RecordPatchesOptionsWithoutItemId extends BaseRecordPatchesOptions {
78
+ /**
79
+ * Include array length in patches (default: true)
80
+ */
81
+ arrayLengthAssignment?: boolean;
82
+ /**
83
+ * Not available unless arrayLengthAssignment is false
84
+ */
85
+ getItemId?: undefined;
86
+ }
87
+ /**
88
+ * Configuration options for recordPatches.
89
+ *
90
+ * Note: getItemId requires arrayLengthAssignment: false because length patches
91
+ * (e.g., { op: 'replace', path: ['arr', 'length'], value: 2, oldValue: 3 })
92
+ * cannot include the IDs of individual items that were removed.
93
+ */
94
+ export type RecordPatchesOptions = RecordPatchesOptionsWithItemId | RecordPatchesOptionsWithoutItemId;
63
95
  export interface RecorderState<T extends NonPrimitive> {
64
96
  state: T;
65
97
  patches: Patches;
@@ -70,4 +102,5 @@ export interface RecorderState<T extends NonPrimitive> {
70
102
  */
71
103
  proxyCache: WeakMap<object, any>;
72
104
  }
105
+ export {};
73
106
  //# sourceMappingURL=types.d.ts.map
@@ -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;;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,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;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,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"}
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,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;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;;GAEG;AACH,UAAU,wBAAwB;IACjC;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;GAGG;AACH,UAAU,8BAA+B,SAAQ,wBAAwB;IACxE;;;OAGG;IACH,qBAAqB,EAAE,KAAK,CAAC;IAC7B;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,EAAE,eAAe,CAAC;CAC3B;AAED;;GAEG;AACH,UAAU,iCAAkC,SAAQ,wBAAwB;IAC3E;;OAEG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAC7B,8BAA8B,GAC9B,iCAAiC,CAAC;AAErC,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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patch-recorder",
3
- "version": "0.2.2",
3
+ "version": "0.3.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",
package/src/index.ts CHANGED
@@ -23,13 +23,19 @@ export function recordPatches<
23
23
  T extends NonPrimitive,
24
24
  PatchesOption extends RecordPatchesOptions = {},
25
25
  >(state: T, mutate: (state: T) => void, options?: PatchesOption): Patches {
26
+ // Runtime validation: getItemId requires arrayLengthAssignment: false
27
+ if (options?.getItemId && options?.arrayLengthAssignment !== false) {
28
+ throw new Error(
29
+ 'getItemId requires arrayLengthAssignment: false. ' +
30
+ 'Length patches cannot include individual item IDs.',
31
+ );
32
+ }
33
+
26
34
  const recorderState = {
27
35
  state,
28
36
  patches: [],
29
37
  basePath: [],
30
- options: {
31
- ...options,
32
- },
38
+ options: options ?? {},
33
39
  proxyCache: new WeakMap(),
34
40
  };
35
41
 
package/src/proxy.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import type {PatchPath, RecorderState} from './types.js';
2
- import {generateSetPatch, generateDeletePatch, generateAddPatch, generateReplacePatch} from './patches.js';
2
+ import {
3
+ generateSetPatch,
4
+ generateDeletePatch,
5
+ generateAddPatch,
6
+ generateReplacePatch,
7
+ } from './patches.js';
3
8
  import {isArray, isMap, isSet} from './utils.js';
4
9
  import {handleArrayGet} from './arrays.js';
5
10
  import {handleMapGet} from './maps.js';
@@ -78,6 +83,21 @@ export function createProxy<T extends object>(
78
83
  return true;
79
84
  }
80
85
 
86
+ // Special handling for array length with arrayLengthAssignment: false
87
+ // Must capture removed items BEFORE mutation
88
+ let removedItems: any[] | null = null;
89
+ if (isArrayType && prop === 'length' && state.options.arrayLengthAssignment === false) {
90
+ const arr = obj as any[];
91
+ const newLength = value as number;
92
+ if (newLength < oldValue) {
93
+ // Capture items that will be removed before mutation
94
+ removedItems = [];
95
+ for (let i = newLength; i < oldValue; i++) {
96
+ removedItems.push(arr[i]);
97
+ }
98
+ }
99
+ }
100
+
81
101
  // Mutate original immediately
82
102
  (obj as any)[prop] = value;
83
103
 
@@ -85,8 +105,28 @@ export function createProxy<T extends object>(
85
105
  if (!hadProperty) {
86
106
  generateAddPatch(state, propPath, value);
87
107
  } else if (isArrayType && prop === 'length') {
88
- // Use generateReplacePatch for array length to include oldValue
89
- generateReplacePatch(state, propPath, value, oldValue);
108
+ if (state.options.arrayLengthAssignment === false) {
109
+ // When arrayLengthAssignment is false, generate individual remove patches
110
+ // for each removed item (in reverse order)
111
+ const newLength = value as number;
112
+
113
+ if (removedItems) {
114
+ // Array was shrinking - generate remove patches for removed items
115
+ // Iterate in reverse to generate patches from end to start
116
+ for (let i = removedItems.length - 1; i >= 0; i--) {
117
+ const index = newLength + i;
118
+ generateDeletePatch(state, [...path, index], removedItems[i]);
119
+ }
120
+ } else if (newLength > oldValue) {
121
+ // Array is growing - generate add patches for new undefined slots
122
+ for (let i = oldValue; i < newLength; i++) {
123
+ generateAddPatch(state, [...path, i], undefined);
124
+ }
125
+ }
126
+ } else {
127
+ // Use generateReplacePatch for array length to include oldValue
128
+ generateReplacePatch(state, propPath, value, oldValue);
129
+ }
90
130
  } else {
91
131
  generateSetPatch(state, propPath, oldValue, value);
92
132
  }
package/src/types.ts CHANGED
@@ -40,15 +40,26 @@ export type Patches = Patch[];
40
40
 
41
41
  export type NonPrimitive = object | Array<unknown>;
42
42
 
43
- export interface RecordPatchesOptions {
44
- /**
45
- * Include array length in patches (default: true)
46
- */
47
- arrayLengthAssignment?: boolean;
43
+ /**
44
+ * Base options shared by all configurations
45
+ */
46
+ interface BaseRecordPatchesOptions {
48
47
  /**
49
48
  * Compress patches by merging redundant operations (default: true)
50
49
  */
51
50
  compressPatches?: boolean;
51
+ }
52
+
53
+ /**
54
+ * Options when using getItemId - requires arrayLengthAssignment to be false
55
+ * because length patches cannot include individual item IDs.
56
+ */
57
+ interface RecordPatchesOptionsWithItemId extends BaseRecordPatchesOptions {
58
+ /**
59
+ * Must be false when using getItemId.
60
+ * Length patches cannot include individual item IDs.
61
+ */
62
+ arrayLengthAssignment: false;
52
63
  /**
53
64
  * Configuration for extracting item IDs for remove/replace patches.
54
65
  * Maps paths to functions that extract IDs from item values.
@@ -56,6 +67,7 @@ export interface RecordPatchesOptions {
56
67
  * @example
57
68
  * ```typescript
58
69
  * recordPatches(state, mutate, {
70
+ * arrayLengthAssignment: false,
59
71
  * getItemId: {
60
72
  * items: (item) => item.id,
61
73
  * users: (user) => user.userId,
@@ -66,9 +78,34 @@ export interface RecordPatchesOptions {
66
78
  * });
67
79
  * ```
68
80
  */
69
- getItemId?: GetItemIdConfig;
81
+ getItemId: GetItemIdConfig;
70
82
  }
71
83
 
84
+ /**
85
+ * Options when not using getItemId - arrayLengthAssignment can be any value
86
+ */
87
+ interface RecordPatchesOptionsWithoutItemId extends BaseRecordPatchesOptions {
88
+ /**
89
+ * Include array length in patches (default: true)
90
+ */
91
+ arrayLengthAssignment?: boolean;
92
+ /**
93
+ * Not available unless arrayLengthAssignment is false
94
+ */
95
+ getItemId?: undefined;
96
+ }
97
+
98
+ /**
99
+ * Configuration options for recordPatches.
100
+ *
101
+ * Note: getItemId requires arrayLengthAssignment: false because length patches
102
+ * (e.g., { op: 'replace', path: ['arr', 'length'], value: 2, oldValue: 3 })
103
+ * cannot include the IDs of individual items that were removed.
104
+ */
105
+ export type RecordPatchesOptions =
106
+ | RecordPatchesOptionsWithItemId
107
+ | RecordPatchesOptionsWithoutItemId;
108
+
72
109
  export interface RecorderState<T extends NonPrimitive> {
73
110
  state: T;
74
111
  patches: Patches;