@tanstack/db
Version:
A reactive client store for building super fast apps on sync
555 lines (554 loc) • 18 kB
JavaScript
import { isTemporal, deepEquals } from "./utils.js";
const draftCopies = /* @__PURE__ */ new WeakMap();
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 createArrayIterationHandler(methodName, methodFn, changeTracker, memoizedCreateChangeProxy) {
if (!CALLBACK_ITERATION_METHODS.has(methodName)) {
return void 0;
}
return function(...args) {
const callback = args[0];
if (typeof callback !== `function`) {
return methodFn.apply(changeTracker.copy_, args);
}
const getProxiedElement = (element, index) => {
if (isProxiableObject(element)) {
const nestedParent = {
tracker: changeTracker,
prop: String(index)
};
const { proxy: elementProxy } = memoizedCreateChangeProxy(
element,
nestedParent
);
return elementProxy;
}
return element;
};
const wrappedCallback = function(element, index, array) {
const proxiedElement = getProxiedElement(element, index);
return callback.call(this, proxiedElement, index, array);
};
if (methodName === `reduce` || methodName === `reduceRight`) {
const reduceCallback = function(accumulator, element, index, array) {
const proxiedElement = getProxiedElement(element, index);
return callback.call(this, accumulator, proxiedElement, index, array);
};
return methodFn.apply(changeTracker.copy_, [
reduceCallback,
...args.slice(1)
]);
}
const result = methodFn.apply(changeTracker.copy_, [
wrappedCallback,
...args.slice(1)
]);
if ((methodName === `find` || methodName === `findLast`) && result && typeof result === `object`) {
const foundIndex = changeTracker.copy_.indexOf(result);
if (foundIndex !== -1) {
return getProxiedElement(result, foundIndex);
}
}
if (methodName === `filter` && Array.isArray(result)) {
return result.map((element) => {
const originalIndex = changeTracker.copy_.indexOf(element);
if (originalIndex !== -1) {
return getProxiedElement(element, originalIndex);
}
return element;
});
}
return result;
};
}
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()) {
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) {
return new Date(obj.getTime());
}
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
if (Array.isArray(obj)) {
const arrayClone = [];
visited.set(obj, arrayClone);
obj.forEach((item, index) => {
arrayClone[index] = deepClone(item, visited);
});
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));
});
return clone2;
}
if (obj instanceof Set) {
const clone2 = /* @__PURE__ */ new Set();
visited.set(obj, clone2);
obj.forEach((value) => {
clone2.add(deepClone(value, visited));
});
return clone2;
}
if (isTemporal(obj)) {
return obj;
}
const clone = {};
visited.set(obj, clone);
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clone[key] = deepClone(
obj[key],
visited
);
}
}
const symbolProps = Object.getOwnPropertySymbols(obj);
for (const sym of symbolProps) {
clone[sym] = deepClone(
obj[sym],
visited
);
}
return clone;
}
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_: {},
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 deepEquals(
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 (!deepEquals(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 (!deepEquals(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_ = {};
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] ?? changeTracker.originalObject[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
);
}
const iterationHandler = createArrayIterationHandler(
methodName,
value,
changeTracker,
memoizedCreateChangeProxy
);
if (iterationHandler) {
return iterationHandler;
}
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 (!deepEquals(currentValue, value)) {
const originalValue = changeTracker.originalObject[prop];
const isRevertToOriginal = deepEquals(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_ = {};
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 (stringProp in dobj) {
const hadPropertyInOriginal = stringProp in changeTracker.originalObject;
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`
);
for (const key in changeTracker.copy_) {
const value = changeTracker.copy_[key];
const original = changeTracker.originalObject[key];
if ((changeTracker.assigned_[key] === true || mayHaveChangedAliases && changeTracker.copy_[key] !== changeTracker.copy_ && !deepEquals(
value instanceof Set ? Array.from(value) : value,
original instanceof Set ? Array.from(original) : original
)) && key in changeTracker.copy_) {
result[key] = changeTracker.copy_[key];
}
}
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());
}
function withArrayChangeTracking(targets, callback) {
const { proxies, getChanges } = createArrayChangeProxy(targets);
callback(proxies);
return deepClone(getChanges());
}
export {
createArrayChangeProxy,
createChangeProxy,
withArrayChangeTracking,
withChangeTracking
};
//# sourceMappingURL=proxy.js.map