Skip to content

Commit

Permalink
fix: Make declarations work without ts proxy lib #718
Browse files Browse the repository at this point in the history
  • Loading branch information
Bnaya committed Dec 10, 2020
1 parent 7faa7b4 commit ee6bce7
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 194 deletions.
33 changes: 33 additions & 0 deletions src/core/ProxyStateTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
ProxyTypeProxyArray,
ImmerState,
ProxyTypeProxyObject,
AnyArray,
Drafted,
AnyObject,
ImmerBaseState
} from "../internal"

export type ProxyState = ProxyObjectState | ProxyArrayState

export interface ProxyBaseState extends ImmerBaseState {
assigned_: {
[property: string]: boolean
}
parent_?: ImmerState
revoke_(): void
}

export interface ProxyObjectState extends ProxyBaseState {
type_: typeof ProxyTypeProxyObject
base_: any
copy_: any
draft_: Drafted<AnyObject, ProxyObjectState>
}

export interface ProxyArrayState extends ProxyBaseState {
type_: typeof ProxyTypeProxyArray
base_: AnyArray
copy_: AnyArray | null
draft_: Drafted<AnyArray, ProxyArrayState>
}
189 changes: 2 additions & 187 deletions src/core/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,15 @@
import {
each,
has,
is,
isDraftable,
shallowCopy,
latest,
ImmerBaseState,
ImmerState,
Drafted,
AnyObject,
AnyArray,
Objectish,
getCurrentScope,
DRAFT_STATE,
die,
createProxy,
ProxyTypeProxyObject,
ProxyTypeProxyArray
} from "../internal"

interface ProxyBaseState extends ImmerBaseState {
assigned_: {
[property: string]: boolean
}
parent_?: ImmerState
revoke_(): void
}

export interface ProxyObjectState extends ProxyBaseState {
type_: typeof ProxyTypeProxyObject
base_: any
copy_: any
draft_: Drafted<AnyObject, ProxyObjectState>
}

export interface ProxyArrayState extends ProxyBaseState {
type_: typeof ProxyTypeProxyArray
base_: AnyArray
copy_: AnyArray | null
draft_: Drafted<AnyArray, ProxyArrayState>
}

type ProxyState = ProxyObjectState | ProxyArrayState
import {objectTraps} from "./proxyObjectTraps"
import {ProxyState, ProxyArrayState} from "./ProxyStateTypes"

/**
* Returns a new draft of the `base` object.
Expand Down Expand Up @@ -95,111 +63,6 @@ export function createProxyProxy<T extends Objectish>(
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-existig 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)
}
// @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_ !== ProxyTypeProxyArray || prop !== "length",
enumerable: desc.enumerable,
value: owner[prop]
}
},
defineProperty() {
die(11)
},
getPrototypeOf(state) {
return Object.getPrototypeOf(state.base_)
},
setPrototypeOf() {
die(12)
}
}

/**
* Array drafts
*/
Expand All @@ -220,51 +83,3 @@ 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_)
}
}

0 comments on commit ee6bce7

Please sign in to comment.