UNPKG

@ribajs/core

Version:

Core module of Riba.js

206 lines (160 loc) 4.7 kB
import { Adapter, AdapterFunction, Ref, ObserverSyncCallback, RVArray, } from "../types/index.js"; export class DotAdapter implements Adapter { public static ARRAY_METHODS = [ "push", "pop", "shift", "unshift", "sort", "reverse", "splice", ]; public name = "."; public counter = 0; public weakmap: any = {}; public weakReference(obj: any) { if (!Object.hasOwnProperty.bind(obj)("__rv")) { const id = this.counter++; Object.defineProperty(obj, "__rv", { value: id, }); } if (!this.weakmap[obj.__rv]) { this.weakmap[obj.__rv] = { callbacks: {}, }; } return this.weakmap[obj.__rv]; } public cleanupWeakReference(ref: Ref, id: number) { if (!Object.keys(ref.callbacks).length) { if (!(ref.pointers && Object.keys(ref.pointers).length)) { delete this.weakmap[id]; } } } public stubFunction(obj: any, fn: string) { const original = obj[fn]; const map = this.weakReference(obj); const weakmap = this.weakmap; obj[fn] = (...args: any[]): AdapterFunction => { const response = original.apply(obj, args); Object.keys(map.pointers).forEach((r) => { const k = map.pointers[r]; if (weakmap[r]) { if (Array.isArray(weakmap[r].callbacks[k])) { weakmap[r].callbacks[k].forEach( (callback: ObserverSyncCallback) => { callback.sync(); }, ); } } }); return response; }; } public observeMutations(obj: any, ref: string, keypath: string) { if (Array.isArray(obj)) { const map = this.weakReference(obj); if (!map.pointers) { map.pointers = {}; DotAdapter.ARRAY_METHODS.forEach((fn) => { this.stubFunction(obj, fn); }); } if (!map.pointers[ref]) { map.pointers[ref] = []; } if (map.pointers[ref].indexOf(keypath) === -1) { map.pointers[ref].push(keypath); } } } public unobserveMutations(obj: RVArray, ref: string, keypath: string) { if (Array.isArray(obj) && obj.__rv != null) { const map = this.weakmap[obj.__rv]; if (map) { const pointers = map.pointers[ref]; if (pointers) { const idx = pointers.indexOf(keypath); if (idx > -1) { pointers.splice(idx, 1); } if (!pointers.length) { delete map.pointers[ref]; } this.cleanupWeakReference(map, obj.__rv); } } } } public observe(obj: any, keypath: string, callback: ObserverSyncCallback) { let value: any; const callbacks = this.weakReference(obj).callbacks; if (!callbacks[keypath]) { callbacks[keypath] = []; const desc = Object.getOwnPropertyDescriptor(obj, keypath); if (!desc || !(desc.get || desc.set || !desc.configurable)) { value = obj[keypath]; Object.defineProperty(obj, keypath, { enumerable: true, get: () => { return value; }, set: (newValue) => { if (newValue !== value) { this.unobserveMutations(value, obj.__rv, keypath); value = newValue; const map = this.weakmap[obj.__rv]; if (map) { const _callbacks = map.callbacks[keypath]; if (_callbacks) { _callbacks.forEach((cb: ObserverSyncCallback) => { cb.sync(); }); } this.observeMutations(newValue, obj.__rv, keypath); } } }, }); } } if (callbacks[keypath].indexOf(callback) === -1) { callbacks[keypath].push(callback); } this.observeMutations(obj[keypath], obj.__rv, keypath); } public unobserve(obj: any, keypath: string, callback: ObserverSyncCallback) { const map = this.weakmap[obj.__rv]; if (map) { const callbacks = map.callbacks[keypath]; if (callbacks) { const idx = callbacks.indexOf(callback); if (idx > -1) { callbacks.splice(idx, 1); if (!callbacks.length) { delete map.callbacks[keypath]; this.unobserveMutations(obj[keypath], obj.__rv, keypath); } } this.cleanupWeakReference(map, obj.__rv); } } } public get(obj: any, keypath: string) { return obj[keypath]; } public set(obj: any, keypath: string, value: any) { obj[keypath] = value; } } const dotAdapter = new DotAdapter(); export { dotAdapter };