Skip to content

Commit

Permalink
feature: Add strict mode for accessing non-draftables
Browse files Browse the repository at this point in the history
In strict mode, accessing a non-draftable property will throw
One can use the unsafe function to perform such operations

Fixes immerjs#686
  • Loading branch information
Gelio committed Dec 3, 2020
1 parent 7faa7b4 commit 229b717
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 4 deletions.
27 changes: 26 additions & 1 deletion src/core/immerClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,19 @@ export class Immer implements ProducersFns {

autoFreeze_: boolean = true

constructor(config?: {useProxies?: boolean; autoFreeze?: boolean}) {
strictModeEnabled_: boolean = false

constructor(config?: {
useProxies?: boolean
autoFreeze?: boolean
strictMode?: boolean
}) {
if (typeof config?.useProxies === "boolean")
this.setUseProxies(config!.useProxies)
if (typeof config?.autoFreeze === "boolean")
this.setAutoFreeze(config!.autoFreeze)
if (typeof config?.strictMode === "boolean")
this.setStrictMode(config!.strictMode)
this.produce = this.produce.bind(this)
this.produceWithPatches = this.produceWithPatches.bind(this)
}
Expand Down Expand Up @@ -183,6 +191,23 @@ export class Immer implements ProducersFns {
this.useProxies_ = value
}

/**
* Pass true to throw errors when attempting to access a non-draftable reference.
*
* By default, strict mode is disabled.
*/
setStrictMode(value: boolean) {
this.strictModeEnabled_ = value
}

unsafe(callback: () => void) {
const scope = getCurrentScope()

scope.unsafeNonDraftabledAllowed_ = true
callback()
scope.unsafeNonDraftabledAllowed_ = false
}

applyPatches(base: Objectish, patches: Patch[]) {
// If a patch replaces the entire state, take that replacement as base
// before applying patches
Expand Down
12 changes: 11 additions & 1 deletion src/core/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,17 @@ export const objectTraps: ProxyHandler<ProxyState> = {
return readPropFromProto(state, source, prop)
}
const value = source[prop]
if (state.finalized_ || !isDraftable(value)) {
if (state.finalized_) {
return value
}
if (!isDraftable(value)) {
if (
state.scope_.immer_.strictModeEnabled_ &&
!state.scope_.unsafeNonDraftabledAllowed_
) {
die(24)
}

return value
}
// Check for existing draft in modified state.
Expand Down
4 changes: 3 additions & 1 deletion src/core/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface ImmerScope {
patchListener_?: PatchListener
immer_: Immer
unfinalizedDrafts_: number
unsafeNonDraftabledAllowed_: boolean
}

let currentScope: ImmerScope | undefined
Expand All @@ -42,7 +43,8 @@ function createScope(
// Whenever the modified draft contains a draft from another scope, we
// need to prevent auto-freezing so the unowned draft can be finalized.
canAutoFreeze_: true,
unfinalizedDrafts_: 0
unfinalizedDrafts_: 0,
unsafeNonDraftabledAllowed_: false
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/immer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ export const setAutoFreeze = immer.setAutoFreeze.bind(immer)
*/
export const setUseProxies = immer.setUseProxies.bind(immer)

/**
* Pass true to throw errors when attempting to access a non-draftable reference.
*
* By default, strict mode is disabled.
*/
export const setStrictMode = immer.setStrictMode.bind(immer)

/**
* Allow accessing non-draftable references in strict mode inside the callback.
*/
export const unsafe = immer.unsafe.bind(immer)

/**
* Apply an array of Immer patches to the first argument.
*
Expand Down
3 changes: 2 additions & 1 deletion src/utils/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ const errors = {
},
23(thing: string) {
return `'original' expects a draft, got: ${thing}`
}
},
24: "Cannot get a non-draftable reference in strict mode. Use the `unsafe` function, add the `immerable` symbol, or disable strict mode"
} as const

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

0 comments on commit 229b717

Please sign in to comment.