"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeTracker = void 0; const VERSION = Symbol(`Version`); function cloneObject(obj) { // Preserving the prototype is out of scope for this library; don't try to // implement it, I won't merge it return Object.assign(Object.create(null), obj); } function cloneValue(value) { if (typeof value === `object` && value !== null) { if (Array.isArray(value)) { return value.slice(); } else if (value instanceof Set) { return new Set(value); } else if (value instanceof Map) { return new Map(value); } else { return cloneObject(value); } } else { return value; } } function cloneValueChecked(value, version) { if (typeof value === `object` && value !== null) { if (value[VERSION] === version) return value; const clone = cloneValue(value); clone[VERSION] = version; return clone; } else { return cloneValue(value); } } function cloneValueDeep(value, filter) { if (typeof value === `object` && value !== null) { if (Array.isArray(value)) { return value.map(subValue => { return cloneValueDeep(subValue, filter); }); } else if (value instanceof Set) { const clone = new Set(); for (const subValue of value.values()) clone.add(cloneValueDeep(subValue, filter)); return clone; } else if (value instanceof Map) { const clone = new Map(); for (const [key, subValue] of value) clone.set(key, cloneValueDeep(subValue, filter)); return clone; } else { const clone = cloneObject(value); for (const key of Object.keys(clone)) { if (filter !== true && !filter[key]) continue; const nextFilter = filter !== true ? filter[key] : true; // @ts-expect-error clone[key] = cloneValueDeep(clone[key], nextFilter); } return clone; } } else { return value; } } function compareValuesDeep(a, b) { if (a === b) { return true; } else if ((a == null) !== (b == null)) { return false; } else if (Array.isArray(a)) { if (!Array.isArray(b)) return false; if (a.length !== b.length) return false; for (let t = 0, T = a.length; t < T; ++t) if (!compareValuesDeep(a[t], b[t])) return false; return true; } else if (a instanceof Set) { if (!(b instanceof Set)) return false; if (a.size !== b.size) return false; for (const key of a.entries()) if (!b.has(key)) return false; return true; } else if (a instanceof Map) { if (!(b instanceof Map)) return false; if (a.size !== b.size) return false; for (const [key, value] of a.entries()) if (!compareValuesDeep(value, b.get(key))) return false; return true; } else if (a.constructor === Object) { if (b.constructor !== Object) return false; const aKeys = Object.keys(a); const bKeys = Object.keys(b); if (aKeys.length !== bKeys.length) return false; for (let t = 0, T = aKeys.length; t < T; ++t) if (aKeys[t] !== bKeys[t]) return false; for (let t = 0, T = aKeys.length; t < T; ++t) if (!compareValuesDeep(a[aKeys[t]], b[bKeys[t]])) return false; return true; } else { return false; } } const proxyHandlerSet = (version, filter, ensureCloning) => ({ get(source, prop) { switch (prop) { case `clear`: return () => { const clonedParent = ensureCloning(); clonedParent.clear(); source.clear(); }; case `delete`: return (key) => { const clonedParent = ensureCloning(); clonedParent.delete(key); source.delete(key); }; case `add`: return (key) => { const clonedParent = ensureCloning(); clonedParent.add(key); source.add(key); }; // @ts-expect-error default: return source[prop]; } }, }); const proxyHandlerMap = (version, filter, ensureCloning) => ({ get(source, prop) { switch (prop) { case `clear`: return () => { const clonedParent = ensureCloning(); clonedParent.clear(); source.clear(); }; case `delete`: return (key) => { const clonedParent = ensureCloning(); clonedParent.delete(key); source.delete(key); }; case `set`: return (key, value) => { const clonedParent = ensureCloning(); clonedParent.set(key, value); source.set(key, value); }; case `get`: return (key) => { const value = source.get(key); return makeValueObservable(value, version, filter, () => { const clonedParent = ensureCloning(); const immutableValue = clonedParent.get(key); const clonedValue = cloneValueChecked(immutableValue, version); clonedParent.set(key, clonedValue); return clonedParent; }); }; // @ts-expect-error default: return source[prop]; } }, }); const proxyHandlerObject = (version, filter, ensureCloning) => ({ get(source, prop) { // @ts-expect-error const value = source[prop]; // Typescript doesn't allow symbol in its index types if (typeof prop === `symbol`) return value; if (filter !== true && !filter[prop]) return value; const nextFilter = filter !== true ? filter[prop] : true; return makeValueObservable(value, version, nextFilter, () => { const clonedParent = ensureCloning(); // @ts-expect-error const immutableValue = clonedParent[prop]; const clonedValue = cloneValueChecked(immutableValue, version); // @ts-expect-error clonedParent[prop] = clonedValue; return clonedValue; }); }, set(source, prop, value) { // @ts-expect-error const currentValue = source[prop]; if (!compareValuesDeep(currentValue, value)) { // We ensure that our parent is cloned, then assign the new value into it const clonedParent = ensureCloning(); // @ts-expect-error clonedParent[prop] = cloneValueDeep(value, filter); } // @ts-expect-error source[prop] = value; return true; }, }); function makeValueObservable(value, version, filter, ensureCloning) { if (typeof value === `object` && value !== null) { if (value instanceof Set) { return new Proxy(value, proxyHandlerSet(version, filter, ensureCloning)); } else if (value instanceof Map) { return new Proxy(value, proxyHandlerMap(version, filter, ensureCloning)); } else { return new Proxy(value, proxyHandlerObject(version, filter, ensureCloning)); } } else { return value; } } function makeTracker(value, filter = true) { const tracker = { immutable: cloneValueDeep(value, filter), open(cb) { // A value guaranteed to be different from everything except itself const version = {}; cb(makeValueObservable(value, version, filter, () => { tracker.immutable = cloneValueChecked(tracker.immutable, version); return tracker.immutable; })); return tracker.immutable; }, }; return tracker; } exports.makeTracker = makeTracker;