@ribajs/core
Version:
Core module of Riba.js
206 lines (160 loc) • 4.7 kB
text/typescript
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 };