UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

580 lines (579 loc) 19.4 kB
import { isTemporal, deepEquals } from "./utils.js"; const draftCopies = /* @__PURE__ */ new WeakMap(); function defineDataProperty(object, key, value) { if (key !== `__proto__`) { object[key] = value; return; } Object.defineProperty(object, key, { value, enumerable: true, configurable: true, writable: true }); } function unwrapDraft(value) { return value !== null && typeof value === `object` ? draftCopies.get(value) ?? value : value; } const CALLBACK_ITERATION_METHODS = /* @__PURE__ */ new Set([ `find`, `findLast`, `findIndex`, `findLastIndex`, `filter`, `map`, `flatMap`, `forEach`, `some`, `every`, `reduce`, `reduceRight` ]); const ARRAY_MODIFYING_METHODS = /* @__PURE__ */ new Set([ `pop`, `push`, `shift`, `unshift`, `splice`, `sort`, `reverse`, `fill`, `copyWithin` ]); const MAP_SET_ITERATOR_METHODS = /* @__PURE__ */ new Set([ `entries`, `keys`, `values`, `forEach` ]); function isProxiableObject(value) { return value !== null && typeof value === `object` && !(value instanceof Date) && !(value instanceof RegExp) && !isTemporal(value); } function createArrayIteratorHandler(changeTracker, memoizedCreateChangeProxy) { return function() { const array = changeTracker.copy_; let index = 0; return { next() { if (index >= array.length) { return { done: true, value: void 0 }; } const element = array[index]; let proxiedElement = element; if (isProxiableObject(element)) { const nestedParent = { tracker: changeTracker, prop: String(index) }; const { proxy: elementProxy } = memoizedCreateChangeProxy( element, nestedParent ); proxiedElement = elementProxy; } index++; return { done: false, value: proxiedElement }; }, [Symbol.iterator]() { return this; } }; }; } function createModifyingMethodHandler(methodFn, changeTracker, markChanged) { return function(...args) { const result = methodFn.apply(changeTracker.copy_, args); markChanged(changeTracker); return result; }; } function createMapSetIteratorHandler(methodName, prop, changeTracker, collectionProxy, memoizedCreateChangeProxy) { if (!MAP_SET_ITERATOR_METHODS.has(methodName) && prop !== Symbol.iterator) { return void 0; } return (...args) => { const copy = changeTracker.copy_; const isMap = copy instanceof Map; if (isMap && methodName === `keys`) return copy.keys(); const track = (value) => isProxiableObject(value) ? memoizedCreateChangeProxy(value, { tracker: changeTracker, prop: ``, retainIdentity: true }).proxy : value; if (methodName === `forEach`) { const callback = args[0]; if (typeof callback !== `function`) throw new TypeError(`forEach callback must be a function`); return copy.forEach((value, key) => { const tracked = track(value); callback.call(args[1], tracked, isMap ? key : tracked, collectionProxy); }); } const entries = copy.entries(); const pairs = methodName === `entries` || isMap && prop === Symbol.iterator; return { next() { const result = entries.next(); if (result.done) return result; const [key, value] = result.value; const tracked = track(value); return { done: false, value: pairs ? [isMap ? key : tracked, tracked] : tracked }; }, [Symbol.iterator]() { return this; } }; }; } function deepClone(obj, visited = /* @__PURE__ */ new WeakMap(), detach = false) { obj = unwrapDraft(obj); if (obj === null || obj === void 0) { return obj; } if (typeof obj !== `object`) { return obj; } if (visited.has(obj)) { return visited.get(obj); } if (obj instanceof Date) { const clone2 = new Date(obj.getTime()); visited.set(obj, clone2); return clone2; } if (obj instanceof RegExp) { const clone2 = new RegExp(obj.source, obj.flags); clone2.lastIndex = obj.lastIndex; visited.set(obj, clone2); return clone2; } if (obj instanceof URL) { const clone2 = new URL(obj.href); visited.set(obj, clone2); return clone2; } if (Array.isArray(obj)) { const arrayClone = new Array(obj.length); visited.set(obj, arrayClone); obj.forEach((item, index) => { arrayClone[index] = deepClone(item, visited, detach); }); return arrayClone; } if (ArrayBuffer.isView(obj) && !(obj instanceof DataView)) { const TypedArrayConstructor = Object.getPrototypeOf(obj).constructor; const clone2 = new TypedArrayConstructor( obj.length ); visited.set(obj, clone2); for (let i = 0; i < obj.length; i++) { clone2[i] = obj[i]; } return clone2; } if (obj instanceof Map) { const clone2 = /* @__PURE__ */ new Map(); visited.set(obj, clone2); obj.forEach((value, key) => { clone2.set(key, deepClone(value, visited, detach)); }); return clone2; } if (obj instanceof Set) { const clone2 = /* @__PURE__ */ new Set(); visited.set(obj, clone2); obj.forEach((value) => { clone2.add(deepClone(value, visited, detach)); }); return clone2; } if (isTemporal(obj)) { return obj; } if (detach) { const prototype = Object.getPrototypeOf(obj); if (prototype !== Object.prototype && prototype !== null) return obj; } const clone = {}; visited.set(obj, clone); for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { defineDataProperty( clone, key, deepClone( obj[key], visited, detach ) ); } } const symbolProps = Object.getOwnPropertySymbols(obj); for (const sym of symbolProps) { clone[sym] = deepClone( obj[sym], visited, detach ); } return clone; } function draftValuesEqual(left, right, paired = /* @__PURE__ */ new Map()) { if (left === right) return true; if (left === null || right === null || typeof left !== `object` || typeof right !== `object`) return deepEquals(left, right); if (paired.has(left)) return paired.get(left) === right; paired.set(left, right); try { if (left instanceof Set || right instanceof Set) { if (!(left instanceof Set) || !(right instanceof Set) || left.size !== right.size) return false; const values = [...right]; return [...left].every( (value, index) => draftValuesEqual(value, values[index], paired) ); } if (left instanceof RegExp || right instanceof RegExp) return left instanceof RegExp && right instanceof RegExp && deepEquals(left, right) && left.lastIndex === right.lastIndex; if (left instanceof Map || right instanceof Map) { if (!(left instanceof Map) || !(right instanceof Map) || left.size !== right.size) return false; const entries = [...right]; return [...left].every( ([key, value], index) => (key === entries[index][0] || Object.is(key, entries[index][0])) && draftValuesEqual(value, entries[index][1], paired) ); } if (left instanceof Date || right instanceof Date || ArrayBuffer.isView(left) || ArrayBuffer.isView(right) || isTemporal(left) || isTemporal(right)) return deepEquals(left, right); if (Array.isArray(left) !== Array.isArray(right)) return false; if (Array.isArray(left) && left.length !== right.length) return false; const keys = (value) => Reflect.ownKeys(value).filter( (key) => Object.prototype.propertyIsEnumerable.call(value, key) ); const leftKeys = keys(left); if (leftKeys.length !== keys(right).length) return false; return leftKeys.every( (key) => Object.prototype.propertyIsEnumerable.call(right, key) && draftValuesEqual( left[key], right[key], paired ) ); } finally { paired.delete(left); } } function createChangeProxy(target, parent) { const changeProxyCache = /* @__PURE__ */ new Map(); function memoizedCreateChangeProxy(innerTarget, innerParent) { if (changeProxyCache.has(innerTarget)) { return changeProxyCache.get(innerTarget); } else { const changeProxy = createChangeProxy(innerTarget, innerParent); changeProxyCache.set(innerTarget, changeProxy); return changeProxy; } } const proxyCache = /* @__PURE__ */ new Map(); const valueCopies = parent?.tracker.valueCopies ?? /* @__PURE__ */ new WeakMap(); const changeTracker = { valueCopies, copy_: parent ? valueCopies.get(target) ?? target : deepClone(target, valueCopies), originalObject: deepClone(target), modified: false, assigned_: /* @__PURE__ */ Object.create(null), parent, target // Store reference to the target object }; function markChanged(state) { if (!state.modified) { state.modified = true; } if (state.parent) { if (!state.parent.retainIdentity && state.parent.tracker.copy_[state.parent.prop] === state.copy_) { state.parent.tracker.assigned_[state.parent.prop] = true; } markChanged(state.parent.tracker); } } function checkIfReverted(state) { if (state.copy_ instanceof Map || state.copy_ instanceof Set) { return draftValuesEqual( Array.from(state.copy_), Array.from( state.originalObject ) ); } if (Object.keys(state.assigned_).length === 0 && Object.getOwnPropertySymbols(state.assigned_).length === 0) { return true; } for (const prop in state.assigned_) { if (state.assigned_[prop] === true) { const currentValue = state.copy_[prop]; const originalValue = state.originalObject[prop]; if (!draftValuesEqual(currentValue, originalValue)) { return false; } } else if (state.assigned_[prop] === false) { return false; } } const symbolProps = Object.getOwnPropertySymbols(state.assigned_); for (const sym of symbolProps) { if (state.assigned_[sym] === true) { const currentValue = state.copy_[sym]; const originalValue = state.originalObject[sym]; if (!draftValuesEqual(currentValue, originalValue)) { return false; } } else if (state.assigned_[sym] === false) { return false; } } return true; } function checkParentStatus(parentState) { const isReverted = checkIfReverted(parentState); if (isReverted) { parentState.modified = false; parentState.assigned_ = /* @__PURE__ */ Object.create(null); if (parentState.parent) { checkParentStatus(parentState.parent.tracker); } } } function createObjectProxy(obj) { if (proxyCache.has(obj)) { return proxyCache.get(obj); } const proxy2 = new Proxy(obj, { get(ptarget, prop, receiver) { const value = changeTracker.copy_[prop]; const desc = Object.getOwnPropertyDescriptor(ptarget, prop); if (desc?.get) { return value; } if (typeof value === `function`) { if (Array.isArray(ptarget)) { const methodName = prop.toString(); if (ARRAY_MODIFYING_METHODS.has(methodName)) { return createModifyingMethodHandler( value, changeTracker, markChanged ); } if (CALLBACK_ITERATION_METHODS.has(methodName) || methodName === `values` || methodName === `entries`) { return value.bind(receiver); } if (prop === Symbol.iterator) { return createArrayIteratorHandler( changeTracker, memoizedCreateChangeProxy ); } } if (ptarget instanceof Map || ptarget instanceof Set) { const methodName = prop.toString(); const resolveValue = (entry) => { const raw = unwrapDraft(entry); return raw !== null && typeof raw === `object` ? valueCopies.get(raw) ?? raw : raw; }; if (methodName === `has` || methodName === `delete` || methodName === `add` || methodName === `set`) { return (...args) => { if (ptarget instanceof Set) args[0] = resolveValue(args[0]); else if (methodName === `set`) args[1] = resolveValue(args[1]); const result = value.apply(ptarget, args); if (methodName !== `has`) markChanged(changeTracker); return result === ptarget ? receiver : result; }; } if (ptarget instanceof Map && methodName === `get`) { return (key) => { const entry = ptarget.get(key); return isProxiableObject(entry) ? memoizedCreateChangeProxy(entry, { tracker: changeTracker, prop: ``, retainIdentity: true }).proxy : entry; }; } if (methodName === `clear`) { return createModifyingMethodHandler( value, changeTracker, markChanged ); } const iteratorHandler = createMapSetIteratorHandler( methodName, prop, changeTracker, receiver, memoizedCreateChangeProxy ); if (iteratorHandler) { return iteratorHandler; } } return value.bind(ptarget); } if (isProxiableObject(value)) { const nestedParent = { tracker: changeTracker, prop: String(prop) }; const { proxy: nestedProxy } = memoizedCreateChangeProxy( value, nestedParent ); proxyCache.set(value, nestedProxy); return nestedProxy; } return value; }, set(_sobj, prop, value) { const currentValue = changeTracker.copy_[prop]; if (!Object.hasOwn(changeTracker.copy_, prop) || !draftValuesEqual(currentValue, value)) { const originalValue = changeTracker.originalObject[prop]; const isRevertToOriginal = Object.hasOwn(changeTracker.originalObject, prop) && draftValuesEqual(value, originalValue); if (isRevertToOriginal) { delete changeTracker.assigned_[prop.toString()]; changeTracker.copy_[prop] = deepClone(originalValue); const allReverted = checkIfReverted(changeTracker); if (allReverted) { changeTracker.modified = false; changeTracker.assigned_ = /* @__PURE__ */ Object.create(null); if (parent) { checkParentStatus(parent.tracker); } } else { changeTracker.modified = true; } } else { changeTracker.copy_[prop] = value; changeTracker.assigned_[prop.toString()] = true; markChanged(changeTracker); } } return true; }, defineProperty(ptarget, prop, descriptor) { const result = Reflect.defineProperty(ptarget, prop, descriptor); if (result && `value` in descriptor) { changeTracker.copy_[prop] = deepClone(descriptor.value); changeTracker.assigned_[prop.toString()] = true; markChanged(changeTracker); } return result; }, getOwnPropertyDescriptor(ptarget, prop) { return Reflect.getOwnPropertyDescriptor(ptarget, prop); }, preventExtensions(ptarget) { return Reflect.preventExtensions(ptarget); }, isExtensible(ptarget) { return Reflect.isExtensible(ptarget); }, deleteProperty(dobj, prop) { const stringProp = typeof prop === `symbol` ? prop.toString() : prop; if (Object.hasOwn(dobj, prop)) { const hadPropertyInOriginal = Object.hasOwn( changeTracker.originalObject, prop ); const result = Reflect.deleteProperty(dobj, prop); if (result) { if (!hadPropertyInOriginal) { delete changeTracker.assigned_[stringProp]; if (Object.keys(changeTracker.assigned_).length === 0 && Object.getOwnPropertySymbols(changeTracker.assigned_).length === 0) { changeTracker.modified = false; } else { changeTracker.modified = true; } } else { changeTracker.assigned_[stringProp] = false; markChanged(changeTracker); } } return result; } return true; } }); proxyCache.set(obj, proxy2); draftCopies.set(proxy2, changeTracker.copy_); return proxy2; } const proxy = createObjectProxy(changeTracker.copy_); return { proxy, getChanges: () => { if (!changeTracker.modified) { return {}; } if (typeof changeTracker.copy_ !== `object` || Array.isArray(changeTracker.copy_)) { return changeTracker.copy_; } if (Object.keys(changeTracker.assigned_).length === 0) { return changeTracker.copy_; } const result = {}; const mayHaveChangedAliases = Object.keys(changeTracker.assigned_).some( (key) => typeof changeTracker.copy_[key] === `object` || typeof changeTracker.originalObject[key] === `object` ); const pairedRoots = /* @__PURE__ */ new Map([ [changeTracker.copy_, changeTracker.originalObject] ]); for (const key in changeTracker.copy_) { const value = changeTracker.copy_[key]; const original = changeTracker.originalObject[key]; if ((changeTracker.assigned_[key] === true || mayHaveChangedAliases && !draftValuesEqual( value instanceof Set ? Array.from(value) : value, original instanceof Set ? Array.from(original) : original, pairedRoots )) && key in changeTracker.copy_) { defineDataProperty(result, key, changeTracker.copy_[key]); } } for (const key of Object.keys(changeTracker.assigned_)) { if (changeTracker.assigned_[key] === false) { defineDataProperty(result, key, void 0); } } return result; } }; } function createArrayChangeProxy(targets) { const proxiesWithChanges = targets.map((target) => createChangeProxy(target)); return { proxies: proxiesWithChanges.map((p) => p.proxy), getChanges: () => proxiesWithChanges.map((p) => p.getChanges()) }; } function withChangeTracking(target, callback) { const { proxy, getChanges } = createChangeProxy(target); callback(proxy); return deepClone(getChanges(), void 0, true); } function withArrayChangeTracking(targets, callback) { const { proxies, getChanges } = createArrayChangeProxy(targets); callback(proxies); return deepClone(getChanges(), void 0, true); } export { createArrayChangeProxy, createChangeProxy, withArrayChangeTracking, withChangeTracking }; //# sourceMappingURL=proxy.js.map