Skip to content

Commit 7836fb0

Browse files
committed
feature: Add strict mode for accessing non-draftables
In strict mode, accessing a non-draftable property will throw One can use the unsafe function to perform such operations Fixes immerjs#686
1 parent 7faa7b4 commit 7836fb0

File tree

6 files changed

+68
-4
lines changed

6 files changed

+68
-4
lines changed

src/core/immerClass.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,19 @@ export class Immer implements ProducersFns {
3737

3838
autoFreeze_: boolean = true
3939

40-
constructor(config?: {useProxies?: boolean; autoFreeze?: boolean}) {
40+
strictModeEnabled_: boolean = false
41+
42+
constructor(config?: {
43+
useProxies?: boolean
44+
autoFreeze?: boolean
45+
strictMode?: boolean
46+
}) {
4147
if (typeof config?.useProxies === "boolean")
4248
this.setUseProxies(config!.useProxies)
4349
if (typeof config?.autoFreeze === "boolean")
4450
this.setAutoFreeze(config!.autoFreeze)
51+
if (typeof config?.strictMode === "boolean")
52+
this.setStrictMode(config!.strictMode)
4553
this.produce = this.produce.bind(this)
4654
this.produceWithPatches = this.produceWithPatches.bind(this)
4755
}
@@ -183,6 +191,23 @@ export class Immer implements ProducersFns {
183191
this.useProxies_ = value
184192
}
185193

194+
/**
195+
* Pass true to throw errors when attempting to access a non-draftable reference.
196+
*
197+
* By default, strict mode is disabled.
198+
*/
199+
setStrictMode(value: boolean) {
200+
this.strictModeEnabled_ = value
201+
}
202+
203+
unsafe(callback: () => void) {
204+
const scope = getCurrentScope()
205+
206+
scope.unsafeNonDraftabledAllowed_ = true
207+
callback()
208+
scope.unsafeNonDraftabledAllowed_ = false
209+
}
210+
186211
applyPatches(base: Objectish, patches: Patch[]) {
187212
// If a patch replaces the entire state, take that replacement as base
188213
// before applying patches

src/core/proxy.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,19 @@ export const objectTraps: ProxyHandler<ProxyState> = {
108108
return readPropFromProto(state, source, prop)
109109
}
110110
const value = source[prop]
111-
if (state.finalized_ || !isDraftable(value)) {
111+
if (state.finalized_) {
112+
return value
113+
}
114+
if (!isDraftable(value)) {
115+
if (
116+
state.scope_.immer_.strictModeEnabled_ &&
117+
!state.scope_.unsafeNonDraftabledAllowed_ &&
118+
typeof value === "object" &&
119+
value !== null
120+
) {
121+
die(24)
122+
}
123+
112124
return value
113125
}
114126
// Check for existing draft in modified state.

src/core/scope.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface ImmerScope {
2222
patchListener_?: PatchListener
2323
immer_: Immer
2424
unfinalizedDrafts_: number
25+
unsafeNonDraftabledAllowed_: boolean
2526
}
2627

2728
let currentScope: ImmerScope | undefined
@@ -42,7 +43,8 @@ function createScope(
4243
// Whenever the modified draft contains a draft from another scope, we
4344
// need to prevent auto-freezing so the unowned draft can be finalized.
4445
canAutoFreeze_: true,
45-
unfinalizedDrafts_: 0
46+
unfinalizedDrafts_: 0,
47+
unsafeNonDraftabledAllowed_: false
4648
}
4749
}
4850

src/immer.ts

+12
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@ export const setAutoFreeze = immer.setAutoFreeze.bind(immer)
6767
*/
6868
export const setUseProxies = immer.setUseProxies.bind(immer)
6969

70+
/**
71+
* Pass true to throw errors when attempting to access a non-draftable reference.
72+
*
73+
* By default, strict mode is disabled.
74+
*/
75+
export const setStrictMode = immer.setStrictMode.bind(immer)
76+
77+
/**
78+
* Allow accessing non-draftable references in strict mode inside the callback.
79+
*/
80+
export const unsafe = immer.unsafe.bind(immer)
81+
7082
/**
7183
* Apply an array of Immer patches to the first argument.
7284
*

src/types/index.js.flow

+12
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,18 @@ declare export function setAutoFreeze(autoFreeze: boolean): void
8484
*/
8585
declare export function setUseProxies(useProxies: boolean): void
8686

87+
/**
88+
* Pass true to throw errors when attempting to access a non-draftable reference.
89+
*
90+
* By default, strict mode is disabled.
91+
*/
92+
declare export function setStrictMode(strictMode: boolean): void
93+
94+
/**
95+
* Allow accessing non-draftable references in strict mode inside the callback.
96+
*/
97+
declare export function unsafe(callback: () => void): void
98+
8799
declare export function applyPatches<S>(state: S, patches: Patch[]): S
88100

89101
declare export function original<S>(value: S): S

src/utils/errors.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ const errors = {
3838
},
3939
23(thing: string) {
4040
return `'original' expects a draft, got: ${thing}`
41-
}
41+
},
42+
24: "Cannot get a non-draftable reference in strict mode. Use the `unsafe` function, add the `immerable` symbol, or disable strict mode"
4243
} as const
4344

4445
export function die(error: keyof typeof errors, ...args: any[]): never {

0 commit comments

Comments
 (0)