Comparing version 1.0.8 to 1.0.9
import { Subscription, Observable } from "rxjs"; | ||
import { observedObjects } from "./observedObjectsSymbol"; | ||
declare const customTraps: unique symbol; | ||
declare const observedObjects: unique symbol; | ||
export declare type ObservableObject<T> = _ObservableObject<T> & T; | ||
@@ -9,2 +10,3 @@ interface ObservableConstructor { | ||
private [observedObjects]; | ||
private [customTraps]; | ||
constructor(from?: T, optHandlers?: ProxyHandler<any>); | ||
@@ -11,0 +13,0 @@ /** |
132
index.js
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var _a; | ||
var _a, _b; | ||
const rxjs_1 = require("rxjs"); | ||
const observedObjectsSymbol_1 = require("./observedObjectsSymbol"); | ||
const debug = require("debug"); | ||
const roxeDebug = debug("roxe"); | ||
const customTraps = Symbol("_customTraps"); | ||
const observedObjects = Symbol("_observedObjects"); | ||
class _ObservableObject { | ||
constructor(from = {}, optHandlers = {}) { | ||
this[_a] = {}; | ||
let setCustomHandler; | ||
let getCustomHandler; | ||
if (optHandlers && Object.keys(optHandlers).length) { | ||
if (optHandlers.set) { | ||
setCustomHandler = optHandlers.set; | ||
delete optHandlers.set; | ||
} | ||
if (optHandlers.get) { | ||
getCustomHandler = optHandlers.get; | ||
} | ||
this[_b] = {}; | ||
if (optHandlers && optHandlers.set) { | ||
this[customTraps].set = optHandlers.set; | ||
delete optHandlers.set; | ||
} | ||
if (optHandlers && optHandlers.get) { | ||
this[customTraps].get = optHandlers.get; | ||
} | ||
const handlers = Object.assign(optHandlers, { | ||
@@ -26,13 +24,10 @@ // Note for future: leave receiver as parameter even if not used | ||
set: (obj, prop, value, receiver, ...args) => { | ||
let notificationChain; | ||
// Creating the chain of properties that will be notified | ||
const shouldBuildNotificationChain = ((obj[prop] && typeof obj[prop] === "object" && !value) || | ||
(value && typeof value === "object")); | ||
const notificationChain = Object.assign({ [[...(args || []), prop].join(".")]: value }, (!shouldBuildNotificationChain && {} || | ||
buildNotificationChain(obj[prop], value, ...args, prop))); | ||
if (typeof value === "object") { | ||
// Creating the chain of properties that will be notified | ||
notificationChain = Object.assign({ | ||
[prop]: value, | ||
}, buildNotificationChain(value, prop)); | ||
if (setCustomHandler) { | ||
let setResult = setCustomHandler(obj, prop, value, receiver); | ||
if (setResult === false) { | ||
return setResult; | ||
} | ||
if (this[customTraps].set && this[customTraps].set(obj, prop, value, receiver) === false) { | ||
return false; | ||
} | ||
@@ -72,13 +67,6 @@ /* | ||
} | ||
if (setCustomHandler) { | ||
let setResult = setCustomHandler(obj, prop, value, receiver); | ||
if (setResult === false) { | ||
return setResult; | ||
} | ||
if (this[customTraps].set && this[customTraps].set(obj, prop, value, receiver) === false) { | ||
return false; | ||
} | ||
obj[prop] = value; | ||
const elementKey = args.length ? [...args, prop].join(".") : prop; | ||
notificationChain = { | ||
[elementKey]: value | ||
}; | ||
} | ||
@@ -88,4 +76,4 @@ Object.keys(notificationChain).forEach((keyPath) => { | ||
// We want both single properties an complex objects to be notified when edited | ||
if (this[observedObjectsSymbol_1.observedObjects][keyPath]) { | ||
this[observedObjectsSymbol_1.observedObjects][keyPath].next(value); | ||
if (this[observedObjects][keyPath]) { | ||
this[observedObjects][keyPath].next(value); | ||
} | ||
@@ -96,7 +84,7 @@ }); | ||
get: bindLast((target, prop, receiver, customGetter) => { | ||
if (customGetter !== undefined && !(prop in _ObservableObject.prototype)) { | ||
return customGetter(target, prop, receiver); | ||
if (!customGetter || prop in _ObservableObject.prototype) { | ||
return Reflect.get(target, prop, receiver); | ||
} | ||
return Reflect.get(target, prop, receiver); | ||
}, getCustomHandler) | ||
return customGetter(target, prop, receiver); | ||
}, this[customTraps].get) | ||
}); | ||
@@ -113,6 +101,6 @@ return new Proxy(Object.assign(this, buildInitialProxyChain(from, handlers)), handlers); | ||
observe(prop) { | ||
if (!this[observedObjectsSymbol_1.observedObjects][prop]) { | ||
this[observedObjectsSymbol_1.observedObjects][prop] = new rxjs_1.Subject(); | ||
if (!this[observedObjects][prop]) { | ||
this[observedObjects][prop] = new rxjs_1.Subject(); | ||
} | ||
return this[observedObjectsSymbol_1.observedObjects][prop].asObservable(); | ||
return this[observedObjects][prop].asObservable(); | ||
} | ||
@@ -135,29 +123,26 @@ /** | ||
snapshot(path) { | ||
let snapshot; | ||
let firstUnavailableKey = ""; | ||
if (path && typeof path === "string") { | ||
snapshot = path.split(".").reduce((acc, current) => { | ||
if (!(acc && typeof acc === "object" && !Array.isArray(acc) && current && acc.hasOwnProperty(current))) { | ||
// if the previous iteration returns undefined, | ||
// we'll forward this until the end of the loop. | ||
// We keep the first unavailable key for debug. | ||
firstUnavailableKey = firstUnavailableKey || current; | ||
return undefined; | ||
} | ||
return acc[current]; | ||
}, this); | ||
if (snapshot === undefined) { | ||
roxeDebug(`Cannot access to path "${path}". "${firstUnavailableKey}" is not reachable`); | ||
return snapshot; | ||
if (!(path && typeof path === "string")) { | ||
const snapshot = Object.assign({}, this); | ||
// In the snapshot, we don't need the symbol that collects | ||
// All the observers | ||
delete snapshot[observedObjects]; | ||
return snapshot; | ||
} | ||
const snapshot = path.split(".").reduce((acc, current) => { | ||
if (!(acc && typeof acc === "object" && !Array.isArray(acc) && current && acc.hasOwnProperty(current))) { | ||
// if the previous iteration returns undefined, | ||
// we'll forward this until the end of the loop. | ||
// We keep the first unavailable key for debug. | ||
firstUnavailableKey = firstUnavailableKey || current; | ||
return undefined; | ||
} | ||
if (typeof snapshot === "object") { | ||
return Object.assign({}, snapshot); | ||
} | ||
return acc[current]; | ||
}, this); | ||
if (snapshot === undefined) { | ||
roxeDebug(`Cannot access to path "${path}". "${firstUnavailableKey}" is not reachable`); | ||
return snapshot; | ||
} | ||
else { | ||
snapshot = Object.assign({}, this); | ||
// In the snapshot, we don't need the symbol that collects | ||
// All the observers | ||
delete snapshot[observedObjectsSymbol_1.observedObjects]; | ||
if (typeof snapshot === "object") { | ||
return Object.assign({}, snapshot); | ||
} | ||
@@ -167,3 +152,3 @@ return snapshot; | ||
} | ||
_a = observedObjectsSymbol_1.observedObjects; | ||
_a = observedObjects, _b = customTraps; | ||
// Workaround to allow us to recognize T's props as part of ObservableObject | ||
@@ -179,3 +164,4 @@ // https://stackoverflow.com/a/54737176/2929433 | ||
let chain = {}; | ||
for (const prop in sourceObject) { | ||
const targetObjectKeys = Object.keys(sourceObject); | ||
for (let i = targetObjectKeys.length, prop; prop = targetObjectKeys[--i];) { | ||
if (typeof sourceObject[prop] === "object" && !Array.isArray(sourceObject[prop])) { | ||
@@ -201,8 +187,14 @@ chain[prop] = buildInitialProxyChain(sourceObject[prop], Object.assign({}, handlers, { | ||
*/ | ||
function buildNotificationChain(source, ...args) { | ||
function buildNotificationChain(current, source, ...args) { | ||
// If our source was valorized and now will be null or undefined | ||
let chain = {}; | ||
for (const prop in source) { | ||
chain[[...args, prop].join(".")] = source[prop]; | ||
if (typeof source[prop] === "object" && !Array.isArray(source[prop])) { | ||
Object.assign(chain, buildNotificationChain(source[prop], ...args, prop)); | ||
const targetObject = ((source && Object.keys(source).length && source) || | ||
(current && Object.keys(current).length && current) || | ||
{}); | ||
const targetObjectKeys = Object.keys(targetObject); | ||
for (let i = targetObjectKeys.length, prop; prop = targetObjectKeys[--i];) { | ||
const realSource = source && source[prop] || undefined; | ||
chain[[...args, prop].join(".")] = realSource; | ||
if (targetObject && targetObject[prop] && typeof targetObject[prop] === "object" && !Array.isArray(targetObject[prop])) { | ||
Object.assign(chain, buildNotificationChain(current[prop], realSource, ...args, prop)); | ||
} | ||
@@ -209,0 +201,0 @@ } |
{ | ||
"name": "roxe", | ||
"version": "1.0.8", | ||
"version": "1.0.9", | ||
"description": "Observe object changes through proxies", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const __1 = require(".."); | ||
const observedObjectsSymbol_1 = require("../observedObjectsSymbol"); | ||
const rxjs_1 = require("rxjs"); | ||
let oo; | ||
@@ -27,3 +27,5 @@ describe("Registration and changes observation:", () => { | ||
oo.observe("b.d.e"); | ||
expect(Object.keys(oo[observedObjectsSymbol_1.observedObjects]).includes("b.d.e")).toBeTruthy(); | ||
const obsSymbols = Object.getOwnPropertySymbols(oo); | ||
const observedObjects = obsSymbols.find((sym) => oo[sym]["b.d.e"] && oo[sym]["b.d.e"] instanceof rxjs_1.Subject); | ||
expect(Object.keys(oo[observedObjects]).includes("b.d.e")).toBeTruthy(); | ||
}); | ||
@@ -30,0 +32,0 @@ it("Should notify all the changes", () => { |
58326
7
391