From ecbb74921deb627c7f77dca72b4a04ba2c5d958d Mon Sep 17 00:00:00 2001 From: Minh Nguyen <2852660+NMinhNguyen@users.noreply.github.com> Date: Sun, 13 Aug 2023 18:52:24 +0100 Subject: [PATCH 1/3] Demonstrate possible regression --- __tests__/base.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/__tests__/base.js b/__tests__/base.js index 1c0059aa..6cfc05c2 100644 --- a/__tests__/base.js +++ b/__tests__/base.js @@ -941,7 +941,9 @@ function runBaseTest(name, autoFreeze, useStrictShallowCopy, useListener) { if (canReferNonEnumerableProperty) s.foo.a++ if (useStrictShallowCopy) expect(isEnumerable(s, "foo")).toBeFalsy() }) - if (canReferNonEnumerableProperty) expect(nextState.foo).toBeTruthy() + if (canReferNonEnumerableProperty) { + expect(nextState.foo).toEqual({a: 2}) + } if (useStrictShallowCopy) expect(isEnumerable(nextState, "foo")).toBeFalsy() if (useStrictShallowCopy) expect(nextState.baz).toBeTruthy() From c72a738b7c2ae36dccb564564b1ed62f6da4dbae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 27 Feb 2024 17:12:19 +0100 Subject: [PATCH 2/3] Make `Patch[]` arrays `readonly` on the outer bounds --- src/core/immerClass.ts | 14 +++++++++----- src/plugins/patches.ts | 2 +- src/types/types-external.ts | 7 +++++-- src/utils/plugins.ts | 2 +- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/core/immerClass.ts b/src/core/immerClass.ts index 6c673e0a..ed04e9f2 100644 --- a/src/core/immerClass.ts +++ b/src/core/immerClass.ts @@ -120,11 +120,15 @@ export class Immer implements ProducersFns { this.produceWithPatches(state, (draft: any) => base(draft, ...args)) } - let patches: Patch[], inversePatches: Patch[] - const result = this.produce(base, recipe, (p: Patch[], ip: Patch[]) => { - patches = p - inversePatches = ip - }) + let patches: readonly Patch[], inversePatches: readonly Patch[] + const result = this.produce( + base, + recipe, + (p: readonly Patch[], ip: readonly Patch[]) => { + patches = p + inversePatches = ip + } + ) return [result, patches!, inversePatches!] } diff --git a/src/plugins/patches.ts b/src/plugins/patches.ts index e82e9996..934e0b96 100644 --- a/src/plugins/patches.ts +++ b/src/plugins/patches.ts @@ -211,7 +211,7 @@ export function enablePatches() { }) } - function applyPatches_(draft: T, patches: Patch[]): T { + function applyPatches_(draft: T, patches: readonly Patch[]): T { patches.forEach(patch => { const {path, op} = patch diff --git a/src/types/types-external.ts b/src/types/types-external.ts index 8bb44aba..717df9b4 100644 --- a/src/types/types-external.ts +++ b/src/types/types-external.ts @@ -68,7 +68,10 @@ export interface Patch { value?: any } -export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void +export type PatchListener = ( + patches: readonly Patch[], + inversePatches: readonly Patch[] +) => void /** Converts `nothing` into `undefined` */ type FromNothing = T extends typeof NOTHING ? undefined : T @@ -81,7 +84,7 @@ export type Produced = Return extends void /** * Utility types */ -type PatchesTuple = readonly [T, Patch[], Patch[]] +type PatchesTuple = readonly [T, readonly Patch[], readonly Patch[]] type ValidRecipeReturnType = | State diff --git a/src/utils/plugins.ts b/src/utils/plugins.ts index 36cc1d70..fede08f5 100644 --- a/src/utils/plugins.ts +++ b/src/utils/plugins.ts @@ -24,7 +24,7 @@ const plugins: { patches: Patch[], inversePatches: Patch[] ): void - applyPatches_(draft: T, patches: Patch[]): T + applyPatches_(draft: T, patches: readonly Patch[]): T } MapSet?: { proxyMap_(target: T, parent?: ImmerState): T From 677becc9079da49021c0de72eb873a7191815209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 9 Mar 2024 12:00:28 +0100 Subject: [PATCH 3/3] Limit the change to `applyPatches` --- __tests__/produce.ts | 17 ++++++++++++++++- src/core/immerClass.ts | 16 ++++++---------- src/types/types-external.ts | 7 ++----- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/__tests__/produce.ts b/__tests__/produce.ts index 22f0a318..f9093688 100644 --- a/__tests__/produce.ts +++ b/__tests__/produce.ts @@ -8,7 +8,8 @@ import { Immutable, Immer, enableMapSet, - enablePatches + enablePatches, + produceWithPatches } from "../src/immer" enableMapSet() @@ -162,6 +163,20 @@ it("can apply patches", () => { expect(applyPatches({}, patches)).toEqual({x: 4}) }) +it("can apply readonly patches", () => { + const [, patches]: readonly [ + { + x: number + }, + readonly Patch[], + readonly Patch[] + ] = produceWithPatches({x: 3}, d => { + d.x++ + }) + + expect(applyPatches({}, patches)).toEqual({x: 4}) +}) + describe("curried producer", () => { it("supports rest parameters", () => { type State = {readonly a: 1} diff --git a/src/core/immerClass.ts b/src/core/immerClass.ts index ed04e9f2..b7ac51b3 100644 --- a/src/core/immerClass.ts +++ b/src/core/immerClass.ts @@ -120,15 +120,11 @@ export class Immer implements ProducersFns { this.produceWithPatches(state, (draft: any) => base(draft, ...args)) } - let patches: readonly Patch[], inversePatches: readonly Patch[] - const result = this.produce( - base, - recipe, - (p: readonly Patch[], ip: readonly Patch[]) => { - patches = p - inversePatches = ip - } - ) + let patches: Patch[], inversePatches: Patch[] + const result = this.produce(base, recipe, (p: Patch[], ip: Patch[]) => { + patches = p + inversePatches = ip + }) return [result, patches!, inversePatches!] } @@ -171,7 +167,7 @@ export class Immer implements ProducersFns { this.useStrictShallowCopy_ = value } - applyPatches(base: T, patches: Patch[]): T { + applyPatches(base: T, patches: readonly Patch[]): T { // If a patch replaces the entire state, take that replacement as base // before applying patches let i: number diff --git a/src/types/types-external.ts b/src/types/types-external.ts index 717df9b4..8bb44aba 100644 --- a/src/types/types-external.ts +++ b/src/types/types-external.ts @@ -68,10 +68,7 @@ export interface Patch { value?: any } -export type PatchListener = ( - patches: readonly Patch[], - inversePatches: readonly Patch[] -) => void +export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void /** Converts `nothing` into `undefined` */ type FromNothing = T extends typeof NOTHING ? undefined : T @@ -84,7 +81,7 @@ export type Produced = Return extends void /** * Utility types */ -type PatchesTuple = readonly [T, readonly Patch[], readonly Patch[]] +type PatchesTuple = readonly [T, Patch[], Patch[]] type ValidRecipeReturnType = | State