Repository: immerjs/immer
Pull request #1112 (2024-03-26 20:34:25)

chore(deps): bump express from 4.18.2 to 4.19.2 in /website
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/expressjs/express/releases">express's releases</a>.</em></p>
<blockquote>
<h2>4.19.2</h2>
<h2>What's Changed</h2>
<ul>
<li><a href="https://github.com/expressjs/express/commit/0b746953c4bd8e377123527db11f9cd866e39f94">Improved fix for open redirect allow list bypass</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/expressjs/express/compare/4.19.1...4.19.2">https://github.com/expressjs/express/compare/4.19.1...4.19.2</a></p>
<h2>4.19.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix ci after location patch by <a href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5552">expressjs/express#5552</a></li>
<li>fixed un-edited version in history.md for 4.19.0 by <a href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5556">expressjs/express#5556</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/expressjs/express/compare/4.19.0...4.19.1">https://github.com/expressjs/express/compare/4.19.0...4.19.1</a></p>
<h2>4.19.0</h2>
<h2>What's Changed</h2>
<ul>
<li>fix typo in release date by <a href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5527">expressjs/express#5527</a></li>
<li>docs: nominating <a href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> to be project captian by <a href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5511">expressjs/express#5511</a></li>
<li>docs: loosen TC activity rules by <a href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5510">expressjs/express#5510</a></li>
<li>Add note on how to update docs for new release by <a href="https://github.com/crandmck"><code>@​crandmck</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5541">expressjs/express#5541</a></li>
<li><a href="https://redirect.github.com/expressjs/express/pull/5551/commits/660ccf5fa33dd0baab069e5c8ddd9ffe7d8bbff1">Prevent open redirect allow list bypass due to encodeurl</a></li>
<li>Release 4.19.0 by <a href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5551">expressjs/express#5551</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/crandmck"><code>@​crandmck</code></a> made their first contribution in <a href="https://redirect.github.com/expressjs/express/pull/5541">expressjs/express#5541</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/expressjs/express/compare/4.18.3...4.19.0">https://github.com/expressjs/express/compare/4.18.3...4.19.0</a></p>
<h2>4.18.3</h2>
<h2>Main Changes</h2>
<ul>
<li>Fix routing requests without method</li>
<li>deps: body-parser@1.20.2
<ul>
<li>Fix strict json error message on Node.js 19+</li>
<li>deps: content-type@~1.0.5</li>
<li>deps: raw-body@2.5.2</li>
</ul>
</li>
</ul>
<h2>Other Changes</h2>
<ul>
<li>Use https: protocol instead of deprecated git: protocol by <a href="https://github.com/vcsjones"><code>@​vcsjones</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5032">expressjs/express#5032</a></li>
<li>build: Node.js@16.18 and Node.js@18.12 by <a href="https://github.com/abenhamdine"><code>@​abenhamdine</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5034">expressjs/express#5034</a></li>
<li>ci: update actions/checkout to v3 by <a href="https://github.com/armujahid"><code>@​armujahid</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5027">expressjs/express#5027</a></li>
<li>test: remove unused function arguments in params by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5124">expressjs/express#5124</a></li>
<li>Remove unused originalIndex from acceptParams by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5119">expressjs/express#5119</a></li>
<li>Fixed typos by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5117">expressjs/express#5117</a></li>
<li>examples: remove unused params by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5113">expressjs/express#5113</a></li>
<li>fix: parameter str is not described in JSDoc by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5130">expressjs/express#5130</a></li>
<li>fix: typos in History.md by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5131">expressjs/express#5131</a></li>
<li>build : add Node.js@19.7 by <a href="https://github.com/abenhamdine"><code>@​abenhamdine</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5028">expressjs/express#5028</a></li>
<li>test: remove unused function arguments in params by <a href="https://github.com/raksbisht"><code>@​raksbisht</code></a> in <a href="https://redirect.github.com/expressjs/express/pull/5137">expressjs/express#5137</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/expressjs/express/blob/master/History.md">express's changelog</a>.</em></p>
<blockquote>
<h1>4.19.2 / 2024-03-25</h1>
<ul>
<li>Improved fix for open redirect allow list bypass</li>
</ul>
<h1>4.19.1 / 2024-03-20</h1>
<ul>
<li>Allow passing non-strings to res.location with new encoding handling checks</li>
</ul>
<h1>4.19.0 / 2024-03-20</h1>
<ul>
<li>Prevent open redirect allow list bypass due to encodeurl</li>
<li>deps: cookie@0.6.0</li>
</ul>
<h1>4.18.3 / 2024-02-29</h1>
<ul>
<li>Fix routing requests without method</li>
<li>deps: body-parser@1.20.2
<ul>
<li>Fix strict json error message on Node.js 19+</li>
<li>deps: content-type@~1.0.5</li>
<li>deps: raw-body@2.5.2</li>
</ul>
</li>
<li>deps: cookie@0.6.0
<ul>
<li>Add <code>partitioned</code> option</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/expressjs/express/commit/04bc62787be974874bc1467b23606c36bc9779ba"><code>04bc627</code></a> 4.19.2</li>
<li><a href="https://github.com/expressjs/express/commit/da4d763ff6ba9df6dbd8f1f0b1d05412dda934d5"><code>da4d763</code></a> Improved fix for open redirect allow list bypass</li>
<li><a href="https://github.com/expressjs/express/commit/4f0f6cc67d531431c096ea006c2191b92931bbc3"><code>4f0f6cc</code></a> 4.19.1</li>
<li><a href="https://github.com/expressjs/express/commit/a003cfab034fbadb1c78ae337ee8ab389adda217"><code>a003cfa</code></a> Allow passing non-strings to res.location with new encoding handling checks f...</li>
<li><a href="https://github.com/expressjs/express/commit/a1fa90fcea7d8e844e1c9938ad095d62669c3abd"><code>a1fa90f</code></a> fixed un-edited version in history.md for 4.19.0</li>
<li><a href="https://github.com/expressjs/express/commit/11f2b1db227fd42c2508c427032c1ec671b306be"><code>11f2b1d</code></a> build: fix build due to inconsistent supertest behavior in older versions</li>
<li><a href="https://github.com/expressjs/express/commit/084e36506a18774f85206a65d8da04dc1107fc1b"><code>084e365</code></a> 4.19.0</li>
<li><a href="https://github.com/expressjs/express/commit/0867302ddbde0e9463d0564fea5861feb708c2dd"><code>0867302</code></a> Prevent open redirect allow list bypass due to encodeurl</li>
<li><a href="https://github.com/expressjs/express/commit/567c9c665d0de4c344b8e160146050770233783c"><code>567c9c6</code></a> Add note on how to update docs for new release (<a href="https://redirect.github.com/expressjs/express/issues/5541">#5541</a>)</li>
<li><a href="https://github.com/expressjs/express/commit/69a4cf2819c4449ec6ea45649691fb43a528d5d1"><code>69a4cf2</code></a> deps: cookie@0.6.0</li>
<li>Additional commits viewable in <a href="https://github.com/expressjs/express/compare/4.18.2...4.19.2">compare view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a href="https://www.npmjs.com/~wesleytodd">wesleytodd</a>, a new releaser for express since your current version.</p>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=express&package-manager=npm_and_yarn&previous-version=4.18.2&new-version=4.19.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/immerjs/immer/network/alerts).

</details>


Patch:
diff --git a/.gitignore b/.gitignore
index 69ff4b3..ca80435 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,3 +63,5 @@ website/build
 website/.docusaurus
 .rts2*
 .gitpod.yml
+
+.watchman-cookie*
diff --git a/src/core/immerClass.ts b/src/core/immerClass.ts
index 6c673e0..763ed62 100644
--- a/src/core/immerClass.ts
+++ b/src/core/immerClass.ts
@@ -31,11 +31,16 @@ interface ProducersFns {
 	produceWithPatches: IProduceWithPatches
 }
 
+export type StrictMode = boolean | "class_only";
+
 export class Immer implements ProducersFns {
 	autoFreeze_: boolean = true
-	useStrictShallowCopy_: boolean = false
+	useStrictShallowCopy_: StrictMode = false
 
-	constructor(config?: {autoFreeze?: boolean; useStrictShallowCopy?: boolean}) {
+	constructor(config?: {
+		autoFreeze?: boolean
+		useStrictShallowCopy?: StrictMode
+	}) {
 		if (typeof config?.autoFreeze === "boolean")
 			this.setAutoFreeze(config!.autoFreeze)
 		if (typeof config?.useStrictShallowCopy === "boolean")
@@ -163,7 +168,7 @@ export class Immer implements ProducersFns {
 	 *
 	 * By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
 	 */
-	setUseStrictShallowCopy(value: boolean) {
+	setUseStrictShallowCopy(value: StrictMode) {
 		this.useStrictShallowCopy_ = value
 	}
 
diff --git a/src/immer.ts b/src/immer.ts
index 687cc2a..0199848 100644
--- a/src/immer.ts
+++ b/src/immer.ts
@@ -8,9 +8,11 @@ import {
 
 export {
 	Draft,
+	WritableDraft,
 	Immutable,
 	Patch,
 	PatchListener,
+	Producer,
 	original,
 	current,
 	isDraft,
@@ -18,7 +20,8 @@ export {
 	NOTHING as nothing,
 	DRAFTABLE as immerable,
 	freeze,
-	Objectish
+	Objectish,
+	StrictMode
 } from "./internal"
 
 const immer = new Immer()
diff --git a/src/utils/common.ts b/src/utils/common.ts
index fb4a768..72a035b 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -8,7 +8,8 @@ import {
 	AnySet,
 	ImmerState,
 	ArchType,
-	die
+	die,
+	StrictMode
 } from "../internal"
 
 export const getPrototypeOf = Object.getPrototypeOf
@@ -140,7 +141,7 @@ export function latest(state: ImmerState): any {
 }
 
 /*#__PURE__*/
-export function shallowCopy(base: any, strict: boolean) {
+export function shallowCopy(base: any, strict: StrictMode) {
 	if (isMap(base)) {
 		return new Map(base)
 	}
@@ -149,36 +150,41 @@ export function shallowCopy(base: any, strict: boolean) {
 	}
 	if (Array.isArray(base)) return Array.prototype.slice.call(base)
 
-	if (!strict && isPlainObject(base)) {
-		if (!getPrototypeOf(base)) {
-			const obj = Object.create(null)
-			return Object.assign(obj, base)
+	const isPlain = isPlainObject(base)
+
+	if (strict === true || (strict === "class_only" && !isPlain)) {
+		// Perform a strict copy
+		const descriptors = Object.getOwnPropertyDescriptors(base)
+		delete descriptors[DRAFT_STATE as any]
+		let keys = Reflect.ownKeys(descriptors)
+		for (let i = 0; i < keys.length; i++) {
+			const key: any = keys[i]
+			const desc = descriptors[key]
+			if (desc.writable === false) {
+				desc.writable = true
+				desc.configurable = true
+			}
+			// like object.assign, we will read any _own_, get/set accessors. This helps in dealing
+			// with libraries that trap values, like mobx or vue
+			// unlike object.assign, non-enumerables will be copied as well
+			if (desc.get || desc.set)
+				descriptors[key] = {
+					configurable: true,
+					writable: true, // could live with !!desc.set as well here...
+					enumerable: desc.enumerable,
+					value: base[key]
+				}
 		}
-		return {...base}
-	}
-
-	const descriptors = Object.getOwnPropertyDescriptors(base)
-	delete descriptors[DRAFT_STATE as any]
-	let keys = Reflect.ownKeys(descriptors)
-	for (let i = 0; i < keys.length; i++) {
-		const key: any = keys[i]
-		const desc = descriptors[key]
-		if (desc.writable === false) {
-			desc.writable = true
-			desc.configurable = true
+		return Object.create(getPrototypeOf(base), descriptors)
+	} else {
+		// perform a sloppy copy
+		const proto = getPrototypeOf(base)
+		if (proto !== null && isPlain) {
+			return {...base} // assumption: better inner class optimization than the assign below
 		}
-		// like object.assign, we will read any _own_, get/set accessors. This helps in dealing
-		// with libraries that trap values, like mobx or vue
-		// unlike object.assign, non-enumerables will be copied as well
-		if (desc.get || desc.set)
-			descriptors[key] = {
-				configurable: true,
-				writable: true, // could live with !!desc.set as well here...
-				enumerable: desc.enumerable,
-				value: base[key]
-			}
+		const obj = Object.create(proto)
+		return Object.assign(obj, base)
 	}
-	return Object.create(getPrototypeOf(base), descriptors)
 }
 
 /**
diff --git a/website/docs/complex-objects.md b/website/docs/complex-objects.md
index 985482c..b98b430 100644
--- a/website/docs/complex-objects.md
+++ b/website/docs/complex-objects.md
@@ -7,8 +7,6 @@ title: Classes
 <div data-ea-publisher="immerjs" data-ea-type="image" className="horizontal bordered"></div>
 </center>
 
-By default, Immer does not strictly handle Plain object's non-eumerable properties such as getters/setters for performance reason. If you want this behavior to be strict, you can opt-in with `useStrictShallowCopy(true)`.
-
 Plain objects (objects without a prototype), arrays, `Map`s and `Set`s are always drafted by Immer. Every other object must use the `immerable` symbol to mark itself as compatible with Immer. When one of these objects is mutated within a producer, its prototype is preserved between copies.
 
 ```js
@@ -61,15 +59,17 @@ console.log(clock2 instanceof Clock) // true
 The semantics on how classes are drafted are as follows:
 
 1. A draft of a class is a fresh object but with the same prototype as the original object.
-1. When creating a draft, Immer will copy all _own_ properties from the base to the draft.This includes non-enumerable and symbolic properties.
+1. When creating a draft, Immer will copy all _own_ properties from the base to the draft.This includes (in strict mode) non-enumerable and symbolic properties.
 1. _Own_ getters will be invoked during the copy process, just like `Object.assign` would.
-1. Inherited getters and methods will remain as is and be inherited by the draft.
+1. Inherited getters and methods will remain as is and be inherited by the draft, as they are stored on the prototype which is untouched.
 1. Immer will not invoke constructor functions.
 1. The final instance will be constructed with the same mechanism as the draft was created.
 1. Only getters that have a setter as well will be writable in the draft, as otherwise the value can't be copied back.
 
 Because Immer will dereference own getters of objects into normal properties, it is possible to use objects that use getter/setter traps on their fields, like MobX and Vue do.
 
+Note that, by default, Immer does not strictly handle object's non-enumerable properties such as getters/setters for performance reason. If you want this behavior to be strict, you can opt-in with `useStrictShallowCopy(config)`. Use `true` to always copy strict, or `"class_only"` to only copy class instances strictly but use the faster loose copying for plain objects. The default is `false`. (Remember, regardless of strict mode, own getters / setters are always copied _by value_. There is currently no config to copy descriptors as-is. Feature request / PR welcome).
+
 Immer does not support exotic / engine native objects such as DOM Nodes or Buffers, nor is subclassing Map, Set or arrays supported and the `immerable` symbol can't be used on them.
 
 So when working for example with `Date` objects, you should always create a new `Date` instance instead of mutating an existing `Date` object.
diff --git a/website/yarn.lock b/website/yarn.lock
index 01519b9..422400b 100644
--- a/website/yarn.lock
+++ b/website/yarn.lock
@@ -2670,13 +2670,13 @@ binary-extensions@^2.0.0:
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
   integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
 
-body-parser@1.20.1:
-  version "1.20.1"
-  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
-  integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
+body-parser@1.20.2:
+  version "1.20.2"
+  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
+  integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
   dependencies:
     bytes "3.1.2"
-    content-type "~1.0.4"
+    content-type "~1.0.5"
     debug "2.6.9"
     depd "2.0.0"
     destroy "1.2.0"
@@ -2684,7 +2684,7 @@ body-parser@1.20.1:
     iconv-lite "0.4.24"
     on-finished "2.4.1"
     qs "6.11.0"
-    raw-body "2.5.1"
+    raw-body "2.5.2"
     type-is "~1.6.18"
     unpipe "1.0.0"
 
@@ -3138,6 +3138,11 @@ content-type@~1.0.4:
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
   integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
 
+content-type@~1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
+  integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
+
 convert-source-map@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
@@ -3150,10 +3155,10 @@ cookie-signature@1.0.6:
   resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
   integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
 
-cookie@0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
-  integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
+cookie@0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
+  integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
 
 copy-text-to-clipboard@^3.0.1:
   version "3.0.1"
@@ -3814,16 +3819,16 @@ execa@^5.0.0:
     strip-final-newline "^2.0.0"
 
 express@^4.17.3:
-  version "4.18.2"
-  resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
-  integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
+  version "4.19.2"
+  resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
+  integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
   dependencies:
     accepts "~1.3.8"
     array-flatten "1.1.1"
-    body-parser "1.20.1"
+    body-parser "1.20.2"
     content-disposition "0.5.4"
     content-type "~1.0.4"
-    cookie "0.5.0"
+    cookie "0.6.0"
     cookie-signature "1.0.6"
     debug "2.6.9"
     depd "2.0.0"
@@ -3989,9 +3994,9 @@ find-up@^5.0.0:
     path-exists "^4.0.0"
 
 follow-redirects@^1.0.0, follow-redirects@^1.14.7:
-  version "1.15.5"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020"
-  integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==
+  version "1.15.6"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
+  integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
 
 fork-ts-checker-webpack-plugin@^6.5.0:
   version "6.5.2"
@@ -6149,10 +6154,10 @@ range-parser@^1.2.1, range-parser@~1.2.1:
   resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
   integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
 
-raw-body@2.5.1:
-  version "2.5.1"
-  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
-  integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
+raw-body@2.5.2:
+  version "2.5.2"
+  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
+  integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
   dependencies:
     bytes "3.1.2"
     http-errors "2.0.0"


Test patch:
diff --git a/__tests__/not-strict-copy.ts b/__tests__/not-strict-copy.ts
index 474d652..9414302 100644
--- a/__tests__/not-strict-copy.ts
+++ b/__tests__/not-strict-copy.ts
@@ -1,37 +1,75 @@
-import {produce, setUseStrictShallowCopy} from "../src/immer"
+import {
+	immerable,
+	produce,
+	setUseStrictShallowCopy,
+	setAutoFreeze,
+	StrictMode
+} from "../src/immer"
 
-describe("setUseStrictShallowCopy(true)", () => {
-	test("keep descriptors", () => {
-		setUseStrictShallowCopy(true)
+describe.each([true, false, "class_only" as const])(
+	"setUseStrictShallowCopy(true)",
+	(strictMode: StrictMode) => {
+		test("keep descriptors, mode: " + strictMode, () => {
+			setUseStrictShallowCopy(strictMode)
 
-		const base: Record<string, unknown> = {}
-		Object.defineProperty(base, "foo", {
-			value: "foo",
-			writable: false,
-			configurable: false
+			const base: Record<string, unknown> = {}
+			Object.defineProperty(base, "foo", {
+				value: "foo",
+				writable: false,
+				configurable: false
+			})
+			const copy = produce(base, (draft: any) => {
+				draft.bar = "bar"
+			})
+			if (strictMode === true) {
+				expect(Object.getOwnPropertyDescriptor(copy, "foo")).toStrictEqual(
+					Object.getOwnPropertyDescriptor(base, "foo")
+				)
+			} else {
+				expect(Object.getOwnPropertyDescriptor(copy, "foo")).toBeUndefined()
+			}
 		})
-		const copy = produce(base, (draft: any) => {
-			draft.bar = "bar"
-		})
-		expect(Object.getOwnPropertyDescriptor(copy, "foo")).toStrictEqual(
-			Object.getOwnPropertyDescriptor(base, "foo")
-		)
-	})
-})
-
-describe("setUseStrictShallowCopy(false)", () => {
-	test("ignore descriptors", () => {
-		setUseStrictShallowCopy(false)
-
-		const base: Record<string, unknown> = {}
-		Object.defineProperty(base, "foo", {
-			value: "foo",
-			writable: false,
-			configurable: false
-		})
-		const copy = produce(base, (draft: any) => {
-			draft.bar = "bar"
+
+		test("keep non-enumerable class descriptors, mode: " + strictMode, () => {
+			setUseStrictShallowCopy(strictMode)
+			setAutoFreeze(false)
+
+			class X {
+				[immerable] = true
+				foo = "foo"
+				bar!: string
+				constructor() {
+					Object.defineProperty(this, "bar", {
+						get() {
+							return this.foo + "bar"
+						},
+						configurable: false,
+						enumerable: false
+					})
+				}
+
+				get baz() {
+					return this.foo + "baz"
+				}
+			}
+
+			const copy = produce(new X(), (draft: any) => {
+				draft.foo = "FOO"
+			})
+
+			const strict = strictMode === true || strictMode === "class_only"
+
+			// descriptors on the prototype are unaffected, so this is still a getter
+			expect(copy.baz).toBe("FOObaz")
+			// descriptors on the instance are found, even when non-enumerable, and read during copy
+			// so new values won't be reflected
+			expect(copy.bar).toBe(strict ? "foobar" : undefined)
+
+			copy.foo = "fluff"
+			// not updated, the own prop became a value
+			expect(copy.bar).toBe(strict ? "foobar" : undefined)
+			// updated, it is still a getter
+			expect(copy.baz).toBe("fluffbaz")
 		})
-		expect(Object.getOwnPropertyDescriptor(copy, "foo")).toBeUndefined()
-	})
-})
+	}
+)