UNPKG

mutative

Version:

A JavaScript library for efficient immutable updates

1,417 lines (1,400 loc) 63.6 kB
const Operation = { Remove: 'remove', Replace: 'replace', Add: 'add', }; // Don't use `Symbol()` just for 3rd party access the draft const PROXY_DRAFT = Symbol.for('__MUTATIVE_PROXY_DRAFT__'); const RAW_RETURN_SYMBOL = Symbol('__MUTATIVE_RAW_RETURN_SYMBOL__'); const iteratorSymbol = Symbol.iterator; const dataTypes = { mutable: 'mutable', immutable: 'immutable', }; const internal = {}; function has(target, key) { return target instanceof Map ? target.has(key) : Object.prototype.hasOwnProperty.call(target, key); } function getDescriptor(target, key) { if (key in target) { let prototype = Reflect.getPrototypeOf(target); while (prototype) { const descriptor = Reflect.getOwnPropertyDescriptor(prototype, key); if (descriptor) return descriptor; prototype = Reflect.getPrototypeOf(prototype); } } return; } function isBaseSetInstance(obj) { return Object.getPrototypeOf(obj) === Set.prototype; } function isBaseMapInstance(obj) { return Object.getPrototypeOf(obj) === Map.prototype; } function latest(proxyDraft) { var _a; return (_a = proxyDraft.copy) !== null && _a !== void 0 ? _a : proxyDraft.original; } /** * Check if the value is a draft */ function isDraft(target) { return !!getProxyDraft(target); } function getProxyDraft(value) { if (typeof value !== 'object') return null; return value === null || value === void 0 ? void 0 : value[PROXY_DRAFT]; } function getValue(value) { var _a; const proxyDraft = getProxyDraft(value); return proxyDraft ? ((_a = proxyDraft.copy) !== null && _a !== void 0 ? _a : proxyDraft.original) : value; } /** * Check if a value is draftable */ function isDraftable(value, options) { if (!value || typeof value !== 'object') return false; let markResult; return (Object.getPrototypeOf(value) === Object.prototype || Array.isArray(value) || value instanceof Map || value instanceof Set || (!!(options === null || options === void 0 ? void 0 : options.mark) && ((markResult = options.mark(value, dataTypes)) === dataTypes.immutable || typeof markResult === 'function'))); } function getPath(target, path = []) { if (Object.hasOwnProperty.call(target, 'key')) { // check if the parent is a draft and the original value is not equal to the current value const parentCopy = target.parent.copy; const proxyDraft = getProxyDraft(get(parentCopy, target.key)); if (proxyDraft !== null && (proxyDraft === null || proxyDraft === void 0 ? void 0 : proxyDraft.original) !== target.original) { return null; } const isSet = target.parent.type === 3 /* DraftType.Set */; const key = isSet ? Array.from(target.parent.setMap.keys()).indexOf(target.key) : target.key; // check if the key is still in the next state parent if (!((isSet && parentCopy.size > key) || has(parentCopy, key))) return null; path.push(key); } if (target.parent) { return getPath(target.parent, path); } // `target` is root draft. path.reverse(); try { // check if the path is valid resolvePath(target.copy, path); } catch (e) { return null; } return path; } function getType(target) { if (Array.isArray(target)) return 1 /* DraftType.Array */; if (target instanceof Map) return 2 /* DraftType.Map */; if (target instanceof Set) return 3 /* DraftType.Set */; return 0 /* DraftType.Object */; } function get(target, key) { return getType(target) === 2 /* DraftType.Map */ ? target.get(key) : target[key]; } function set(target, key, value) { const type = getType(target); if (type === 2 /* DraftType.Map */) { target.set(key, value); } else { target[key] = value; } } function peek(target, key) { const state = getProxyDraft(target); const source = state ? latest(state) : target; return source[key]; } function isEqual(x, y) { if (x === y) { return x !== 0 || 1 / x === 1 / y; } else { return x !== x && y !== y; } } function revokeProxy(proxyDraft) { if (!proxyDraft) return; while (proxyDraft.finalities.revoke.length > 0) { const revoke = proxyDraft.finalities.revoke.pop(); revoke(); } } // handle JSON Pointer path with spec https://www.rfc-editor.org/rfc/rfc6901 function escapePath(path, pathAsArray) { return pathAsArray ? path : [''] .concat(path) .map((_item) => { const item = `${_item}`; if (item.indexOf('/') === -1 && item.indexOf('~') === -1) return item; return item.replace(/~/g, '~0').replace(/\//g, '~1'); }) .join('/'); } function unescapePath(path) { if (Array.isArray(path)) return path; return path .split('/') .map((_item) => _item.replace(/~1/g, '/').replace(/~0/g, '~')) .slice(1); } function resolvePath(base, path) { for (let index = 0; index < path.length - 1; index += 1) { const key = path[index]; // use `index` in Set draft base = get(getType(base) === 3 /* DraftType.Set */ ? Array.from(base) : base, key); if (typeof base !== 'object') { throw new Error(`Cannot resolve patch at '${path.join('/')}'.`); } } return base; } function strictCopy(target) { const copy = Object.create(Object.getPrototypeOf(target)); Reflect.ownKeys(target).forEach((key) => { let desc = Reflect.getOwnPropertyDescriptor(target, key); if (desc.enumerable && desc.configurable && desc.writable) { copy[key] = target[key]; return; } // for freeze if (!desc.writable) { desc.writable = true; desc.configurable = true; } if (desc.get || desc.set) desc = { configurable: true, writable: true, enumerable: desc.enumerable, value: target[key], }; Reflect.defineProperty(copy, key, desc); }); return copy; } const propIsEnum = Object.prototype.propertyIsEnumerable; function shallowCopy(original, options) { let markResult; if (Array.isArray(original)) { return Array.prototype.concat.call(original); } else if (original instanceof Set) { if (!isBaseSetInstance(original)) { const SubClass = Object.getPrototypeOf(original).constructor; return new SubClass(original.values()); } return Set.prototype.difference ? Set.prototype.difference.call(original, new Set()) : new Set(original.values()); } else if (original instanceof Map) { if (!isBaseMapInstance(original)) { const SubClass = Object.getPrototypeOf(original).constructor; return new SubClass(original); } return new Map(original); } else if ((options === null || options === void 0 ? void 0 : options.mark) && ((markResult = options.mark(original, dataTypes)), markResult !== undefined) && markResult !== dataTypes.mutable) { if (markResult === dataTypes.immutable) { return strictCopy(original); } else if (typeof markResult === 'function') { if ((options.enablePatches || options.enableAutoFreeze)) { throw new Error(`You can't use mark and patches or auto freeze together.`); } return markResult(); } throw new Error(`Unsupported mark result: ${markResult}`); } else if (typeof original === 'object' && Object.getPrototypeOf(original) === Object.prototype) { // For best performance with shallow copies, // don't use `Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));` by default. const copy = {}; Object.keys(original).forEach((key) => { copy[key] = original[key]; }); Object.getOwnPropertySymbols(original).forEach((key) => { if (propIsEnum.call(original, key)) { copy[key] = original[key]; } }); return copy; } else { throw new Error(`Please check mark() to ensure that it is a stable marker draftable function.`); } } function ensureShallowCopy(target) { if (target.copy) return; target.copy = shallowCopy(target.original, target.options); } function deepClone(target) { if (!isDraftable(target)) return getValue(target); if (Array.isArray(target)) return target.map(deepClone); if (target instanceof Map) { const iterable = Array.from(target.entries()).map(([k, v]) => [ k, deepClone(v), ]); if (!isBaseMapInstance(target)) { const SubClass = Object.getPrototypeOf(target).constructor; return new SubClass(iterable); } return new Map(iterable); } if (target instanceof Set) { const iterable = Array.from(target).map(deepClone); if (!isBaseSetInstance(target)) { const SubClass = Object.getPrototypeOf(target).constructor; return new SubClass(iterable); } return new Set(iterable); } const copy = Object.create(Object.getPrototypeOf(target)); for (const key in target) copy[key] = deepClone(target[key]); return copy; } function cloneIfNeeded(target) { return isDraft(target) ? deepClone(target) : target; } function markChanged(proxyDraft) { var _a; proxyDraft.assignedMap = (_a = proxyDraft.assignedMap) !== null && _a !== void 0 ? _a : new Map(); if (!proxyDraft.operated) { proxyDraft.operated = true; if (proxyDraft.parent) { markChanged(proxyDraft.parent); } } } function throwFrozenError() { throw new Error('Cannot modify frozen object'); } function deepFreeze(target, subKey, updatedValues, stack, keys) { { updatedValues = updatedValues !== null && updatedValues !== void 0 ? updatedValues : new WeakMap(); stack = stack !== null && stack !== void 0 ? stack : []; keys = keys !== null && keys !== void 0 ? keys : []; const value = updatedValues.has(target) ? updatedValues.get(target) : target; if (stack.length > 0) { const index = stack.indexOf(value); if (value && typeof value === 'object' && index !== -1) { if (stack[0] === value) { throw new Error(`Forbids circular reference`); } throw new Error(`Forbids circular reference: ~/${keys .slice(0, index) .map((key, index) => { if (typeof key === 'symbol') return `[${key.toString()}]`; const parent = stack[index]; if (typeof key === 'object' && (parent instanceof Map || parent instanceof Set)) return Array.from(parent.keys()).indexOf(key); return key; }) .join('/')}`); } stack.push(value); keys.push(subKey); } else { stack.push(value); } } if (Object.isFrozen(target) || isDraft(target)) { { stack.pop(); keys.pop(); } return; } const type = getType(target); switch (type) { case 2 /* DraftType.Map */: for (const [key, value] of target) { deepFreeze(key, key, updatedValues, stack, keys); deepFreeze(value, key, updatedValues, stack, keys); } target.set = target.clear = target.delete = throwFrozenError; break; case 3 /* DraftType.Set */: for (const value of target) { deepFreeze(value, value, updatedValues, stack, keys); } target.add = target.clear = target.delete = throwFrozenError; break; case 1 /* DraftType.Array */: Object.freeze(target); let index = 0; for (const value of target) { deepFreeze(value, index, updatedValues, stack, keys); index += 1; } break; default: Object.freeze(target); // ignore non-enumerable or symbol properties Object.keys(target).forEach((name) => { const value = target[name]; deepFreeze(value, name, updatedValues, stack, keys); }); } { stack.pop(); keys.pop(); } } function forEach(target, iter) { const type = getType(target); if (type === 0 /* DraftType.Object */) { Reflect.ownKeys(target).forEach((key) => { iter(key, target[key], target); }); } else if (type === 1 /* DraftType.Array */) { let index = 0; for (const entry of target) { iter(index, entry, target); index += 1; } } else { target.forEach((entry, index) => iter(index, entry, target)); } } function handleValue(target, handledSet, options) { if (isDraft(target) || !isDraftable(target, options) || handledSet.has(target) || Object.isFrozen(target)) return; const isSet = target instanceof Set; const setMap = isSet ? new Map() : undefined; handledSet.add(target); forEach(target, (key, value) => { var _a; if (isDraft(value)) { const proxyDraft = getProxyDraft(value); ensureShallowCopy(proxyDraft); // A draft where a child node has been changed, or assigned a value const updatedValue = ((_a = proxyDraft.assignedMap) === null || _a === void 0 ? void 0 : _a.size) || proxyDraft.operated ? proxyDraft.copy : proxyDraft.original; // final update value set(isSet ? setMap : target, key, updatedValue); } else { handleValue(value, handledSet, options); } }); if (setMap) { const set = target; const values = Array.from(set); set.clear(); values.forEach((value) => { set.add(setMap.has(value) ? setMap.get(value) : value); }); } } function finalizeAssigned(proxyDraft, key) { // handle the draftable assigned values, and the value is not a draft const copy = proxyDraft.type === 3 /* DraftType.Set */ ? proxyDraft.setMap : proxyDraft.copy; if (proxyDraft.finalities.revoke.length > 1 && proxyDraft.assignedMap.get(key) && copy) { handleValue(get(copy, key), proxyDraft.finalities.handledSet, proxyDraft.options); } } function finalizeSetValue(target) { if (target.type === 3 /* DraftType.Set */ && target.copy) { target.copy.clear(); target.setMap.forEach((value) => { target.copy.add(getValue(value)); }); } } function finalizePatches(target, generatePatches, patches, inversePatches) { const shouldFinalize = target.operated && target.assignedMap && target.assignedMap.size > 0 && !target.finalized; if (shouldFinalize) { if (patches && inversePatches) { const basePath = getPath(target); if (basePath) { generatePatches(target, basePath, patches, inversePatches); } } target.finalized = true; } } function markFinalization(target, key, value, generatePatches) { const proxyDraft = getProxyDraft(value); if (proxyDraft) { // !case: assign the draft value if (!proxyDraft.callbacks) { proxyDraft.callbacks = []; } proxyDraft.callbacks.push((patches, inversePatches) => { var _a; const copy = target.type === 3 /* DraftType.Set */ ? target.setMap : target.copy; if (isEqual(get(copy, key), value)) { let updatedValue = proxyDraft.original; if (proxyDraft.copy) { updatedValue = proxyDraft.copy; } finalizeSetValue(target); finalizePatches(target, generatePatches, patches, inversePatches); if (target.options.enableAutoFreeze) { target.options.updatedValues = (_a = target.options.updatedValues) !== null && _a !== void 0 ? _a : new WeakMap(); target.options.updatedValues.set(updatedValue, proxyDraft.original); } // final update value set(copy, key, updatedValue); } }); if (target.options.enableAutoFreeze) { // !case: assign the draft value in cross draft tree if (proxyDraft.finalities !== target.finalities) { target.options.enableAutoFreeze = false; } } } if (isDraftable(value, target.options)) { // !case: assign the non-draft value target.finalities.draft.push(() => { const copy = target.type === 3 /* DraftType.Set */ ? target.setMap : target.copy; if (isEqual(get(copy, key), value)) { finalizeAssigned(target, key); } }); } } function generateArrayPatches(proxyState, basePath, patches, inversePatches, pathAsArray) { let { original, assignedMap, options } = proxyState; let copy = proxyState.copy; if (copy.length < original.length) { [original, copy] = [copy, original]; [patches, inversePatches] = [inversePatches, patches]; } for (let index = 0; index < original.length; index += 1) { if (assignedMap.get(index.toString()) && copy[index] !== original[index]) { const _path = basePath.concat([index]); const path = escapePath(_path, pathAsArray); patches.push({ op: Operation.Replace, path, // If it is a draft, it needs to be deep cloned, and it may also be non-draft. value: cloneIfNeeded(copy[index]), }); inversePatches.push({ op: Operation.Replace, path, // If it is a draft, it needs to be deep cloned, and it may also be non-draft. value: cloneIfNeeded(original[index]), }); } } for (let index = original.length; index < copy.length; index += 1) { const _path = basePath.concat([index]); const path = escapePath(_path, pathAsArray); patches.push({ op: Operation.Add, path, // If it is a draft, it needs to be deep cloned, and it may also be non-draft. value: cloneIfNeeded(copy[index]), }); } if (original.length < copy.length) { // https://www.rfc-editor.org/rfc/rfc6902#appendix-A.4 // For performance, here we only generate an operation that replaces the length of the array, // which is inconsistent with JSON Patch specification const { arrayLengthAssignment = true } = options.enablePatches; if (arrayLengthAssignment) { const _path = basePath.concat(['length']); const path = escapePath(_path, pathAsArray); inversePatches.push({ op: Operation.Replace, path, value: original.length, }); } else { for (let index = copy.length; original.length < index; index -= 1) { const _path = basePath.concat([index - 1]); const path = escapePath(_path, pathAsArray); inversePatches.push({ op: Operation.Remove, path, }); } } } } function generatePatchesFromAssigned({ original, copy, assignedMap }, basePath, patches, inversePatches, pathAsArray) { assignedMap.forEach((assignedValue, key) => { const originalValue = get(original, key); const value = cloneIfNeeded(get(copy, key)); const op = !assignedValue ? Operation.Remove : has(original, key) ? Operation.Replace : Operation.Add; if (isEqual(originalValue, value) && op === Operation.Replace) return; const _path = basePath.concat(key); const path = escapePath(_path, pathAsArray); patches.push(op === Operation.Remove ? { op, path } : { op, path, value }); inversePatches.push(op === Operation.Add ? { op: Operation.Remove, path } : op === Operation.Remove ? { op: Operation.Add, path, value: originalValue } : { op: Operation.Replace, path, value: originalValue }); }); } function generateSetPatches({ original, copy }, basePath, patches, inversePatches, pathAsArray) { let index = 0; original.forEach((value) => { if (!copy.has(value)) { const _path = basePath.concat([index]); const path = escapePath(_path, pathAsArray); patches.push({ op: Operation.Remove, path, value, }); inversePatches.unshift({ op: Operation.Add, path, value, }); } index += 1; }); index = 0; copy.forEach((value) => { if (!original.has(value)) { const _path = basePath.concat([index]); const path = escapePath(_path, pathAsArray); patches.push({ op: Operation.Add, path, value, }); inversePatches.unshift({ op: Operation.Remove, path, value, }); } index += 1; }); } function generatePatches(proxyState, basePath, patches, inversePatches) { const { pathAsArray = true } = proxyState.options.enablePatches; switch (proxyState.type) { case 0 /* DraftType.Object */: case 2 /* DraftType.Map */: return generatePatchesFromAssigned(proxyState, basePath, patches, inversePatches, pathAsArray); case 1 /* DraftType.Array */: return generateArrayPatches(proxyState, basePath, patches, inversePatches, pathAsArray); case 3 /* DraftType.Set */: return generateSetPatches(proxyState, basePath, patches, inversePatches, pathAsArray); } } let readable = false; const checkReadable = (value, options, ignoreCheckDraftable = false) => { if (typeof value === 'object' && value !== null && (!isDraftable(value, options) || ignoreCheckDraftable) && !readable) { throw new Error(`Strict mode: Mutable data cannot be accessed directly, please use 'unsafe(callback)' wrap.`); } }; /** * `unsafe(callback)` to access mutable data directly in strict mode. * * ## Example * * ```ts * import { create, unsafe } from '../index'; * * class Foobar { * bar = 1; * } * * const baseState = { foobar: new Foobar() }; * const state = create( * baseState, * (draft) => { * unsafe(() => { * draft.foobar.bar = 2; * }); * }, * { * strict: true, * } * ); * * expect(state).toBe(baseState); * expect(state.foobar).toBe(baseState.foobar); * expect(state.foobar.bar).toBe(2); * ``` */ function unsafe(callback) { readable = true; let result; try { result = callback(); } finally { readable = false; } return result; } const mapHandler = { get size() { const current = latest(getProxyDraft(this)); return current.size; }, has(key) { return latest(getProxyDraft(this)).has(key); }, set(key, value) { const target = getProxyDraft(this); const source = latest(target); if (!source.has(key) || !isEqual(source.get(key), value)) { ensureShallowCopy(target); markChanged(target); target.assignedMap.set(key, true); target.copy.set(key, value); markFinalization(target, key, value, generatePatches); } return this; }, delete(key) { if (!this.has(key)) { return false; } const target = getProxyDraft(this); ensureShallowCopy(target); markChanged(target); if (target.original.has(key)) { target.assignedMap.set(key, false); } else { target.assignedMap.delete(key); } target.copy.delete(key); return true; }, clear() { const target = getProxyDraft(this); if (!this.size) return; ensureShallowCopy(target); markChanged(target); target.assignedMap = new Map(); for (const [key] of target.original) { target.assignedMap.set(key, false); } target.copy.clear(); }, forEach(callback, thisArg) { const target = getProxyDraft(this); latest(target).forEach((_value, _key) => { callback.call(thisArg, this.get(_key), _key, this); }); }, get(key) { var _a, _b; const target = getProxyDraft(this); const value = latest(target).get(key); const mutable = ((_b = (_a = target.options).mark) === null || _b === void 0 ? void 0 : _b.call(_a, value, dataTypes)) === dataTypes.mutable; if (target.options.strict) { checkReadable(value, target.options, mutable); } if (mutable) { return value; } if (target.finalized || !isDraftable(value, target.options)) { return value; } // drafted or reassigned if (value !== target.original.get(key)) { return value; } const draft = internal.createDraft({ original: value, parentDraft: target, key, finalities: target.finalities, options: target.options, }); ensureShallowCopy(target); target.copy.set(key, draft); return draft; }, keys() { return latest(getProxyDraft(this)).keys(); }, values() { const iterator = this.keys(); return { [iteratorSymbol]: () => this.values(), next: () => { const result = iterator.next(); if (result.done) return result; const value = this.get(result.value); return { done: false, value, }; }, }; }, entries() { const iterator = this.keys(); return { [iteratorSymbol]: () => this.entries(), next: () => { const result = iterator.next(); if (result.done) return result; const value = this.get(result.value); return { done: false, value: [result.value, value], }; }, }; }, [iteratorSymbol]() { return this.entries(); }, }; const mapHandlerKeys = Reflect.ownKeys(mapHandler); const getNextIterator = (target, iterator, { isValuesIterator }) => () => { var _a, _b; const result = iterator.next(); if (result.done) return result; const key = result.value; let value = target.setMap.get(key); const currentDraft = getProxyDraft(value); const mutable = ((_b = (_a = target.options).mark) === null || _b === void 0 ? void 0 : _b.call(_a, value, dataTypes)) === dataTypes.mutable; if (target.options.strict) { checkReadable(key, target.options, mutable); } if (!mutable && !currentDraft && isDraftable(key, target.options) && !target.finalized && target.original.has(key)) { // draft a draftable original set item const proxy = internal.createDraft({ original: key, parentDraft: target, key, finalities: target.finalities, options: target.options, }); target.setMap.set(key, proxy); value = proxy; } else if (currentDraft) { // drafted value = currentDraft.proxy; } return { done: false, value: isValuesIterator ? value : [value, value], }; }; const setHandler = { get size() { const target = getProxyDraft(this); return target.setMap.size; }, has(value) { const target = getProxyDraft(this); // reassigned or non-draftable values if (target.setMap.has(value)) return true; ensureShallowCopy(target); const valueProxyDraft = getProxyDraft(value); // drafted if (valueProxyDraft && target.setMap.has(valueProxyDraft.original)) return true; return false; }, add(value) { const target = getProxyDraft(this); if (!this.has(value)) { ensureShallowCopy(target); markChanged(target); target.assignedMap.set(value, true); target.setMap.set(value, value); markFinalization(target, value, value, generatePatches); } return this; }, delete(value) { if (!this.has(value)) { return false; } const target = getProxyDraft(this); ensureShallowCopy(target); markChanged(target); const valueProxyDraft = getProxyDraft(value); if (valueProxyDraft && target.setMap.has(valueProxyDraft.original)) { // delete drafted target.assignedMap.set(valueProxyDraft.original, false); return target.setMap.delete(valueProxyDraft.original); } if (!valueProxyDraft && target.setMap.has(value)) { // non-draftable values target.assignedMap.set(value, false); } else { // reassigned target.assignedMap.delete(value); } // delete reassigned or non-draftable values return target.setMap.delete(value); }, clear() { if (!this.size) return; const target = getProxyDraft(this); ensureShallowCopy(target); markChanged(target); for (const value of target.original) { target.assignedMap.set(value, false); } target.setMap.clear(); }, values() { const target = getProxyDraft(this); ensureShallowCopy(target); const iterator = target.setMap.keys(); return { [Symbol.iterator]: () => this.values(), next: getNextIterator(target, iterator, { isValuesIterator: true }), }; }, entries() { const target = getProxyDraft(this); ensureShallowCopy(target); const iterator = target.setMap.keys(); return { [Symbol.iterator]: () => this.entries(), next: getNextIterator(target, iterator, { isValuesIterator: false, }), }; }, keys() { return this.values(); }, [iteratorSymbol]() { return this.values(); }, forEach(callback, thisArg) { const iterator = this.values(); let result = iterator.next(); while (!result.done) { callback.call(thisArg, result.value, result.value, this); result = iterator.next(); } }, }; if (Set.prototype.difference) { // for compatibility with new Set methods // https://github.com/tc39/proposal-set-methods // And `https://github.com/tc39/proposal-set-methods/blob/main/details.md#symbolspecies` has some details about the `@@species` symbol. // So we can't use SubSet instance constructor to get the constructor of the SubSet instance. Object.assign(setHandler, { intersection(other) { return Set.prototype.intersection.call(new Set(this.values()), other); }, union(other) { return Set.prototype.union.call(new Set(this.values()), other); }, difference(other) { return Set.prototype.difference.call(new Set(this.values()), other); }, symmetricDifference(other) { return Set.prototype.symmetricDifference.call(new Set(this.values()), other); }, isSubsetOf(other) { return Set.prototype.isSubsetOf.call(new Set(this.values()), other); }, isSupersetOf(other) { return Set.prototype.isSupersetOf.call(new Set(this.values()), other); }, isDisjointFrom(other) { return Set.prototype.isDisjointFrom.call(new Set(this.values()), other); }, }); } const setHandlerKeys = Reflect.ownKeys(setHandler); const proxyHandler = { get(target, key, receiver) { var _a, _b; const copy = (_a = target.copy) === null || _a === void 0 ? void 0 : _a[key]; // Improve draft reading performance by caching the draft copy. if (copy && target.finalities.draftsCache.has(copy)) { return copy; } if (key === PROXY_DRAFT) return target; let markResult; if (target.options.mark) { // handle `Uncaught TypeError: Method get Map.prototype.size called on incompatible receiver #<Map>` // or `Uncaught TypeError: Method get Set.prototype.size called on incompatible receiver #<Set>` const value = key === 'size' && (target.original instanceof Map || target.original instanceof Set) ? Reflect.get(target.original, key) : Reflect.get(target.original, key, receiver); markResult = target.options.mark(value, dataTypes); if (markResult === dataTypes.mutable) { if (target.options.strict) { checkReadable(value, target.options, true); } return value; } } const source = latest(target); if (source instanceof Map && mapHandlerKeys.includes(key)) { if (key === 'size') { return Object.getOwnPropertyDescriptor(mapHandler, 'size').get.call(target.proxy); } const handle = mapHandler[key]; return handle.bind(target.proxy); } if (source instanceof Set && setHandlerKeys.includes(key)) { if (key === 'size') { return Object.getOwnPropertyDescriptor(setHandler, 'size').get.call(target.proxy); } const handle = setHandler[key]; return handle.bind(target.proxy); } if (!has(source, key)) { const desc = getDescriptor(source, key); return desc ? `value` in desc ? desc.value : // !case: support for getter (_b = desc.get) === null || _b === void 0 ? void 0 : _b.call(target.proxy) : undefined; } const value = source[key]; if (target.options.strict) { checkReadable(value, target.options); } if (target.finalized || !isDraftable(value, target.options)) { return value; } // Ensure that the assigned values are not drafted if (value === peek(target.original, key)) { ensureShallowCopy(target); target.copy[key] = createDraft({ original: target.original[key], parentDraft: target, key: target.type === 1 /* DraftType.Array */ ? Number(key) : key, finalities: target.finalities, options: target.options, }); // !case: support for custom shallow copy function if (typeof markResult === 'function') { const subProxyDraft = getProxyDraft(target.copy[key]); ensureShallowCopy(subProxyDraft); // Trigger a custom shallow copy to update to a new copy markChanged(subProxyDraft); return subProxyDraft.copy; } return target.copy[key]; } if (isDraft(value)) { target.finalities.draftsCache.add(value); } return value; }, set(target, key, value) { var _a; if (target.type === 3 /* DraftType.Set */ || target.type === 2 /* DraftType.Map */) { throw new Error(`Map/Set draft does not support any property assignment.`); } let _key; if (target.type === 1 /* DraftType.Array */ && key !== 'length' && !(Number.isInteger((_key = Number(key))) && _key >= 0 && (key === 0 || _key === 0 || String(_key) === String(key)))) { throw new Error(`Only supports setting array indices and the 'length' property.`); } const desc = getDescriptor(latest(target), key); if (desc === null || desc === void 0 ? void 0 : desc.set) { // !case: cover the case of setter desc.set.call(target.proxy, value); return true; } const current = peek(latest(target), key); const currentProxyDraft = getProxyDraft(current); if (currentProxyDraft && isEqual(currentProxyDraft.original, value)) { // !case: ignore the case of assigning the original draftable value to a draft target.copy[key] = value; target.assignedMap = (_a = target.assignedMap) !== null && _a !== void 0 ? _a : new Map(); target.assignedMap.set(key, false); return true; } // !case: handle new props with value 'undefined' if (isEqual(value, current) && (value !== undefined || has(target.original, key))) return true; ensureShallowCopy(target); markChanged(target); if (has(target.original, key) && isEqual(value, target.original[key])) { // !case: handle the case of assigning the original non-draftable value to a draft target.assignedMap.delete(key); } else { target.assignedMap.set(key, true); } target.copy[key] = value; markFinalization(target, key, value, generatePatches); return true; }, has(target, key) { return key in latest(target); }, ownKeys(target) { return Reflect.ownKeys(latest(target)); }, getOwnPropertyDescriptor(target, key) { const source = latest(target); const descriptor = Reflect.getOwnPropertyDescriptor(source, key); if (!descriptor) return descriptor; return { writable: true, configurable: target.type !== 1 /* DraftType.Array */ || key !== 'length', enumerable: descriptor.enumerable, value: source[key], }; }, getPrototypeOf(target) { return Reflect.getPrototypeOf(target.original); }, setPrototypeOf() { throw new Error(`Cannot call 'setPrototypeOf()' on drafts`); }, defineProperty() { throw new Error(`Cannot call 'defineProperty()' on drafts`); }, deleteProperty(target, key) { var _a; if (target.type === 1 /* DraftType.Array */) { return proxyHandler.set.call(this, target, key, undefined, target.proxy); } if (peek(target.original, key) !== undefined || key in target.original) { // !case: delete an existing key ensureShallowCopy(target); markChanged(target); target.assignedMap.set(key, false); } else { target.assignedMap = (_a = target.assignedMap) !== null && _a !== void 0 ? _a : new Map(); // The original non-existent key has been deleted target.assignedMap.delete(key); } if (target.copy) delete target.copy[key]; return true; }, }; function createDraft(createDraftOptions) { const { original, parentDraft, key, finalities, options } = createDraftOptions; const type = getType(original); const proxyDraft = { type, finalized: false, parent: parentDraft, original, copy: null, proxy: null, finalities, options, // Mapping of draft Set items to their corresponding draft values. setMap: type === 3 /* DraftType.Set */ ? new Map(original.entries()) : undefined, }; // !case: undefined as a draft map key if (key || 'key' in createDraftOptions) { proxyDraft.key = key; } const { proxy, revoke } = Proxy.revocable(type === 1 /* DraftType.Array */ ? Object.assign([], proxyDraft) : proxyDraft, proxyHandler); finalities.revoke.push(revoke); proxyDraft.proxy = proxy; if (parentDraft) { const target = parentDraft; target.finalities.draft.push((patches, inversePatches) => { var _a, _b; const oldProxyDraft = getProxyDraft(proxy); // if target is a Set draft, `setMap` is the real Set copies proxy mapping. let copy = target.type === 3 /* DraftType.Set */ ? target.setMap : target.copy; const draft = get(copy, key); const proxyDraft = getProxyDraft(draft); if (proxyDraft) { // assign the updated value to the copy object let updatedValue = proxyDraft.original; if (proxyDraft.operated) { updatedValue = getValue(draft); } finalizeSetValue(proxyDraft); finalizePatches(proxyDraft, generatePatches, patches, inversePatches); if (target.options.enableAutoFreeze) { target.options.updatedValues = (_a = target.options.updatedValues) !== null && _a !== void 0 ? _a : new WeakMap(); target.options.updatedValues.set(updatedValue, proxyDraft.original); } // final update value set(copy, key, updatedValue); } // !case: handle the deleted key (_b = oldProxyDraft.callbacks) === null || _b === void 0 ? void 0 : _b.forEach((callback) => { callback(patches, inversePatches); }); }); } else { // !case: handle the root draft const target = getProxyDraft(proxy); target.finalities.draft.push((patches, inversePatches) => { finalizeSetValue(target); finalizePatches(target, generatePatches, patches, inversePatches); }); } return proxy; } internal.createDraft = createDraft; function finalizeDraft(result, returnedValue, patches, inversePatches, enableAutoFreeze) { var _a; const proxyDraft = getProxyDraft(result); const original = (_a = proxyDraft === null || proxyDraft === void 0 ? void 0 : proxyDraft.original) !== null && _a !== void 0 ? _a : result; const hasReturnedValue = !!returnedValue.length; if (proxyDraft === null || proxyDraft === void 0 ? void 0 : proxyDraft.operated) { while (proxyDraft.finalities.draft.length > 0) { const finalize = proxyDraft.finalities.draft.pop(); finalize(patches, inversePatches); } } const state = hasReturnedValue ? returnedValue[0] : proxyDraft ? proxyDraft.operated ? proxyDraft.copy : proxyDraft.original : result; if (proxyDraft) revokeProxy(proxyDraft); if (enableAutoFreeze) { deepFreeze(state, state, proxyDraft === null || proxyDraft === void 0 ? void 0 : proxyDraft.options.updatedValues); } return [ state, patches && hasReturnedValue ? [{ op: Operation.Replace, path: [], value: returnedValue[0] }] : patches, inversePatches && hasReturnedValue ? [{ op: Operation.Replace, path: [], value: original }] : inversePatches, ]; } function draftify(baseState, options) { var _a; const finalities = { draft: [], revoke: [], handledSet: new WeakSet(), draftsCache: new WeakSet(), }; let patches; let inversePatches; if (options.enablePatches) { patches = []; inversePatches = []; } const isMutable = ((_a = options.mark) === null || _a === void 0 ? void 0 : _a.call(options, baseState, dataTypes)) === dataTypes.mutable || !isDraftable(baseState, options); const draft = isMutable ? baseState : createDraft({ original: baseState, parentDraft: null, finalities, options, }); return [ draft, (returnedValue = []) => { const [finalizedState, finalizedPatches, finalizedInversePatches] = finalizeDraft(draft, returnedValue, patches, inversePatches, options.enableAutoFreeze); return (options.enablePatches ? [finalizedState, finalizedPatches, finalizedInversePatches] : finalizedState); }, ]; } function handleReturnValue(options) { const { rootDraft, value, useRawReturn = false, isRoot = true } = options; forEach(value, (key, item, source) => { const proxyDraft = getProxyDraft(item); // just handle the draft which is created by the same rootDraft if (proxyDraft && rootDraft && proxyDraft.finalities === rootDraft.finalities) { options.isContainDraft = true; const currentValue = proxyDraft.original; // final update value, but just handle return value if (source instanceof Set) { const arr = Array.from(source); source.clear(); arr.forEach((_item) => source.add(key === _item ? currentValue : _item)); } else { set(source, key, currentValue); } } else if (typeof item === 'object' && item !== null) { options.value = item; options.isRoot = false; handleReturnValue(options); } }); if (isRoot) { if (!options.isContainDraft) console.warn(`The return value does not contain any draft, please use 'rawReturn()' to wrap the return value to improve performance.`); if (useRawReturn) { console.warn(`The return value contains drafts, please don't use 'rawReturn()' to wrap the return value.`); } } } function getCurrent(target) { var _a; const proxyDraft = getProxyDraft(target); if (!isDraftable(target, proxyDraft === null || proxyDraft === void 0 ? void 0 : proxyDraft.options)) return target; const type = getType(target); if (proxyDraft && !proxyDraft.operated) return proxyDraft.original; let currentValue; function ensureShallowCopy() { currentValue = type === 2 /* DraftType.Map */ ? !isBaseMapInstance(target) ? new (Object.getPrototypeOf(target).constructor)(target) : new Map(target) : type === 3 /* DraftType.Set */ ? Array.from(proxyDraft.setMap.values()) : shallowCopy(target, proxyDraft === null || proxyDraft === void 0 ? void 0 : proxyDraft.options); } if (proxyDraft) { // It's a proxy draft, let's create a shallow copy eagerly proxyDraft.finalized = true; try { ensureShallowCopy(); } finally { proxyDraft.finalized = false; } } else { // It's not a proxy draft, let's use the target directly and let's see // lazily if we need to create a shallow copy currentValue = target; } forEach(currentValue, (key, value) => { if (proxyDraft && isEqual(get(proxyDraft.original, key), value)) return; const newValue = getCurrent(value); if (newValue !== value) { if (currentValue === target) ensureShallowCopy(); set(currentValue, key, newValue); } }); if (type === 3 /* DraftType.Set */) { const value = (_a = proxyDraft === null || proxyDraft === void 0 ? void 0 : proxyDraft.original) !== null && _a !== void 0 ? _a : currentValue; return !isBaseSetInstance(value) ? new (Object.getPrototypeOf(value).constructor)(currentValue) : new Set(currentValue); } return currentValue; } function current(target) { if (!isDraft(target)) { throw new Error(`current() is only used for Draft, parameter: ${target}`); } return getCurrent(target); } /** * `makeCreator(options)` to make a creator function. * * ## Example * * ```ts * import { makeCreator } from '../index'; * * const baseState = { foo: { bar: 'str' }, arr: [] }; * const create = makeCreator({ enableAutoFreeze: true }); * const state = create( * baseState, * (draft) => { * draft.foo.bar = 'str2'; * }, * ); * * expect(state).toEqual({ foo: { bar: 's