This commit is contained in:
60
node_modules/immer/src/core/current.ts
generated
vendored
Normal file
60
node_modules/immer/src/core/current.ts
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
die,
|
||||
isDraft,
|
||||
shallowCopy,
|
||||
each,
|
||||
DRAFT_STATE,
|
||||
get,
|
||||
set,
|
||||
ImmerState,
|
||||
isDraftable,
|
||||
Archtype,
|
||||
getArchtype,
|
||||
getPlugin
|
||||
} from "../internal"
|
||||
|
||||
/** Takes a snapshot of the current state of a draft and finalizes it (but without freezing). This is a great utility to print the current state during debugging (no Proxies in the way). The output of current can also be safely leaked outside the producer. */
|
||||
export function current<T>(value: T): T
|
||||
export function current(value: any): any {
|
||||
if (!isDraft(value)) die(22, value)
|
||||
return currentImpl(value)
|
||||
}
|
||||
|
||||
function currentImpl(value: any): any {
|
||||
if (!isDraftable(value)) return value
|
||||
const state: ImmerState | undefined = value[DRAFT_STATE]
|
||||
let copy: any
|
||||
const archType = getArchtype(value)
|
||||
if (state) {
|
||||
if (
|
||||
!state.modified_ &&
|
||||
(state.type_ < 4 || !getPlugin("ES5").hasChanges_(state as any))
|
||||
)
|
||||
return state.base_
|
||||
// Optimization: avoid generating new drafts during copying
|
||||
state.finalized_ = true
|
||||
copy = copyHelper(value, archType)
|
||||
state.finalized_ = false
|
||||
} else {
|
||||
copy = copyHelper(value, archType)
|
||||
}
|
||||
|
||||
each(copy, (key, childValue) => {
|
||||
if (state && get(state.base_, key) === childValue) return // no need to copy or search in something that didn't change
|
||||
set(copy, key, currentImpl(childValue))
|
||||
})
|
||||
// In the future, we might consider freezing here, based on the current settings
|
||||
return archType === Archtype.Set ? new Set(copy) : copy
|
||||
}
|
||||
|
||||
function copyHelper(value: any, archType: number): any {
|
||||
// creates a shallow copy, even if it is a map or set
|
||||
switch (archType) {
|
||||
case Archtype.Map:
|
||||
return new Map(value)
|
||||
case Archtype.Set:
|
||||
// Set will be cloned as array temporarily, so that we can replace individual items
|
||||
return Array.from(value)
|
||||
}
|
||||
return shallowCopy(value)
|
||||
}
|
||||
168
node_modules/immer/src/core/finalize.ts
generated
vendored
Normal file
168
node_modules/immer/src/core/finalize.ts
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
import {
|
||||
ImmerScope,
|
||||
DRAFT_STATE,
|
||||
isDraftable,
|
||||
NOTHING,
|
||||
PatchPath,
|
||||
each,
|
||||
has,
|
||||
freeze,
|
||||
ImmerState,
|
||||
isDraft,
|
||||
SetState,
|
||||
set,
|
||||
ProxyType,
|
||||
getPlugin,
|
||||
die,
|
||||
revokeScope,
|
||||
isFrozen,
|
||||
shallowCopy
|
||||
} from "../internal"
|
||||
|
||||
export function processResult(result: any, scope: ImmerScope) {
|
||||
scope.unfinalizedDrafts_ = scope.drafts_.length
|
||||
const baseDraft = scope.drafts_![0]
|
||||
const isReplaced = result !== undefined && result !== baseDraft
|
||||
if (!scope.immer_.useProxies_)
|
||||
getPlugin("ES5").willFinalizeES5_(scope, result, isReplaced)
|
||||
if (isReplaced) {
|
||||
if (baseDraft[DRAFT_STATE].modified_) {
|
||||
revokeScope(scope)
|
||||
die(4)
|
||||
}
|
||||
if (isDraftable(result)) {
|
||||
// Finalize the result in case it contains (or is) a subset of the draft.
|
||||
result = finalize(scope, result)
|
||||
if (!scope.parent_) maybeFreeze(scope, result)
|
||||
}
|
||||
if (scope.patches_) {
|
||||
getPlugin("Patches").generateReplacementPatches_(
|
||||
baseDraft[DRAFT_STATE].base_,
|
||||
result,
|
||||
scope.patches_,
|
||||
scope.inversePatches_!
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Finalize the base draft.
|
||||
result = finalize(scope, baseDraft, [])
|
||||
}
|
||||
revokeScope(scope)
|
||||
if (scope.patches_) {
|
||||
scope.patchListener_!(scope.patches_, scope.inversePatches_!)
|
||||
}
|
||||
return result !== NOTHING ? result : undefined
|
||||
}
|
||||
|
||||
function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) {
|
||||
// Don't recurse in tho recursive data structures
|
||||
if (isFrozen(value)) return value
|
||||
|
||||
const state: ImmerState = value[DRAFT_STATE]
|
||||
// A plain object, might need freezing, might contain drafts
|
||||
if (!state) {
|
||||
each(
|
||||
value,
|
||||
(key, childValue) =>
|
||||
finalizeProperty(rootScope, state, value, key, childValue, path),
|
||||
true // See #590, don't recurse into non-enumerable of non drafted objects
|
||||
)
|
||||
return value
|
||||
}
|
||||
// Never finalize drafts owned by another scope.
|
||||
if (state.scope_ !== rootScope) return value
|
||||
// Unmodified draft, return the (frozen) original
|
||||
if (!state.modified_) {
|
||||
maybeFreeze(rootScope, state.base_, true)
|
||||
return state.base_
|
||||
}
|
||||
// Not finalized yet, let's do that now
|
||||
if (!state.finalized_) {
|
||||
state.finalized_ = true
|
||||
state.scope_.unfinalizedDrafts_--
|
||||
const result =
|
||||
// For ES5, create a good copy from the draft first, with added keys and without deleted keys.
|
||||
state.type_ === ProxyType.ES5Object || state.type_ === ProxyType.ES5Array
|
||||
? (state.copy_ = shallowCopy(state.draft_))
|
||||
: state.copy_
|
||||
// Finalize all children of the copy
|
||||
// For sets we clone before iterating, otherwise we can get in endless loop due to modifying during iteration, see #628
|
||||
// To preserve insertion order in all cases we then clear the set
|
||||
// And we let finalizeProperty know it needs to re-add non-draft children back to the target
|
||||
let resultEach = result
|
||||
let isSet = false
|
||||
if (state.type_ === ProxyType.Set) {
|
||||
resultEach = new Set(result)
|
||||
result.clear()
|
||||
isSet = true
|
||||
}
|
||||
each(resultEach, (key, childValue) =>
|
||||
finalizeProperty(rootScope, state, result, key, childValue, path, isSet)
|
||||
)
|
||||
// everything inside is frozen, we can freeze here
|
||||
maybeFreeze(rootScope, result, false)
|
||||
// first time finalizing, let's create those patches
|
||||
if (path && rootScope.patches_) {
|
||||
getPlugin("Patches").generatePatches_(
|
||||
state,
|
||||
path,
|
||||
rootScope.patches_,
|
||||
rootScope.inversePatches_!
|
||||
)
|
||||
}
|
||||
}
|
||||
return state.copy_
|
||||
}
|
||||
|
||||
function finalizeProperty(
|
||||
rootScope: ImmerScope,
|
||||
parentState: undefined | ImmerState,
|
||||
targetObject: any,
|
||||
prop: string | number,
|
||||
childValue: any,
|
||||
rootPath?: PatchPath,
|
||||
targetIsSet?: boolean
|
||||
) {
|
||||
if (__DEV__ && childValue === targetObject) die(5)
|
||||
if (isDraft(childValue)) {
|
||||
const path =
|
||||
rootPath &&
|
||||
parentState &&
|
||||
parentState!.type_ !== ProxyType.Set && // Set objects are atomic since they have no keys.
|
||||
!has((parentState as Exclude<ImmerState, SetState>).assigned_!, prop) // Skip deep patches for assigned keys.
|
||||
? rootPath!.concat(prop)
|
||||
: undefined
|
||||
// Drafts owned by `scope` are finalized here.
|
||||
const res = finalize(rootScope, childValue, path)
|
||||
set(targetObject, prop, res)
|
||||
// Drafts from another scope must prevented to be frozen
|
||||
// if we got a draft back from finalize, we're in a nested produce and shouldn't freeze
|
||||
if (isDraft(res)) {
|
||||
rootScope.canAutoFreeze_ = false
|
||||
} else return
|
||||
} else if (targetIsSet) {
|
||||
targetObject.add(childValue)
|
||||
}
|
||||
// Search new objects for unfinalized drafts. Frozen objects should never contain drafts.
|
||||
if (isDraftable(childValue) && !isFrozen(childValue)) {
|
||||
if (!rootScope.immer_.autoFreeze_ && rootScope.unfinalizedDrafts_ < 1) {
|
||||
// optimization: if an object is not a draft, and we don't have to
|
||||
// deepfreeze everything, and we are sure that no drafts are left in the remaining object
|
||||
// cause we saw and finalized all drafts already; we can stop visiting the rest of the tree.
|
||||
// This benefits especially adding large data tree's without further processing.
|
||||
// See add-data.js perf test
|
||||
return
|
||||
}
|
||||
finalize(rootScope, childValue)
|
||||
// immer deep freezes plain objects, so if there is no parent state, we freeze as well
|
||||
if (!parentState || !parentState.scope_.parent_)
|
||||
maybeFreeze(rootScope, childValue)
|
||||
}
|
||||
}
|
||||
|
||||
function maybeFreeze(scope: ImmerScope, value: any, deep = false) {
|
||||
// we never freeze for a non-root scope; as it would prevent pruning for drafts inside wrapping objects
|
||||
if (!scope.parent_ && scope.immer_.autoFreeze_ && scope.canAutoFreeze_) {
|
||||
freeze(value, deep)
|
||||
}
|
||||
}
|
||||
241
node_modules/immer/src/core/immerClass.ts
generated
vendored
Normal file
241
node_modules/immer/src/core/immerClass.ts
generated
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
import {
|
||||
IProduceWithPatches,
|
||||
IProduce,
|
||||
ImmerState,
|
||||
Drafted,
|
||||
isDraftable,
|
||||
processResult,
|
||||
Patch,
|
||||
Objectish,
|
||||
DRAFT_STATE,
|
||||
Draft,
|
||||
PatchListener,
|
||||
isDraft,
|
||||
isMap,
|
||||
isSet,
|
||||
createProxyProxy,
|
||||
getPlugin,
|
||||
die,
|
||||
hasProxies,
|
||||
enterScope,
|
||||
revokeScope,
|
||||
leaveScope,
|
||||
usePatchesInScope,
|
||||
getCurrentScope,
|
||||
NOTHING,
|
||||
freeze,
|
||||
current
|
||||
} from "../internal"
|
||||
|
||||
interface ProducersFns {
|
||||
produce: IProduce
|
||||
produceWithPatches: IProduceWithPatches
|
||||
}
|
||||
|
||||
export class Immer implements ProducersFns {
|
||||
useProxies_: boolean = hasProxies
|
||||
|
||||
autoFreeze_: boolean = true
|
||||
|
||||
constructor(config?: {useProxies?: boolean; autoFreeze?: boolean}) {
|
||||
if (typeof config?.useProxies === "boolean")
|
||||
this.setUseProxies(config!.useProxies)
|
||||
if (typeof config?.autoFreeze === "boolean")
|
||||
this.setAutoFreeze(config!.autoFreeze)
|
||||
}
|
||||
|
||||
/**
|
||||
* The `produce` function takes a value and a "recipe function" (whose
|
||||
* return value often depends on the base state). The recipe function is
|
||||
* free to mutate its first argument however it wants. All mutations are
|
||||
* only ever applied to a __copy__ of the base state.
|
||||
*
|
||||
* Pass only a function to create a "curried producer" which relieves you
|
||||
* from passing the recipe function every time.
|
||||
*
|
||||
* Only plain objects and arrays are made mutable. All other objects are
|
||||
* considered uncopyable.
|
||||
*
|
||||
* Note: This function is __bound__ to its `Immer` instance.
|
||||
*
|
||||
* @param {any} base - the initial state
|
||||
* @param {Function} recipe - function that receives a proxy of the base state as first argument and which can be freely modified
|
||||
* @param {Function} patchListener - optional function that will be called with all the patches produced here
|
||||
* @returns {any} a new state, or the initial state if nothing was modified
|
||||
*/
|
||||
produce: IProduce = (base: any, recipe?: any, patchListener?: any) => {
|
||||
// curried invocation
|
||||
if (typeof base === "function" && typeof recipe !== "function") {
|
||||
const defaultBase = recipe
|
||||
recipe = base
|
||||
|
||||
const self = this
|
||||
return function curriedProduce(
|
||||
this: any,
|
||||
base = defaultBase,
|
||||
...args: any[]
|
||||
) {
|
||||
return self.produce(base, (draft: Drafted) => recipe.call(this, draft, ...args)) // prettier-ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof recipe !== "function") die(6)
|
||||
if (patchListener !== undefined && typeof patchListener !== "function")
|
||||
die(7)
|
||||
|
||||
let result
|
||||
|
||||
// Only plain objects, arrays, and "immerable classes" are drafted.
|
||||
if (isDraftable(base)) {
|
||||
const scope = enterScope(this)
|
||||
const proxy = createProxy(this, base, undefined)
|
||||
let hasError = true
|
||||
try {
|
||||
result = recipe(proxy)
|
||||
hasError = false
|
||||
} finally {
|
||||
// finally instead of catch + rethrow better preserves original stack
|
||||
if (hasError) revokeScope(scope)
|
||||
else leaveScope(scope)
|
||||
}
|
||||
if (typeof Promise !== "undefined" && result instanceof Promise) {
|
||||
return result.then(
|
||||
result => {
|
||||
usePatchesInScope(scope, patchListener)
|
||||
return processResult(result, scope)
|
||||
},
|
||||
error => {
|
||||
revokeScope(scope)
|
||||
throw error
|
||||
}
|
||||
)
|
||||
}
|
||||
usePatchesInScope(scope, patchListener)
|
||||
return processResult(result, scope)
|
||||
} else if (!base || typeof base !== "object") {
|
||||
result = recipe(base)
|
||||
if (result === undefined) result = base
|
||||
if (result === NOTHING) result = undefined
|
||||
if (this.autoFreeze_) freeze(result, true)
|
||||
if (patchListener) {
|
||||
const p: Patch[] = []
|
||||
const ip: Patch[] = []
|
||||
getPlugin("Patches").generateReplacementPatches_(base, result, p, ip)
|
||||
patchListener(p, ip)
|
||||
}
|
||||
return result
|
||||
} else die(21, base)
|
||||
}
|
||||
|
||||
produceWithPatches: IProduceWithPatches = (base: any, recipe?: any): any => {
|
||||
// curried invocation
|
||||
if (typeof base === "function") {
|
||||
return (state: any, ...args: any[]) =>
|
||||
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
|
||||
})
|
||||
|
||||
if (typeof Promise !== "undefined" && result instanceof Promise) {
|
||||
return result.then(nextState => [nextState, patches!, inversePatches!])
|
||||
}
|
||||
return [result, patches!, inversePatches!]
|
||||
}
|
||||
|
||||
createDraft<T extends Objectish>(base: T): Draft<T> {
|
||||
if (!isDraftable(base)) die(8)
|
||||
if (isDraft(base)) base = current(base)
|
||||
const scope = enterScope(this)
|
||||
const proxy = createProxy(this, base, undefined)
|
||||
proxy[DRAFT_STATE].isManual_ = true
|
||||
leaveScope(scope)
|
||||
return proxy as any
|
||||
}
|
||||
|
||||
finishDraft<D extends Draft<any>>(
|
||||
draft: D,
|
||||
patchListener?: PatchListener
|
||||
): D extends Draft<infer T> ? T : never {
|
||||
const state: ImmerState = draft && (draft as any)[DRAFT_STATE]
|
||||
if (__DEV__) {
|
||||
if (!state || !state.isManual_) die(9)
|
||||
if (state.finalized_) die(10)
|
||||
}
|
||||
const {scope_: scope} = state
|
||||
usePatchesInScope(scope, patchListener)
|
||||
return processResult(undefined, scope)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass true to automatically freeze all copies created by Immer.
|
||||
*
|
||||
* By default, auto-freezing is enabled.
|
||||
*/
|
||||
setAutoFreeze(value: boolean) {
|
||||
this.autoFreeze_ = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass true to use the ES2015 `Proxy` class when creating drafts, which is
|
||||
* always faster than using ES5 proxies.
|
||||
*
|
||||
* By default, feature detection is used, so calling this is rarely necessary.
|
||||
*/
|
||||
setUseProxies(value: boolean) {
|
||||
if (value && !hasProxies) {
|
||||
die(20)
|
||||
}
|
||||
this.useProxies_ = value
|
||||
}
|
||||
|
||||
applyPatches<T extends Objectish>(base: T, patches: Patch[]): T {
|
||||
// If a patch replaces the entire state, take that replacement as base
|
||||
// before applying patches
|
||||
let i: number
|
||||
for (i = patches.length - 1; i >= 0; i--) {
|
||||
const patch = patches[i]
|
||||
if (patch.path.length === 0 && patch.op === "replace") {
|
||||
base = patch.value
|
||||
break
|
||||
}
|
||||
}
|
||||
// If there was a patch that replaced the entire state, start from the
|
||||
// patch after that.
|
||||
if (i > -1) {
|
||||
patches = patches.slice(i + 1)
|
||||
}
|
||||
|
||||
const applyPatchesImpl = getPlugin("Patches").applyPatches_
|
||||
if (isDraft(base)) {
|
||||
// N.B: never hits if some patch a replacement, patches are never drafts
|
||||
return applyPatchesImpl(base, patches)
|
||||
}
|
||||
// Otherwise, produce a copy of the base state.
|
||||
return this.produce(base, (draft: Drafted) =>
|
||||
applyPatchesImpl(draft, patches)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function createProxy<T extends Objectish>(
|
||||
immer: Immer,
|
||||
value: T,
|
||||
parent?: ImmerState
|
||||
): Drafted<T, ImmerState> {
|
||||
// precondition: createProxy should be guarded by isDraftable, so we know we can safely draft
|
||||
const draft: Drafted = isMap(value)
|
||||
? getPlugin("MapSet").proxyMap_(value, parent)
|
||||
: isSet(value)
|
||||
? getPlugin("MapSet").proxySet_(value, parent)
|
||||
: immer.useProxies_
|
||||
? createProxyProxy(value, parent)
|
||||
: getPlugin("ES5").createES5Proxy_(value, parent)
|
||||
|
||||
const scope = parent ? parent.scope_ : getCurrentScope()
|
||||
scope.drafts_.push(draft)
|
||||
return draft
|
||||
}
|
||||
280
node_modules/immer/src/core/proxy.ts
generated
vendored
Normal file
280
node_modules/immer/src/core/proxy.ts
generated
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
import {
|
||||
each,
|
||||
has,
|
||||
is,
|
||||
isDraftable,
|
||||
shallowCopy,
|
||||
latest,
|
||||
ImmerBaseState,
|
||||
ImmerState,
|
||||
Drafted,
|
||||
AnyObject,
|
||||
AnyArray,
|
||||
Objectish,
|
||||
getCurrentScope,
|
||||
DRAFT_STATE,
|
||||
die,
|
||||
createProxy,
|
||||
ProxyType
|
||||
} from "../internal"
|
||||
|
||||
interface ProxyBaseState extends ImmerBaseState {
|
||||
assigned_: {
|
||||
[property: string]: boolean
|
||||
}
|
||||
parent_?: ImmerState
|
||||
revoke_(): void
|
||||
}
|
||||
|
||||
export interface ProxyObjectState extends ProxyBaseState {
|
||||
type_: ProxyType.ProxyObject
|
||||
base_: any
|
||||
copy_: any
|
||||
draft_: Drafted<AnyObject, ProxyObjectState>
|
||||
}
|
||||
|
||||
export interface ProxyArrayState extends ProxyBaseState {
|
||||
type_: ProxyType.ProxyArray
|
||||
base_: AnyArray
|
||||
copy_: AnyArray | null
|
||||
draft_: Drafted<AnyArray, ProxyArrayState>
|
||||
}
|
||||
|
||||
type ProxyState = ProxyObjectState | ProxyArrayState
|
||||
|
||||
/**
|
||||
* Returns a new draft of the `base` object.
|
||||
*
|
||||
* The second argument is the parent draft-state (used internally).
|
||||
*/
|
||||
export function createProxyProxy<T extends Objectish>(
|
||||
base: T,
|
||||
parent?: ImmerState
|
||||
): Drafted<T, ProxyState> {
|
||||
const isArray = Array.isArray(base)
|
||||
const state: ProxyState = {
|
||||
type_: isArray ? ProxyType.ProxyArray : (ProxyType.ProxyObject as any),
|
||||
// Track which produce call this is associated with.
|
||||
scope_: parent ? parent.scope_ : getCurrentScope()!,
|
||||
// True for both shallow and deep changes.
|
||||
modified_: false,
|
||||
// Used during finalization.
|
||||
finalized_: false,
|
||||
// Track which properties have been assigned (true) or deleted (false).
|
||||
assigned_: {},
|
||||
// The parent draft state.
|
||||
parent_: parent,
|
||||
// The base state.
|
||||
base_: base,
|
||||
// The base proxy.
|
||||
draft_: null as any, // set below
|
||||
// The base copy with any updated values.
|
||||
copy_: null,
|
||||
// Called by the `produce` function.
|
||||
revoke_: null as any,
|
||||
isManual_: false
|
||||
}
|
||||
|
||||
// the traps must target something, a bit like the 'real' base.
|
||||
// but also, we need to be able to determine from the target what the relevant state is
|
||||
// (to avoid creating traps per instance to capture the state in closure,
|
||||
// and to avoid creating weird hidden properties as well)
|
||||
// So the trick is to use 'state' as the actual 'target'! (and make sure we intercept everything)
|
||||
// Note that in the case of an array, we put the state in an array to have better Reflect defaults ootb
|
||||
let target: T = state as any
|
||||
let traps: ProxyHandler<object | Array<any>> = objectTraps
|
||||
if (isArray) {
|
||||
target = [state] as any
|
||||
traps = arrayTraps
|
||||
}
|
||||
|
||||
const {revoke, proxy} = Proxy.revocable(target, traps)
|
||||
state.draft_ = proxy as any
|
||||
state.revoke_ = revoke
|
||||
return proxy as any
|
||||
}
|
||||
|
||||
/**
|
||||
* Object drafts
|
||||
*/
|
||||
export const objectTraps: ProxyHandler<ProxyState> = {
|
||||
get(state, prop) {
|
||||
if (prop === DRAFT_STATE) return state
|
||||
|
||||
const source = latest(state)
|
||||
if (!has(source, prop)) {
|
||||
// non-existing or non-own property...
|
||||
return readPropFromProto(state, source, prop)
|
||||
}
|
||||
const value = source[prop]
|
||||
if (state.finalized_ || !isDraftable(value)) {
|
||||
return value
|
||||
}
|
||||
// Check for existing draft in modified state.
|
||||
// Assigned values are never drafted. This catches any drafts we created, too.
|
||||
if (value === peek(state.base_, prop)) {
|
||||
prepareCopy(state)
|
||||
return (state.copy_![prop as any] = createProxy(
|
||||
state.scope_.immer_,
|
||||
value,
|
||||
state
|
||||
))
|
||||
}
|
||||
return value
|
||||
},
|
||||
has(state, prop) {
|
||||
return prop in latest(state)
|
||||
},
|
||||
ownKeys(state) {
|
||||
return Reflect.ownKeys(latest(state))
|
||||
},
|
||||
set(
|
||||
state: ProxyObjectState,
|
||||
prop: string /* strictly not, but helps TS */,
|
||||
value
|
||||
) {
|
||||
const desc = getDescriptorFromProto(latest(state), prop)
|
||||
if (desc?.set) {
|
||||
// special case: if this write is captured by a setter, we have
|
||||
// to trigger it with the correct context
|
||||
desc.set.call(state.draft_, value)
|
||||
return true
|
||||
}
|
||||
if (!state.modified_) {
|
||||
// the last check is because we need to be able to distinguish setting a non-existing to undefined (which is a change)
|
||||
// from setting an existing property with value undefined to undefined (which is not a change)
|
||||
const current = peek(latest(state), prop)
|
||||
// special case, if we assigning the original value to a draft, we can ignore the assignment
|
||||
const currentState: ProxyObjectState = current?.[DRAFT_STATE]
|
||||
if (currentState && currentState.base_ === value) {
|
||||
state.copy_![prop] = value
|
||||
state.assigned_[prop] = false
|
||||
return true
|
||||
}
|
||||
if (is(value, current) && (value !== undefined || has(state.base_, prop)))
|
||||
return true
|
||||
prepareCopy(state)
|
||||
markChanged(state)
|
||||
}
|
||||
|
||||
if (
|
||||
(state.copy_![prop] === value &&
|
||||
// special case: handle new props with value 'undefined'
|
||||
(value !== undefined || prop in state.copy_)) ||
|
||||
// special case: NaN
|
||||
(Number.isNaN(value) && Number.isNaN(state.copy_![prop]))
|
||||
)
|
||||
return true
|
||||
|
||||
// @ts-ignore
|
||||
state.copy_![prop] = value
|
||||
state.assigned_[prop] = true
|
||||
return true
|
||||
},
|
||||
deleteProperty(state, prop: string) {
|
||||
// The `undefined` check is a fast path for pre-existing keys.
|
||||
if (peek(state.base_, prop) !== undefined || prop in state.base_) {
|
||||
state.assigned_[prop] = false
|
||||
prepareCopy(state)
|
||||
markChanged(state)
|
||||
} else {
|
||||
// if an originally not assigned property was deleted
|
||||
delete state.assigned_[prop]
|
||||
}
|
||||
// @ts-ignore
|
||||
if (state.copy_) delete state.copy_[prop]
|
||||
return true
|
||||
},
|
||||
// Note: We never coerce `desc.value` into an Immer draft, because we can't make
|
||||
// the same guarantee in ES5 mode.
|
||||
getOwnPropertyDescriptor(state, prop) {
|
||||
const owner = latest(state)
|
||||
const desc = Reflect.getOwnPropertyDescriptor(owner, prop)
|
||||
if (!desc) return desc
|
||||
return {
|
||||
writable: true,
|
||||
configurable: state.type_ !== ProxyType.ProxyArray || prop !== "length",
|
||||
enumerable: desc.enumerable,
|
||||
value: owner[prop]
|
||||
}
|
||||
},
|
||||
defineProperty() {
|
||||
die(11)
|
||||
},
|
||||
getPrototypeOf(state) {
|
||||
return Object.getPrototypeOf(state.base_)
|
||||
},
|
||||
setPrototypeOf() {
|
||||
die(12)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Array drafts
|
||||
*/
|
||||
|
||||
const arrayTraps: ProxyHandler<[ProxyArrayState]> = {}
|
||||
each(objectTraps, (key, fn) => {
|
||||
// @ts-ignore
|
||||
arrayTraps[key] = function() {
|
||||
arguments[0] = arguments[0][0]
|
||||
return fn.apply(this, arguments)
|
||||
}
|
||||
})
|
||||
arrayTraps.deleteProperty = function(state, prop) {
|
||||
if (__DEV__ && isNaN(parseInt(prop as any))) die(13)
|
||||
// @ts-ignore
|
||||
return arrayTraps.set!.call(this, state, prop, undefined)
|
||||
}
|
||||
arrayTraps.set = function(state, prop, value) {
|
||||
if (__DEV__ && prop !== "length" && isNaN(parseInt(prop as any))) die(14)
|
||||
return objectTraps.set!.call(this, state[0], prop, value, state[0])
|
||||
}
|
||||
|
||||
// Access a property without creating an Immer draft.
|
||||
function peek(draft: Drafted, prop: PropertyKey) {
|
||||
const state = draft[DRAFT_STATE]
|
||||
const source = state ? latest(state) : draft
|
||||
return source[prop]
|
||||
}
|
||||
|
||||
function readPropFromProto(state: ImmerState, source: any, prop: PropertyKey) {
|
||||
const desc = getDescriptorFromProto(source, prop)
|
||||
return desc
|
||||
? `value` in desc
|
||||
? desc.value
|
||||
: // This is a very special case, if the prop is a getter defined by the
|
||||
// prototype, we should invoke it with the draft as context!
|
||||
desc.get?.call(state.draft_)
|
||||
: undefined
|
||||
}
|
||||
|
||||
function getDescriptorFromProto(
|
||||
source: any,
|
||||
prop: PropertyKey
|
||||
): PropertyDescriptor | undefined {
|
||||
// 'in' checks proto!
|
||||
if (!(prop in source)) return undefined
|
||||
let proto = Object.getPrototypeOf(source)
|
||||
while (proto) {
|
||||
const desc = Object.getOwnPropertyDescriptor(proto, prop)
|
||||
if (desc) return desc
|
||||
proto = Object.getPrototypeOf(proto)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function markChanged(state: ImmerState) {
|
||||
if (!state.modified_) {
|
||||
state.modified_ = true
|
||||
if (state.parent_) {
|
||||
markChanged(state.parent_)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function prepareCopy(state: {base_: any; copy_: any}) {
|
||||
if (!state.copy_) {
|
||||
state.copy_ = shallowCopy(state.base_)
|
||||
}
|
||||
}
|
||||
85
node_modules/immer/src/core/scope.ts
generated
vendored
Normal file
85
node_modules/immer/src/core/scope.ts
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
Patch,
|
||||
PatchListener,
|
||||
Drafted,
|
||||
Immer,
|
||||
DRAFT_STATE,
|
||||
ImmerState,
|
||||
ProxyType,
|
||||
getPlugin
|
||||
} from "../internal"
|
||||
import {die} from "../utils/errors"
|
||||
|
||||
/** Each scope represents a `produce` call. */
|
||||
|
||||
export interface ImmerScope {
|
||||
patches_?: Patch[]
|
||||
inversePatches_?: Patch[]
|
||||
canAutoFreeze_: boolean
|
||||
drafts_: any[]
|
||||
parent_?: ImmerScope
|
||||
patchListener_?: PatchListener
|
||||
immer_: Immer
|
||||
unfinalizedDrafts_: number
|
||||
}
|
||||
|
||||
let currentScope: ImmerScope | undefined
|
||||
|
||||
export function getCurrentScope() {
|
||||
if (__DEV__ && !currentScope) die(0)
|
||||
return currentScope!
|
||||
}
|
||||
|
||||
function createScope(
|
||||
parent_: ImmerScope | undefined,
|
||||
immer_: Immer
|
||||
): ImmerScope {
|
||||
return {
|
||||
drafts_: [],
|
||||
parent_,
|
||||
immer_,
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
export function usePatchesInScope(
|
||||
scope: ImmerScope,
|
||||
patchListener?: PatchListener
|
||||
) {
|
||||
if (patchListener) {
|
||||
getPlugin("Patches") // assert we have the plugin
|
||||
scope.patches_ = []
|
||||
scope.inversePatches_ = []
|
||||
scope.patchListener_ = patchListener
|
||||
}
|
||||
}
|
||||
|
||||
export function revokeScope(scope: ImmerScope) {
|
||||
leaveScope(scope)
|
||||
scope.drafts_.forEach(revokeDraft)
|
||||
// @ts-ignore
|
||||
scope.drafts_ = null
|
||||
}
|
||||
|
||||
export function leaveScope(scope: ImmerScope) {
|
||||
if (scope === currentScope) {
|
||||
currentScope = scope.parent_
|
||||
}
|
||||
}
|
||||
|
||||
export function enterScope(immer: Immer) {
|
||||
return (currentScope = createScope(currentScope, immer))
|
||||
}
|
||||
|
||||
function revokeDraft(draft: Drafted) {
|
||||
const state: ImmerState = draft[DRAFT_STATE]
|
||||
if (
|
||||
state.type_ === ProxyType.ProxyObject ||
|
||||
state.type_ === ProxyType.ProxyArray
|
||||
)
|
||||
state.revoke_()
|
||||
else state.revoked_ = true
|
||||
}
|
||||
Reference in New Issue
Block a user