Skip to content

Commit

Permalink
Showing 18 changed files with 309 additions and 186 deletions.
16 changes: 15 additions & 1 deletion __tests__/base.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict"
import {Immer, nothing, original, isDraft, immerable} from "../src/index"
import {each, shallowCopy, isEnumerable, DRAFT_STATE} from "../src/common"
import {each, shallowCopy, isEnumerable, DRAFT_STATE} from "../src/internal"
import deepFreeze from "deep-freeze"
import cloneDeep from "lodash.clonedeep"
import * as lodash from "lodash"
@@ -582,6 +582,13 @@ function runBaseTest(name, useProxies, autoFreeze, useListener) {
expect(entries[0][0]).toBe(key)
expect(entries[0][0].a).toBe(2)
})

it("does support instanceof Map", () => {
const map = new Map()
produce(map, d => {
expect(d instanceof Map).toBeTruthy()
})
})
})

describe("set drafts", () => {
@@ -849,6 +856,13 @@ function runBaseTest(name, useProxies, autoFreeze, useListener) {
"Cannot use a proxy that has been revoked"
)
})

it("does support instanceof Set", () => {
const set = new Set()
produce(set, d => {
expect(d instanceof Set).toBeTruthy()
})
})
})

it("supports `immerable` symbol on constructor", () => {
3 changes: 2 additions & 1 deletion __tests__/empty.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {produce, produceWithPatches, setUseProxies} from "../src"
import {DRAFT_STATE} from "../src/common"
import {DRAFT_STATE} from "../src/internal"

test("empty stub test", () => {
expect(true).toBe(true)
@@ -10,6 +10,7 @@ describe("map set - es5", () => {
setUseProxies(false)

const baseState = new Map([["x", 1]])
debugger
const nextState = produce(baseState, s => {
s.set("x", 2)
})
2 changes: 1 addition & 1 deletion __tests__/polyfills.js
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ Object.assign = undefined
Reflect.ownKeys = undefined

jest.resetModules()
const common = require("../src/common")
const common = require("../src/internal")

// Reset the globals to avoid unintended effects.
Symbol = SymbolConstructor
46 changes: 6 additions & 40 deletions src/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {
DRAFT_STATE,
DRAFTABLE,
hasSet,
Objectish,
Drafted,
AnyObject,
@@ -7,46 +10,9 @@ import {
AnySet,
ImmerState,
ProxyType,
Archtype
} from "./types"

/** Use a class type for `nothing` so its type is unique */
export class Nothing {
// This lets us do `Exclude<T, Nothing>`
// @ts-ignore
private _!: unique symbol
}

const hasSymbol = typeof Symbol !== "undefined"
export const hasMap = typeof Map !== "undefined"
export const hasSet = typeof Set !== "undefined"

/**
* The sentinel value returned by producers to replace the draft with undefined.
*/
export const NOTHING: Nothing = hasSymbol
? Symbol("immer-nothing")
: ({["immer-nothing"]: true} as any)

/**
* To let Immer treat your class instances as plain immutable objects
* (albeit with a custom prototype), you must define either an instance property
* or a static property on each of your custom classes.
*
* Otherwise, your class instance will never be drafted, which means it won't be
* safe to mutate in a produce callback.
*/
export const DRAFTABLE: unique symbol = hasSymbol
? Symbol("immer-draftable")
: ("__$immer_draftable" as any)

export const DRAFT_STATE: unique symbol = hasSymbol
? Symbol("immer-state")
: ("__$immer_state" as any)

export const iteratorSymbol: typeof Symbol.iterator = hasSymbol
? Symbol.iterator
: ("@@iterator" as any)
Archtype,
hasMap
} from "./internal"

/** Returns true if the given value is an Immer draft */
export function isDraft(value: any): boolean {
40 changes: 40 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Should be no imports here!

// SOme things that should be evaluated before all else...
const hasSymbol = typeof Symbol !== "undefined"
export const hasMap = typeof Map !== "undefined"
export const hasSet = typeof Set !== "undefined"

/**
* The sentinel value returned by producers to replace the draft with undefined.
*/
export const NOTHING: Nothing = hasSymbol
? Symbol("immer-nothing")
: ({["immer-nothing"]: true} as any)

/**
* To let Immer treat your class instances as plain immutable objects
* (albeit with a custom prototype), you must define either an instance property
* or a static property on each of your custom classes.
*
* Otherwise, your class instance will never be drafted, which means it won't be
* safe to mutate in a produce callback.
*/
export const DRAFTABLE: unique symbol = hasSymbol
? Symbol("immer-draftable")
: ("__$immer_draftable" as any)

export const DRAFT_STATE: unique symbol = hasSymbol
? Symbol("immer-state")
: ("__$immer_state" as any)

export const iteratorSymbol: typeof Symbol.iterator = hasSymbol
? Symbol.iterator
: ("@@iterator" as any)

/** Use a class type for `nothing` so its type is unique */
export class Nothing {
// This lets us do `Exclude<T, Nothing>`
// @ts-ignore
private _!: unique symbol
}
17 changes: 7 additions & 10 deletions src/es5.ts
Original file line number Diff line number Diff line change
@@ -7,23 +7,20 @@ import {
isDraftable,
isEnumerable,
shallowCopy,
DRAFT_STATE,
latest,
createHiddenProperty
} from "./common"

import {ImmerScope} from "./scope"
import {
createHiddenProperty,
ImmerScope,
ImmerState,
Drafted,
AnyObject,
Objectish,
ImmerBaseState,
AnyArray,
ProxyType
} from "./types"
import {MapState} from "./map"
import {SetState} from "./set"
ProxyType,
MapState,
SetState,
DRAFT_STATE
} from "./internal"

interface ES5BaseState extends ImmerBaseState {
finalizing: boolean
23 changes: 23 additions & 0 deletions src/extends.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
var extendStatics = function(d: any, b: any): any {
extendStatics =
Object.setPrototypeOf ||
({__proto__: []} instanceof Array &&
function(d, b) {
d.__proto__ = b
}) ||
function(d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]
}
return extendStatics(d, b)
}

// Ugly hack to resolve #502 and inherit built in Map / Set
export function __extends(d: any, b: any): any {
extendStatics(d, b)
function __(this: any): any {
this.constructor = d
}
d.prototype =
// @ts-ignore
((__.prototype = b.prototype), new __())
}
30 changes: 17 additions & 13 deletions src/finalize.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import {Immer} from "./immer"
import {ImmerState, Drafted, ProxyType} from "./types"
import {ImmerScope} from "./scope"
import {
isSet,
has,
is,
get,
each,
Immer,
ImmerScope,
DRAFT_STATE,
isDraftable,
NOTHING,
Drafted,
PatchPath,
ProxyType,
each,
has,
freeze,
generatePatches,
shallowCopy,
set
} from "./common"
import {isDraft, isDraftable} from "./index"
import {SetState} from "./set"
import {generatePatches, PatchPath} from "./patches"
ImmerState,
isSet,
isDraft,
SetState,
set,
is,
get
} from "./internal"

export function processResult(immer: Immer, result: any, scope: ImmerScope) {
const baseDraft = scope.drafts![0]
45 changes: 23 additions & 22 deletions src/immer.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
import {createES5Proxy, willFinalizeES5, markChangedES5} from "./es5"
import {createProxy, markChanged} from "./proxy"

import {applyPatches} from "./patches"
import {
createES5Proxy,
willFinalizeES5,
markChangedES5,
IProduceWithPatches,
IProduce,
ImmerState,
each,
isDraft,
isSet,
isMap,
Drafted,
isDraftable,
DRAFT_STATE,
ImmerScope,
processResult,
NOTHING,
die
} from "./common"
import {ImmerScope} from "./scope"
import {
ImmerState,
IProduce,
IProduceWithPatches,
maybeFreeze,
die,
Patch,
Objectish,
PatchListener,
DRAFT_STATE,
Draft,
Patch,
Drafted
} from "./types"
import {proxyMap} from "./map"
import {proxySet} from "./set"
import {processResult, maybeFreeze} from "./finalize"
PatchListener,
isDraft,
applyPatches,
isMap,
proxyMap,
isSet,
proxySet,
createProxy,
markChanged
} from "./internal"

/* istanbul ignore next */
function verifyMinified() {}
23 changes: 12 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import {Immer} from "./immer"
import {IProduce, IProduceWithPatches} from "./types"
import {IProduce, IProduceWithPatches, Immer} from "./internal"

export {Draft, Immutable, Patch, PatchListener} from "./types"
export {
Draft,
Immutable,
Patch,
PatchListener,
original,
isDraft,
isDraftable,
NOTHING as nothing,
DRAFTABLE as immerable
} from "./internal"

const immer = new Immer()

@@ -73,12 +82,4 @@ export const createDraft = immer.createDraft.bind(immer)
*/
export const finishDraft = immer.finishDraft.bind(immer)

export {
original,
isDraft,
isDraftable,
NOTHING as nothing,
DRAFTABLE as immerable
} from "./common"

export {Immer}
12 changes: 12 additions & 0 deletions src/internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export * from "./env"
export * from "./extends"
export * from "./types"
export * from "./common"
export * from "./scope"
export * from "./finalize"
export * from "./proxy"
export * from "./es5"
export * from "./map"
export * from "./set"
export * from "./patches"
export * from "./immer"
90 changes: 57 additions & 33 deletions src/map.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import {isDraftable, DRAFT_STATE, latest, iteratorSymbol} from "./common"

import {ImmerScope} from "./scope"
import {AnyMap, Drafted, ImmerState, ImmerBaseState, ProxyType} from "./types"
import {assertUnrevoked} from "./es5"
import {
__extends,
ImmerBaseState,
ProxyType,
AnyMap,
Drafted,
ImmerState,
DRAFT_STATE,
ImmerScope,
latest,
assertUnrevoked,
isDraftable,
iteratorSymbol
} from "./internal"

export interface MapState extends ImmerBaseState {
type: ProxyType.Map
@@ -13,15 +22,14 @@ export interface MapState extends ImmerBaseState {
draft: Drafted<AnyMap, MapState>
}

// Make sure DraftMap declarion doesn't die if Map is not avialable...
/* istanbul ignore next */
const MapBase: MapConstructor =
typeof Map !== "undefined" ? Map : (function FakeMap() {} as any)

export class DraftMap<K, V> extends MapBase implements Map<K, V> {
[DRAFT_STATE]: MapState
constructor(target: AnyMap, parent?: ImmerState) {
super()
const DraftMap = (function(_super) {
if (!_super) {
/* istanbul ignore next */
throw new Error("Map is not polyfilled")
}
__extends(DraftMap, _super)
// Create class manually, cause #502
function DraftMap(this: any, target: AnyMap, parent?: ImmerState): any {
this[DRAFT_STATE] = {
type: ProxyType.Map,
parent,
@@ -31,21 +39,28 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
copy: undefined,
assigned: undefined,
base: target,
draft: this,
draft: this as any,
isManual: false,
revoked: false
}
return this
}

get size(): number {
return latest(this[DRAFT_STATE]).size
}

has(key: K): boolean {
const p = DraftMap.prototype

// TODO: smaller build size if we create a util for Object.defineProperty
Object.defineProperty(p, "size", {
get: function() {
return latest(this[DRAFT_STATE]).size
},
enumerable: true,
configurable: true
})

p.has = function(key: any): boolean {
return latest(this[DRAFT_STATE]).has(key)
}

set(key: K, value: V): this {
p.set = function(key: any, value: any) {
const state = this[DRAFT_STATE]
assertUnrevoked(state)
if (latest(state).get(key) !== value) {
@@ -58,7 +73,7 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
return this
}

delete(key: K): boolean {
p.delete = function(key: any): boolean {
if (!this.has(key)) {
return false
}
@@ -72,7 +87,7 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
return true
}

clear() {
p.clear = function() {
const state = this[DRAFT_STATE]
assertUnrevoked(state)
prepareCopy(state)
@@ -84,14 +99,17 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
return state.copy!.clear()
}

forEach(cb: (value: V, key: K, self: this) => void, thisArg?: any) {
p.forEach = function(
cb: (value: any, key: any, self: any) => void,
thisArg?: any
) {
const state = this[DRAFT_STATE]
latest(state).forEach((_value: V, key: K, _map: this) => {
latest(state).forEach((_value: any, key: any, _map: any) => {
cb.call(thisArg, this.get(key), key, this)
})
}

get(key: K): V {
p.get = function(key: any): any {
const state = this[DRAFT_STATE]
assertUnrevoked(state)
const value = latest(state).get(key)
@@ -108,11 +126,11 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
return draft
}

keys(): IterableIterator<K> {
p.keys = function(): IterableIterator<any> {
return latest(this[DRAFT_STATE]).keys()
}

values(): IterableIterator<V> {
p.values = function(): IterableIterator<any> {
const iterator = this.keys()
return {
[iteratorSymbol]: () => this.values(),
@@ -129,7 +147,7 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
} as any
}

entries(): IterableIterator<[K, V]> {
p.entries = function(): IterableIterator<[any, any]> {
const iterator = this.keys()
return {
[iteratorSymbol]: () => this.entries(),
@@ -146,12 +164,18 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
} as any
}

[iteratorSymbol]() {
p[iteratorSymbol] = function() {
return this.entries()
}
}

export function proxyMap(target: AnyMap, parent?: ImmerState) {
return DraftMap
})(Map)

export function proxyMap<T extends AnyMap>(
target: T,
parent?: ImmerState
): T & {[DRAFT_STATE]: MapState} {
// @ts-ignore
return new DraftMap(target, parent)
}

24 changes: 18 additions & 6 deletions src/patches.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import {get, each, isMap, has, die, getArchtype} from "./common"
import {Patch, ImmerState, ProxyType, Archtype} from "./types"
import {SetState} from "./set"
import {ES5ArrayState, ES5ObjectState} from "./es5"
import {ProxyArrayState, ProxyObjectState} from "./proxy"
import {MapState} from "./map"
import {
get,
each,
has,
die,
getArchtype,
ImmerState,
Patch,
ProxyType,
SetState,
ES5ArrayState,
ProxyArrayState,
MapState,
ES5ObjectState,
ProxyObjectState,
Archtype,
isMap
} from "./internal"

export type PatchPath = (string | number)[]

18 changes: 8 additions & 10 deletions src/proxy.ts
Original file line number Diff line number Diff line change
@@ -5,19 +5,17 @@ import {
is,
isDraftable,
shallowCopy,
DRAFT_STATE,
latest
} from "./common"
import {ImmerScope} from "./scope"
import {
AnyObject,
Drafted,
latest,
ImmerBaseState,
ImmerState,
Drafted,
ProxyType,
AnyObject,
AnyArray,
Objectish,
ImmerBaseState,
ProxyType
} from "./types"
ImmerScope,
DRAFT_STATE
} from "./internal"

interface ProxyBaseState extends ImmerBaseState {
assigned: {
11 changes: 8 additions & 3 deletions src/scope.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import {DRAFT_STATE} from "./common"
import {Patch, PatchListener, Drafted, ProxyType} from "./types"
import {Immer} from "./immer"
import {
Patch,
PatchListener,
Drafted,
ProxyType,
Immer,
DRAFT_STATE
} from "./internal"

/** Each scope represents a `produce` call. */
export class ImmerScope {
76 changes: 48 additions & 28 deletions src/set.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import {DRAFT_STATE, latest, isDraftable, iteratorSymbol} from "./common"

import {ImmerScope} from "./scope"
import {AnySet, Drafted, ImmerState, ImmerBaseState, ProxyType} from "./types"
import {assertUnrevoked} from "./es5"
import {
__extends,
ImmerBaseState,
ProxyType,
AnySet,
Drafted,
ImmerState,
DRAFT_STATE,
ImmerScope,
latest,
assertUnrevoked,
iteratorSymbol,
isDraftable
} from "./internal"

export interface SetState extends ImmerBaseState {
type: ProxyType.Set
@@ -13,15 +22,14 @@ export interface SetState extends ImmerBaseState {
draft: Drafted<AnySet, SetState>
}

// Make sure DraftSet declarion doesn't die if Map is not avialable...
/* istanbul ignore next */
const SetBase: SetConstructor =
typeof Set !== "undefined" ? Set : (function FakeSet() {} as any)

export class DraftSet<K, V> extends SetBase implements Set<V> {
[DRAFT_STATE]: SetState
constructor(target: AnySet, parent?: ImmerState) {
super()
const DraftSet = (function(_super) {
if (!_super) {
/* istanbul ignore next */
throw new Error("Set is not polyfilled")
}
__extends(DraftSet, _super)
// Create class manually, cause #502
function DraftSet(this: any, target: AnySet, parent?: ImmerState) {
this[DRAFT_STATE] = {
type: ProxyType.Set,
parent,
@@ -35,13 +43,19 @@ export class DraftSet<K, V> extends SetBase implements Set<V> {
revoked: false,
isManual: false
}
return this
}
const p = DraftSet.prototype

get size(): number {
return latest(this[DRAFT_STATE]).size
}
Object.defineProperty(p, "size", {
get: function() {
return latest(this[DRAFT_STATE]).size
},
enumerable: true,
configurable: true
})

has(value: V): boolean {
p.has = function(value: any): boolean {
const state = this[DRAFT_STATE]
assertUnrevoked(state)
// bit of trickery here, to be able to recognize both the value, and the draft of its value
@@ -54,7 +68,7 @@ export class DraftSet<K, V> extends SetBase implements Set<V> {
return false
}

add(value: V): this {
p.add = function(value: any): any {
const state = this[DRAFT_STATE]
assertUnrevoked(state)
if (state.copy) {
@@ -67,7 +81,7 @@ export class DraftSet<K, V> extends SetBase implements Set<V> {
return this
}

delete(value: V): boolean {
p.delete = function(value: any): any {
if (!this.has(value)) {
return false
}
@@ -84,47 +98,53 @@ export class DraftSet<K, V> extends SetBase implements Set<V> {
)
}

clear() {
p.clear = function() {
const state = this[DRAFT_STATE]
assertUnrevoked(state)
prepareCopy(state)
state.scope.immer.markChanged(state)
return state.copy!.clear()
}

values(): IterableIterator<V> {
p.values = function(): IterableIterator<any> {
const state = this[DRAFT_STATE]
assertUnrevoked(state)
prepareCopy(state)
return state.copy!.values()
}

entries(): IterableIterator<[V, V]> {
p.entries = function entries(): IterableIterator<[any, any]> {
const state = this[DRAFT_STATE]
assertUnrevoked(state)
prepareCopy(state)
return state.copy!.entries()
}

keys(): IterableIterator<V> {
p.keys = function(): IterableIterator<any> {
return this.values()
}

[iteratorSymbol]() {
p[iteratorSymbol] = function() {
return this.values()
}

forEach(cb: (value: V, key: V, self: this) => void, thisArg?: any) {
p.forEach = function forEach(cb: any, thisArg?: any) {
const iterator = this.values()
let result = iterator.next()
while (!result.done) {
cb.call(thisArg, result.value, result.value, this)
result = iterator.next()
}
}
}

export function proxySet(target: AnySet, parent?: ImmerState) {
return DraftSet
})(Set)

export function proxySet<T extends AnySet>(
target: T,
parent?: ImmerState
): T & {[DRAFT_STATE]: SetState} {
// @ts-ignore
return new DraftSet(target, parent)
}

17 changes: 11 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import {Nothing, DRAFT_STATE} from "./common"
import {SetState} from "./set"
import {MapState} from "./map"
import {ProxyObjectState, ProxyArrayState} from "./proxy"
import {ES5ObjectState, ES5ArrayState} from "./es5"
import {ImmerScope} from "./scope"
import {
SetState,
ImmerScope,
ProxyObjectState,
ProxyArrayState,
ES5ObjectState,
ES5ArrayState,
MapState,
DRAFT_STATE,
Nothing
} from "./internal"

export type Objectish = AnyObject | AnyArray | AnyMap | AnySet
export type ObjectishNoSet = AnyObject | AnyArray | AnyMap
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"lib": ["es2015"],
"target": "ES6",
"target": "ES5",
"strict": true,
"declaration": true,
"importHelpers": false,

0 comments on commit 9f4711a

Please sign in to comment.