UNPKG

orange-orm

Version:

Object Relational Mapper

247 lines (199 loc) 6.79 kB
/* eslint-disable unicorn/prefer-spread */ import {TARGET, UNSUBSCRIBE} from './lib/constants.js'; import {isBuiltinWithMutableMethods, isBuiltinWithoutMutableMethods} from './lib/is-builtin.js'; import path from './lib/path.js'; import isSymbol from './lib/is-symbol.js'; import isIterator from './lib/is-iterator.js'; import wrapIterator from './lib/wrap-iterator.js'; import ignoreProperty from './lib/ignore-property.js'; import Cache from './lib/cache.js'; import SmartClone from './lib/smart-clone/smart-clone.js'; const defaultOptions = { equals: Object.is, isShallow: false, pathAsArray: false, ignoreSymbols: false, ignoreUnderscores: false, ignoreDetached: false, details: false, }; const onChange = (object, onChange, options = {}) => { options = { ...defaultOptions, ...options, }; const proxyTarget = Symbol('ProxyTarget'); const {equals, isShallow, ignoreDetached, details} = options; const cache = new Cache(equals); const hasOnValidate = typeof options.onValidate === 'function'; const smartClone = new SmartClone(hasOnValidate); // eslint-disable-next-line max-params const validate = (target, property, value, previous, applyData) => !hasOnValidate || smartClone.isCloning || options.onValidate(path.concat(cache.getPath(target), property), value, previous, applyData) === true; const handleChangeOnTarget = (target, property, value, previous) => { if ( !ignoreProperty(cache, options, property) && !(ignoreDetached && cache.isDetached(target, object)) ) { handleChange(cache.getPath(target), property, value, previous); } }; // eslint-disable-next-line max-params const handleChange = (changePath, property, value, previous, applyData) => { if (smartClone.isCloning) { smartClone.update(changePath, property, previous); } else { onChange(path.concat(changePath, property), value, previous, applyData); } }; const getProxyTarget = value => value ? (value[proxyTarget] || value) : value; const prepareValue = (value, target, property, basePath) => { if ( isBuiltinWithoutMutableMethods(value) || property === 'constructor' || (isShallow && !SmartClone.isHandledMethod(target, property)) || ignoreProperty(cache, options, property) || cache.isGetInvariant(target, property) || (ignoreDetached && cache.isDetached(target, object)) ) { return value; } if (basePath === undefined) { basePath = cache.getPath(target); } return cache.getProxy(value, path.concat(basePath, property), handler, proxyTarget); }; const handler = { get(target, property, receiver) { if (isSymbol(property)) { if (property === proxyTarget || property === TARGET) { return target; } if ( property === UNSUBSCRIBE && !cache.isUnsubscribed && cache.getPath(target).length === 0 ) { cache.unsubscribe(); return target; } } const value = isBuiltinWithMutableMethods(target) ? Reflect.get(target, property) : Reflect.get(target, property, receiver); return prepareValue(value, target, property); }, set(target, property, value, receiver) { value = getProxyTarget(value); const reflectTarget = target[proxyTarget] || target; const previous = reflectTarget[property]; if (equals(previous, value) && property in target) { return true; } const isValid = validate(target, property, value, previous); if ( isValid && cache.setProperty(reflectTarget, property, value, receiver, previous) ) { handleChangeOnTarget(target, property, target[property], previous); return true; } return !isValid; }, defineProperty(target, property, descriptor) { if (!cache.isSameDescriptor(descriptor, target, property)) { const previous = target[property]; if ( validate(target, property, descriptor.value, previous) && cache.defineProperty(target, property, descriptor, previous) ) { handleChangeOnTarget(target, property, descriptor.value, previous); } } return true; }, deleteProperty(target, property) { if (!Reflect.has(target, property)) { return true; } const previous = Reflect.get(target, property); const isValid = validate(target, property, undefined, previous); if ( isValid && cache.deleteProperty(target, property, previous) ) { handleChangeOnTarget(target, property, undefined, previous); return true; } return !isValid; }, apply(target, thisArg, argumentsList) { const thisProxyTarget = thisArg[proxyTarget] || thisArg; if (cache.isUnsubscribed) { return Reflect.apply(target, thisProxyTarget, argumentsList); } if ( (details === false || (details !== true && !details.includes(target.name))) && SmartClone.isHandledType(thisProxyTarget) ) { let applyPath = path.initial(cache.getPath(target)); const isHandledMethod = SmartClone.isHandledMethod(thisProxyTarget, target.name); smartClone.start(thisProxyTarget, applyPath, argumentsList); let result = Reflect.apply( target, smartClone.preferredThisArg(target, thisArg, thisProxyTarget), isHandledMethod ? argumentsList.map(argument => getProxyTarget(argument)) : argumentsList, ); const isChanged = smartClone.isChanged(thisProxyTarget, equals); const previous = smartClone.stop(); if (SmartClone.isHandledType(result) && isHandledMethod) { if (thisArg instanceof Map && target.name === 'get') { applyPath = path.concat(applyPath, argumentsList[0]); } result = cache.getProxy(result, applyPath, handler); } if (isChanged) { const applyData = { name: target.name, args: argumentsList, result, }; const changePath = smartClone.isCloning ? path.initial(applyPath) : applyPath; const property = smartClone.isCloning ? path.last(applyPath) : ''; if (validate(path.get(object, changePath), property, thisProxyTarget, previous, applyData)) { handleChange(changePath, property, thisProxyTarget, previous, applyData); } else { smartClone.undo(thisProxyTarget); } } if ( (thisArg instanceof Map || thisArg instanceof Set) && isIterator(result) ) { return wrapIterator(result, target, thisArg, applyPath, prepareValue); } return result; } return Reflect.apply(target, thisArg, argumentsList); }, }; const proxy = cache.getProxy(object, options.pathAsArray ? [] : '', handler); onChange = onChange.bind(proxy); if (hasOnValidate) { options.onValidate = options.onValidate.bind(proxy); } return proxy; }; onChange.target = proxy => (proxy && proxy[TARGET]) || proxy; onChange.unsubscribe = proxy => proxy[UNSUBSCRIBE] || proxy; export default onChange;