deep-state-observer
Advanced tools
Comparing version 3.26.9 to 4.0.0
458
index.cjs.js
'use strict'; | ||
/*! ***************************************************************************** | ||
Copyright (c) Microsoft Corporation. All rights reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use | ||
this file except in compliance with the License. You may obtain a copy of the | ||
License at http://www.apache.org/licenses/LICENSE-2.0 | ||
Copyright (c) Microsoft Corporation. | ||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED | ||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, | ||
MERCHANTABLITY OR NON-INFRINGEMENT. | ||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted. | ||
See the Apache Version 2.0 License for specific language governing permissions | ||
and limitations under the License. | ||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | ||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | ||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | ||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | ||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | ||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | ||
PERFORMANCE OF THIS SOFTWARE. | ||
***************************************************************************** */ | ||
@@ -358,6 +358,5 @@ | ||
only: [], | ||
source: '', | ||
source: "", | ||
debug: false, | ||
data: undefined, | ||
queue: false, | ||
force: false, | ||
@@ -368,2 +367,11 @@ }; | ||
} | ||
/** | ||
* Is object - helper function to determine if specified variable is an object | ||
* | ||
* @param {any} item | ||
* @returns {boolean} | ||
*/ | ||
function isObject(item) { | ||
return item && typeof item === "object" && item !== null && item.constructor && item.constructor.name === "Object"; | ||
} | ||
function getDefaultOptions() { | ||
@@ -379,3 +387,2 @@ return { | ||
experimentalMatch: false, | ||
queue: false, | ||
maxSimultaneousJobs: 1000, | ||
@@ -390,21 +397,11 @@ maxQueueRuns: 1000, | ||
debug: false, | ||
source: '', | ||
source: "", | ||
data: undefined, | ||
queue: false, | ||
group: false, | ||
}; | ||
const handler = { | ||
get(obj, prop) { }, | ||
set(obj, prop, value) { | ||
return true; | ||
}, | ||
}; | ||
class DeepState { | ||
constructor(data = {}, options = {}) { | ||
this.jobsRunning = 0; | ||
this.updateQueue = []; | ||
this.subscribeQueue = []; | ||
this.listenersIgnoreCache = new WeakMap(); | ||
this.destroyed = false; | ||
this.queueRuns = 0; | ||
this.groupId = 0; | ||
@@ -417,8 +414,46 @@ this.traceId = 0; | ||
this.collections = 0; | ||
this.proxyPath = []; | ||
this.handler = { | ||
get: (obj, prop) => { | ||
if (obj.hasOwnProperty(prop)) | ||
this.proxyPath.push(prop); | ||
return obj[prop]; | ||
}, | ||
set: (obj, prop, value) => { | ||
this.proxyPath.push(prop); | ||
const path = this.proxyPath.join("."); | ||
if (typeof value === "function") { | ||
value = value(obj[prop]); | ||
} | ||
let final = value; | ||
if (isObject(value)) { | ||
final = new Proxy(this.mergeDeepProxy({}, value), this.handler); | ||
} | ||
else if (Array.isArray(value)) { | ||
final = new Proxy(this.mergeDeepProxy([], value), this.handler); | ||
} | ||
this.proxyPath = []; | ||
this.update(path, final); | ||
obj[prop] = final; | ||
return true; | ||
}, | ||
}; | ||
this.lastExecs = new WeakMap(); | ||
this.listeners = new Map(); | ||
this.waitingListeners = new Map(); | ||
this.handler.get = this.handler.get.bind(this); | ||
this.handler.set = this.handler.set.bind(this); | ||
const self = this; | ||
this.data = data; | ||
this.proxy = new Proxy(this.data, handler); | ||
this.options = Object.assign({}, getDefaultOptions(), options); | ||
this.proxy = new Proxy(this.mergeDeepProxy({}, data), { | ||
get(obj, prop) { | ||
if (obj.hasOwnProperty(prop)) | ||
self.proxyPath = [prop]; | ||
return obj[prop]; | ||
}, | ||
set(obj, prop, value) { | ||
return self.handler.set(obj, prop, value); | ||
}, | ||
}); | ||
this.$$$ = this.proxy; | ||
this.options = Object.assign(Object.assign({}, getDefaultOptions()), options); | ||
this.id = 0; | ||
@@ -438,2 +473,57 @@ this.pathGet = ObjectPath.get; | ||
} | ||
mergeDeepProxy(target, ...sources) { | ||
const source = sources.shift(); | ||
if (isObject(target) && isObject(source)) { | ||
const targetValMain = {}; | ||
for (const key in source) { | ||
if (isObject(source[key])) { | ||
targetValMain[key] = new Proxy(this.mergeDeepProxy({}, source[key]), this.handler); | ||
} | ||
else if (Array.isArray(source[key])) { | ||
const targetVal = new Array(source[key].length); | ||
let index = 0; | ||
for (let item of source[key]) { | ||
if (isObject(item) || Array.isArray(item)) { | ||
targetVal[index] = new Proxy(this.mergeDeepProxy({}, item), this.handler); | ||
} | ||
else { | ||
targetVal[index] = item; | ||
} | ||
index++; | ||
} | ||
targetValMain[key] = new Proxy(targetVal, this.handler); | ||
} | ||
else { | ||
targetValMain[key] = source[key]; | ||
} | ||
} | ||
if (sources.length) { | ||
target = new Proxy(targetValMain, this.handler); | ||
} | ||
else { | ||
target = targetValMain; | ||
} | ||
} | ||
else if (Array.isArray(source)) { | ||
const targetVal = new Array(source.length); | ||
for (let i = 0, len = source.length; i < len; i++) { | ||
if (isObject(source[i]) || Array.isArray(source[i])) { | ||
targetVal[i] = this.mergeDeepProxy(source[i]); | ||
} | ||
else { | ||
targetVal[i] = source[i]; | ||
} | ||
} | ||
if (sources.length) { | ||
target = new Proxy(targetVal, this.handler); | ||
} | ||
else { | ||
target = targetVal; | ||
} | ||
} | ||
if (!sources.length) { | ||
return target; | ||
} | ||
return this.mergeDeepProxy(target, ...sources); | ||
} | ||
loadWasmMatcher(pathToWasmFile) { | ||
@@ -447,4 +537,3 @@ return __awaiter(this, void 0, void 0, function* () { | ||
same(newValue, oldValue) { | ||
return ((['number', 'string', 'undefined', 'boolean'].includes(typeof newValue) || | ||
newValue === null) && | ||
return ((["number", "string", "undefined", "boolean"].includes(typeof newValue) || newValue === null) && | ||
oldValue === newValue); | ||
@@ -459,4 +548,2 @@ } | ||
this.listeners = new Map(); | ||
this.updateQueue = []; | ||
this.jobsRunning = 0; | ||
} | ||
@@ -471,4 +558,3 @@ match(first, second, nested = true) { | ||
if (!nested && | ||
this.getIndicesCount(this.options.delimiter, first) < | ||
this.getIndicesCount(this.options.delimiter, second)) { | ||
this.getIndicesCount(this.options.delimiter, first) < this.getIndicesCount(this.options.delimiter, second)) { | ||
// first < second because first is a listener path and may be longer but not shorter | ||
@@ -520,3 +606,3 @@ return false; | ||
split(path) { | ||
return path === '' ? [] : path.split(this.options.delimiter); | ||
return path === "" ? [] : path.split(this.options.delimiter); | ||
} | ||
@@ -530,5 +616,3 @@ isWildcard(path) { | ||
cleanNotRecursivePath(path) { | ||
return this.isNotRecursive(path) | ||
? path.substring(0, path.length - 1) | ||
: path; | ||
return this.isNotRecursive(path) ? path.substring(0, path.length - 1) : path; | ||
} | ||
@@ -539,3 +623,3 @@ hasParams(path) { | ||
getParamsInfo(path) { | ||
let paramsInfo = { replaced: '', original: path, params: {} }; | ||
let paramsInfo = { replaced: "", original: path, params: {} }; | ||
let partIndex = 0; | ||
@@ -546,6 +630,6 @@ let fullReplaced = []; | ||
original: part, | ||
replaced: '', | ||
name: '', | ||
replaced: "", | ||
name: "", | ||
}; | ||
const reg = new RegExp(`\\${this.options.param}([^\\${this.options.delimiter}\\${this.options.param}]+)`, 'g'); | ||
const reg = new RegExp(`\\${this.options.param}([^\\${this.options.delimiter}\\${this.options.param}]+)`, "g"); | ||
let param = reg.exec(part); | ||
@@ -581,47 +665,2 @@ if (param) { | ||
} | ||
waitForAll(userPaths, fn) { | ||
const paths = {}; | ||
for (let path of userPaths) { | ||
paths[path] = { dirty: false }; | ||
if (this.hasParams(path)) { | ||
paths[path].paramsInfo = this.getParamsInfo(path); | ||
} | ||
paths[path].isWildcard = this.isWildcard(path); | ||
paths[path].isRecursive = !this.isNotRecursive(path); | ||
} | ||
this.waitingListeners.set(userPaths, { fn, paths }); | ||
fn(paths); | ||
return function unsubscribe() { | ||
this.waitingListeners.delete(userPaths); | ||
}; | ||
} | ||
executeWaitingListeners(updatePath) { | ||
if (this.destroyed) | ||
return; | ||
for (const waitingListener of this.waitingListeners.values()) { | ||
const { fn, paths } = waitingListener; | ||
let dirty = 0; | ||
let all = 0; | ||
for (let path in paths) { | ||
const pathInfo = paths[path]; | ||
let match = false; | ||
if (pathInfo.isRecursive) | ||
updatePath = this.cutPath(updatePath, path); | ||
if (pathInfo.isWildcard && this.match(path, updatePath)) | ||
match = true; | ||
if (updatePath === path) | ||
match = true; | ||
if (match) { | ||
pathInfo.dirty = true; | ||
} | ||
if (pathInfo.dirty) { | ||
dirty++; | ||
} | ||
all++; | ||
} | ||
if (dirty === all) { | ||
fn(paths); | ||
} | ||
} | ||
} | ||
subscribeAll(userPaths, fn, options = defaultListenerOptions) { | ||
@@ -656,3 +695,3 @@ if (this.destroyed) | ||
fn, | ||
options: Object.assign({}, defaultListenerOptions, options), | ||
options: Object.assign(Object.assign({}, defaultListenerOptions), options), | ||
groupId: null, | ||
@@ -673,3 +712,3 @@ }; | ||
if (debug) { | ||
console.log('[getListenerCollectionMatch]', { | ||
console.log("[getListenerCollectionMatch]", { | ||
listenerPath, | ||
@@ -710,3 +749,3 @@ scopedListenerPath, | ||
} | ||
let listenersCollection = this.getCleanListenersCollection(Object.assign({}, collCfg, { match: this.getListenerCollectionMatch(collCfg.path, collCfg.isRecursive, collCfg.isWildcard) })); | ||
let listenersCollection = this.getCleanListenersCollection(Object.assign(Object.assign({}, collCfg), { match: this.getListenerCollectionMatch(collCfg.path, collCfg.isRecursive, collCfg.isWildcard) })); | ||
this.id++; | ||
@@ -725,4 +764,3 @@ listenersCollection.listeners.set(this.id, listener); | ||
return () => { }; | ||
this.jobsRunning++; | ||
const type = 'subscribe'; | ||
const type = "subscribe"; | ||
let listener = this.getCleanListener(fn, options); | ||
@@ -737,5 +775,3 @@ if (options.group) | ||
listenersCollection.count++; | ||
if (!options.group || | ||
(options.group && | ||
subscribeAllOptions.all.length - 1 === subscribeAllOptions.index)) { | ||
if (!options.group || (options.group && subscribeAllOptions.all.length - 1 === subscribeAllOptions.index)) { | ||
const cleanPath = this.cleanNotRecursivePath(listenersCollection.path); | ||
@@ -807,3 +843,2 @@ if (!listenersCollection.isWildcard) { | ||
this.debugSubscribe(listener, listenersCollection, listenerPath); | ||
this.jobsRunning--; | ||
return this.unsubscribe(listenerPath, this.id); | ||
@@ -827,24 +862,7 @@ } | ||
return; | ||
if (this.jobsRunning === 0) { | ||
this.queueRuns = 0; | ||
const queue = [...this.subscribeQueue]; | ||
for (let i = 0, len = queue.length; i < len; i++) { | ||
queue[i](); | ||
} | ||
this.subscribeQueue.length = 0; | ||
const queue = [...this.subscribeQueue]; | ||
for (let i = 0, len = queue.length; i < len; i++) { | ||
queue[i](); | ||
} | ||
else { | ||
this.queueRuns++; | ||
if (this.queueRuns >= this.options.maxQueueRuns) { | ||
this.queueRuns = 0; | ||
throw new Error('Maximal number of queue runs exhausted.'); | ||
} | ||
else { | ||
Promise.resolve() | ||
.then(() => this.runQueuedListeners()) | ||
.catch((e) => { | ||
throw e; | ||
}); | ||
} | ||
} | ||
this.subscribeQueue.length = 0; | ||
} | ||
@@ -858,10 +876,5 @@ getQueueNotifyListeners(groupedListeners, queue = []) { | ||
let alreadyInQueue = false; | ||
let resolvedIdPath = singleListener.listener.id + | ||
':' + | ||
singleListener.eventInfo.path.resolved; | ||
let resolvedIdPath = singleListener.listener.id + ":" + singleListener.eventInfo.path.resolved; | ||
if (!singleListener.eventInfo.path.resolved) { | ||
resolvedIdPath = | ||
singleListener.listener.id + | ||
':' + | ||
singleListener.eventInfo.path.listener; | ||
resolvedIdPath = singleListener.listener.id + ":" + singleListener.eventInfo.path.listener; | ||
} | ||
@@ -879,29 +892,17 @@ for (const excludedListener of queue) { | ||
if (!this.isMuted(singleListener.listener.fn)) { | ||
if (singleListener.listener.options.queue && this.jobsRunning) { | ||
this.subscribeQueue.push(() => { | ||
let resolvedIdPath = singleListener.listener.id + ":" + singleListener.eventInfo.path.resolved; | ||
if (!singleListener.eventInfo.path.resolved) { | ||
resolvedIdPath = singleListener.listener.id + ":" + singleListener.eventInfo.path.listener; | ||
} | ||
queue.push({ | ||
id: singleListener.listener.id, | ||
resolvedPath: singleListener.eventInfo.path.resolved, | ||
resolvedIdPath, | ||
originalFn: singleListener.listener.fn, | ||
fn: () => { | ||
singleListener.listener.fn(singleListener.value(), singleListener.eventInfo); | ||
}); | ||
} | ||
else { | ||
let resolvedIdPath = singleListener.listener.id + | ||
':' + | ||
singleListener.eventInfo.path.resolved; | ||
if (!singleListener.eventInfo.path.resolved) { | ||
resolvedIdPath = | ||
singleListener.listener.id + | ||
':' + | ||
singleListener.eventInfo.path.listener; | ||
} | ||
queue.push({ | ||
id: singleListener.listener.id, | ||
resolvedPath: singleListener.eventInfo.path.resolved, | ||
resolvedIdPath, | ||
originalFn: singleListener.listener.fn, | ||
fn: () => { | ||
singleListener.listener.fn(singleListener.value(), singleListener.eventInfo); | ||
}, | ||
options: singleListener.listener.options, | ||
groupId: singleListener.listener.groupId, | ||
}); | ||
} | ||
}, | ||
options: singleListener.listener.options, | ||
groupId: singleListener.listener.groupId, | ||
}); | ||
} | ||
@@ -923,36 +924,20 @@ this.debugListener(time, singleListener); | ||
for (const bulk of bulkListener.value) { | ||
bulkValue.push(Object.assign({}, bulk, { value: bulk.value() })); | ||
bulkValue.push(Object.assign(Object.assign({}, bulk), { value: bulk.value() })); | ||
} | ||
if (!this.isMuted(bulkListener.listener.fn)) { | ||
if (bulkListener.listener.options.queue && this.jobsRunning) { | ||
this.subscribeQueue.push(() => { | ||
if (!this.jobsRunning) { | ||
bulkListener.listener.fn(bulkValue, bulkListener.eventInfo); | ||
return true; | ||
} | ||
return false; | ||
}); | ||
let resolvedIdPath = bulkListener.listener.id + ":" + bulkListener.eventInfo.path.resolved; | ||
if (!bulkListener.eventInfo.path.resolved) { | ||
resolvedIdPath = bulkListener.listener.id + ":" + bulkListener.eventInfo.path.listener; | ||
} | ||
else { | ||
let resolvedIdPath = bulkListener.listener.id + | ||
':' + | ||
bulkListener.eventInfo.path.resolved; | ||
if (!bulkListener.eventInfo.path.resolved) { | ||
resolvedIdPath = | ||
bulkListener.listener.id + | ||
':' + | ||
bulkListener.eventInfo.path.listener; | ||
} | ||
queue.push({ | ||
id: bulkListener.listener.id, | ||
resolvedPath: bulkListener.eventInfo.path.resolved, | ||
resolvedIdPath, | ||
originalFn: bulkListener.listener.fn, | ||
fn: () => { | ||
bulkListener.listener.fn(bulkValue, bulkListener.eventInfo); | ||
}, | ||
options: bulkListener.listener.options, | ||
groupId: bulkListener.listener.groupId, | ||
}); | ||
} | ||
queue.push({ | ||
id: bulkListener.listener.id, | ||
resolvedPath: bulkListener.eventInfo.path.resolved, | ||
resolvedIdPath, | ||
originalFn: bulkListener.listener.fn, | ||
fn: () => { | ||
bulkListener.listener.fn(bulkValue, bulkListener.eventInfo); | ||
}, | ||
options: bulkListener.listener.options, | ||
groupId: bulkListener.listener.groupId, | ||
}); | ||
} | ||
@@ -984,4 +969,4 @@ this.debugListener(time, bulkListener); | ||
} | ||
getSubscribedListeners(updatePath, newValue, options, type = 'update', originalPath = null) { | ||
options = Object.assign({}, defaultUpdateOptions, options); | ||
getSubscribedListeners(updatePath, newValue, options, type = "update", originalPath = null) { | ||
options = Object.assign(Object.assign({}, defaultUpdateOptions), options); | ||
const listeners = {}; | ||
@@ -1066,6 +1051,6 @@ for (let [listenerPath, listenersCollection] of this.listeners) { | ||
} | ||
notifySubscribedListeners(updatePath, newValue, options, type = 'update', originalPath = null) { | ||
notifySubscribedListeners(updatePath, newValue, options, type = "update", originalPath = null) { | ||
return this.getQueueNotifyListeners(this.getSubscribedListeners(updatePath, newValue, options, type, originalPath)); | ||
} | ||
getNestedListeners(updatePath, newValue, options, type = 'update', originalPath = null) { | ||
getNestedListeners(updatePath, newValue, options, type = "update", originalPath = null) { | ||
const listeners = {}; | ||
@@ -1143,3 +1128,3 @@ for (let [listenerPath, listenersCollection] of this.listeners) { | ||
if (listener.options.debug) { | ||
console.log('[getNestedListeners] Listener was not fired because there was no match.', { | ||
console.log("[getNestedListeners] Listener was not fired because there was no match.", { | ||
listener, | ||
@@ -1156,10 +1141,10 @@ listenersCollection, | ||
} | ||
notifyNestedListeners(updatePath, newValue, options, type = 'update', queue, originalPath = null) { | ||
notifyNestedListeners(updatePath, newValue, options, type = "update", queue, originalPath = null) { | ||
return this.getQueueNotifyListeners(this.getNestedListeners(updatePath, newValue, options, type, originalPath), queue); | ||
} | ||
getNotifyOnlyListeners(updatePath, newValue, options, type = 'update', originalPath = null) { | ||
getNotifyOnlyListeners(updatePath, newValue, options, type = "update", originalPath = null) { | ||
const listeners = {}; | ||
if (typeof options.only !== 'object' || | ||
if (typeof options.only !== "object" || | ||
!Array.isArray(options.only) || | ||
typeof options.only[0] === 'undefined' || | ||
typeof options.only[0] === "undefined" || | ||
!this.canBeNested(newValue)) { | ||
@@ -1243,3 +1228,3 @@ return listeners; | ||
} | ||
notifyOnly(updatePath, newValue, options, type = 'update', originalPath = '') { | ||
notifyOnly(updatePath, newValue, options, type = "update", originalPath = "") { | ||
const queue = this.getQueueNotifyListeners(this.getNotifyOnlyListeners(updatePath, newValue, options, type, originalPath)); | ||
@@ -1249,7 +1234,7 @@ this.sortAndRunQueue(queue, updatePath); | ||
canBeNested(newValue) { | ||
return typeof newValue === 'object' && newValue !== null; | ||
return typeof newValue === "object" && newValue !== null; | ||
} | ||
getUpdateValues(oldValue, split, fn) { | ||
let newValue = fn; | ||
if (typeof fn === 'function') { | ||
if (typeof fn === "function") { | ||
newValue = fn(this.pathGet(split, this.data)); | ||
@@ -1259,3 +1244,3 @@ } | ||
} | ||
wildcardNotify(groupedListenersPack, waitingPaths) { | ||
wildcardNotify(groupedListenersPack) { | ||
let queue = []; | ||
@@ -1265,11 +1250,6 @@ for (const groupedListeners of groupedListenersPack) { | ||
} | ||
for (const path of waitingPaths) { | ||
this.executeWaitingListeners(path); | ||
} | ||
this.jobsRunning--; | ||
return queue; | ||
} | ||
wildcardUpdate(updatePath, fn, options = defaultUpdateOptions, multi = false) { | ||
++this.jobsRunning; | ||
options = Object.assign({}, defaultUpdateOptions, options); | ||
options = Object.assign(Object.assign({}, defaultUpdateOptions), options); | ||
const scanned = this.scan.get(updatePath); | ||
@@ -1286,16 +1266,14 @@ const updated = {}; | ||
const groupedListenersPack = []; | ||
const waitingPaths = []; | ||
for (const path in updated) { | ||
const newValue = updated[path]; | ||
if (options.only.length) { | ||
groupedListenersPack.push(this.getNotifyOnlyListeners(path, newValue, options, 'update', updatePath)); | ||
groupedListenersPack.push(this.getNotifyOnlyListeners(path, newValue, options, "update", updatePath)); | ||
} | ||
else { | ||
groupedListenersPack.push(this.getSubscribedListeners(path, newValue, options, 'update', updatePath)); | ||
groupedListenersPack.push(this.getSubscribedListeners(path, newValue, options, "update", updatePath)); | ||
if (this.canBeNested(newValue)) { | ||
groupedListenersPack.push(this.getNestedListeners(path, newValue, options, 'update', updatePath)); | ||
groupedListenersPack.push(this.getNestedListeners(path, newValue, options, "update", updatePath)); | ||
} | ||
} | ||
options.debug && this.options.log('Wildcard update', { path, newValue }); | ||
waitingPaths.push(path); | ||
options.debug && this.options.log("Wildcard update", { path, newValue }); | ||
} | ||
@@ -1305,26 +1283,15 @@ if (multi) { | ||
return function () { | ||
const queue = self.wildcardNotify(groupedListenersPack, waitingPaths); | ||
const queue = self.wildcardNotify(groupedListenersPack); | ||
self.sortAndRunQueue(queue, updatePath); | ||
}; | ||
} | ||
const queue = this.wildcardNotify(groupedListenersPack, waitingPaths); | ||
const queue = this.wildcardNotify(groupedListenersPack); | ||
this.sortAndRunQueue(queue, updatePath); | ||
} | ||
runUpdateQueue() { | ||
if (this.destroyed) | ||
return; | ||
while (this.updateQueue.length && | ||
this.updateQueue.length < this.options.maxSimultaneousJobs) { | ||
const params = this.updateQueue.shift(); | ||
params.options.queue = false; // prevent infinite loop | ||
this.update(params.updatePath, params.fnOrValue, params.options, params.multi); | ||
} | ||
} | ||
updateNotify(updatePath, newValue, options) { | ||
const queue = this.notifySubscribedListeners(updatePath, newValue, options); | ||
if (this.canBeNested(newValue)) { | ||
this.notifyNestedListeners(updatePath, newValue, options, 'update', queue); | ||
this.notifyNestedListeners(updatePath, newValue, options, "update", queue); | ||
} | ||
this.sortAndRunQueue(queue, updatePath); | ||
this.executeWaitingListeners(updatePath); | ||
} | ||
@@ -1348,3 +1315,3 @@ updateNotifyAll(updateStack) { | ||
if (this.canBeNested(current.newValue)) { | ||
this.notifyNestedListeners(current.updatePath, value, current.options, 'update', queue); | ||
this.notifyNestedListeners(current.updatePath, value, current.options, "update", queue); | ||
} | ||
@@ -1356,3 +1323,2 @@ } | ||
this.notifyOnly(updatePath, newValue, options); | ||
this.executeWaitingListeners(updatePath); | ||
} | ||
@@ -1371,26 +1337,9 @@ update(updatePath, fnOrValue, options = Object.assign({}, defaultUpdateOptions), multi = false) { | ||
} | ||
const jobsRunning = this.jobsRunning; | ||
if ((this.options.queue || options.queue) && jobsRunning) { | ||
if (jobsRunning > this.options.maxSimultaneousJobs) { | ||
throw new Error('Maximal simultaneous jobs limit reached.'); | ||
} | ||
this.updateQueue.push({ updatePath, fnOrValue, options, multi }); | ||
const result = Promise.resolve().then(() => { | ||
this.runUpdateQueue(); | ||
}); | ||
if (multi) { | ||
return function () { | ||
return result; | ||
}; | ||
} | ||
return result; | ||
} | ||
if (this.isWildcard(updatePath)) { | ||
return this.wildcardUpdate(updatePath, fnOrValue, options, multi); | ||
} | ||
++this.jobsRunning; | ||
const split = this.split(updatePath); | ||
const { oldValue, newValue } = this.getUpdateValues(this.pathGet(split, this.data), split, fnOrValue); | ||
if (options.debug) { | ||
this.options.log(`Updating ${updatePath} ${options.source ? `from ${options.source}` : ''}`, { | ||
this.options.log(`Updating ${updatePath} ${options.source ? `from ${options.source}` : ""}`, { | ||
oldValue, | ||
@@ -1401,3 +1350,2 @@ newValue, | ||
if (this.same(newValue, oldValue) && !options.force) { | ||
--this.jobsRunning; | ||
if (multi) | ||
@@ -1410,5 +1358,4 @@ return function () { | ||
this.pathSet(split, newValue, this.data); | ||
options = Object.assign({}, defaultUpdateOptions, options); | ||
options = Object.assign(Object.assign({}, defaultUpdateOptions), options); | ||
if (options.only === null) { | ||
--this.jobsRunning; | ||
if (multi) | ||
@@ -1419,3 +1366,2 @@ return function () { }; | ||
if (options.only.length) { | ||
--this.jobsRunning; | ||
if (multi) { | ||
@@ -1431,3 +1377,2 @@ const self = this; | ||
if (multi) { | ||
--this.jobsRunning; | ||
const self = this; | ||
@@ -1439,3 +1384,2 @@ return function multiUpdate() { | ||
this.updateNotify(updatePath, newValue, options); | ||
--this.jobsRunning; | ||
return newValue; | ||
@@ -1464,3 +1408,3 @@ } | ||
let value = fnOrValue; | ||
if (typeof value === 'function') { | ||
if (typeof value === "function") { | ||
value = value(self.pathGet(split, self.data)); | ||
@@ -1522,3 +1466,3 @@ } | ||
return; | ||
if (typeof userPath === 'undefined' || userPath === '') { | ||
if (typeof userPath === "undefined" || userPath === "") { | ||
return this.data; | ||
@@ -1545,3 +1489,3 @@ } | ||
return false; | ||
if (typeof pathOrListenerFunction === 'function') { | ||
if (typeof pathOrListenerFunction === "function") { | ||
return this.isMutedListener(pathOrListenerFunction); | ||
@@ -1570,3 +1514,3 @@ } | ||
mute(pathOrListenerFunction) { | ||
if (typeof pathOrListenerFunction === 'function') { | ||
if (typeof pathOrListenerFunction === "function") { | ||
return this.mutedListeners.add(pathOrListenerFunction); | ||
@@ -1577,3 +1521,3 @@ } | ||
unmute(pathOrListenerFunction) { | ||
if (typeof pathOrListenerFunction === 'function') { | ||
if (typeof pathOrListenerFunction === "function") { | ||
return this.mutedListeners.delete(pathOrListenerFunction); | ||
@@ -1585,3 +1529,3 @@ } | ||
if (listener.options.debug) { | ||
this.options.log('listener subscribed', { | ||
this.options.log("listener subscribed", { | ||
listenerPath, | ||
@@ -1594,5 +1538,4 @@ listener, | ||
debugListener(time, groupedListener) { | ||
if (groupedListener.eventInfo.options.debug || | ||
groupedListener.listener.options.debug) { | ||
this.options.log('Listener fired', { | ||
if (groupedListener.eventInfo.options.debug || groupedListener.listener.options.debug) { | ||
this.options.log("Listener fired", { | ||
time: Date.now() - time, | ||
@@ -1604,10 +1547,7 @@ info: groupedListener, | ||
debugTime(groupedListener) { | ||
return groupedListener.listener.options.debug || | ||
groupedListener.eventInfo.options.debug | ||
? Date.now() | ||
: 0; | ||
return groupedListener.listener.options.debug || groupedListener.eventInfo.options.debug ? Date.now() : 0; | ||
} | ||
startTrace(name, additionalData = null) { | ||
this.traceId++; | ||
const id = this.traceId + ':' + name; | ||
const id = this.traceId + ":" + name; | ||
this.traceMap.set(id, { | ||
@@ -1614,0 +1554,0 @@ id, |
@@ -23,3 +23,2 @@ export interface PathInfo { | ||
experimentalMatch?: boolean; | ||
queue?: boolean; | ||
maxSimultaneousJobs?: number; | ||
@@ -37,3 +36,2 @@ maxQueueRuns?: number; | ||
data?: any; | ||
queue?: boolean; | ||
ignore?: string[]; | ||
@@ -145,5 +143,7 @@ group?: boolean; | ||
} | ||
export interface UnknownObject { | ||
[key: string]: unknown; | ||
} | ||
declare class DeepState<T> { | ||
private listeners; | ||
private waitingListeners; | ||
private data; | ||
@@ -155,4 +155,2 @@ private options; | ||
private scan; | ||
private jobsRunning; | ||
private updateQueue; | ||
private subscribeQueue; | ||
@@ -162,3 +160,2 @@ private listenersIgnoreCache; | ||
private destroyed; | ||
private queueRuns; | ||
private resolved; | ||
@@ -174,4 +171,11 @@ private muted; | ||
private collections; | ||
proxy: any; | ||
private proxyPath; | ||
private handler; | ||
proxy: T; | ||
/** | ||
* @property $$$ proxy shorthand | ||
*/ | ||
$$$: T; | ||
constructor(data?: T | object, options?: Options); | ||
private mergeDeepProxy; | ||
loadWasmMatcher(pathToWasmFile: string): Promise<void>; | ||
@@ -193,4 +197,2 @@ private same; | ||
private getParams; | ||
waitForAll(userPaths: string[], fn: WaitingListenerFunction): () => void; | ||
private executeWaitingListeners; | ||
subscribeAll(userPaths: string[], fn: ListenerFunction | WaitingListenerFunction, options?: ListenerOptions): () => void; | ||
@@ -218,3 +220,2 @@ private getCleanListenersCollection; | ||
private wildcardUpdate; | ||
private runUpdateQueue; | ||
private updateNotify; | ||
@@ -221,0 +222,0 @@ private updateNotifyAll; |
460
index.esm.js
/*! ***************************************************************************** | ||
Copyright (c) Microsoft Corporation. All rights reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use | ||
this file except in compliance with the License. You may obtain a copy of the | ||
License at http://www.apache.org/licenses/LICENSE-2.0 | ||
Copyright (c) Microsoft Corporation. | ||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED | ||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, | ||
MERCHANTABLITY OR NON-INFRINGEMENT. | ||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted. | ||
See the Apache Version 2.0 License for specific language governing permissions | ||
and limitations under the License. | ||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | ||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | ||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | ||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | ||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | ||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | ||
PERFORMANCE OF THIS SOFTWARE. | ||
***************************************************************************** */ | ||
@@ -356,6 +356,5 @@ | ||
only: [], | ||
source: '', | ||
source: "", | ||
debug: false, | ||
data: undefined, | ||
queue: false, | ||
force: false, | ||
@@ -366,2 +365,11 @@ }; | ||
} | ||
/** | ||
* Is object - helper function to determine if specified variable is an object | ||
* | ||
* @param {any} item | ||
* @returns {boolean} | ||
*/ | ||
function isObject(item) { | ||
return item && typeof item === "object" && item !== null && item.constructor && item.constructor.name === "Object"; | ||
} | ||
function getDefaultOptions() { | ||
@@ -377,3 +385,2 @@ return { | ||
experimentalMatch: false, | ||
queue: false, | ||
maxSimultaneousJobs: 1000, | ||
@@ -388,21 +395,11 @@ maxQueueRuns: 1000, | ||
debug: false, | ||
source: '', | ||
source: "", | ||
data: undefined, | ||
queue: false, | ||
group: false, | ||
}; | ||
const handler = { | ||
get(obj, prop) { }, | ||
set(obj, prop, value) { | ||
return true; | ||
}, | ||
}; | ||
class DeepState { | ||
constructor(data = {}, options = {}) { | ||
this.jobsRunning = 0; | ||
this.updateQueue = []; | ||
this.subscribeQueue = []; | ||
this.listenersIgnoreCache = new WeakMap(); | ||
this.destroyed = false; | ||
this.queueRuns = 0; | ||
this.groupId = 0; | ||
@@ -415,8 +412,46 @@ this.traceId = 0; | ||
this.collections = 0; | ||
this.proxyPath = []; | ||
this.handler = { | ||
get: (obj, prop) => { | ||
if (obj.hasOwnProperty(prop)) | ||
this.proxyPath.push(prop); | ||
return obj[prop]; | ||
}, | ||
set: (obj, prop, value) => { | ||
this.proxyPath.push(prop); | ||
const path = this.proxyPath.join("."); | ||
if (typeof value === "function") { | ||
value = value(obj[prop]); | ||
} | ||
let final = value; | ||
if (isObject(value)) { | ||
final = new Proxy(this.mergeDeepProxy({}, value), this.handler); | ||
} | ||
else if (Array.isArray(value)) { | ||
final = new Proxy(this.mergeDeepProxy([], value), this.handler); | ||
} | ||
this.proxyPath = []; | ||
this.update(path, final); | ||
obj[prop] = final; | ||
return true; | ||
}, | ||
}; | ||
this.lastExecs = new WeakMap(); | ||
this.listeners = new Map(); | ||
this.waitingListeners = new Map(); | ||
this.handler.get = this.handler.get.bind(this); | ||
this.handler.set = this.handler.set.bind(this); | ||
const self = this; | ||
this.data = data; | ||
this.proxy = new Proxy(this.data, handler); | ||
this.options = Object.assign({}, getDefaultOptions(), options); | ||
this.proxy = new Proxy(this.mergeDeepProxy({}, data), { | ||
get(obj, prop) { | ||
if (obj.hasOwnProperty(prop)) | ||
self.proxyPath = [prop]; | ||
return obj[prop]; | ||
}, | ||
set(obj, prop, value) { | ||
return self.handler.set(obj, prop, value); | ||
}, | ||
}); | ||
this.$$$ = this.proxy; | ||
this.options = Object.assign(Object.assign({}, getDefaultOptions()), options); | ||
this.id = 0; | ||
@@ -436,2 +471,57 @@ this.pathGet = ObjectPath.get; | ||
} | ||
mergeDeepProxy(target, ...sources) { | ||
const source = sources.shift(); | ||
if (isObject(target) && isObject(source)) { | ||
const targetValMain = {}; | ||
for (const key in source) { | ||
if (isObject(source[key])) { | ||
targetValMain[key] = new Proxy(this.mergeDeepProxy({}, source[key]), this.handler); | ||
} | ||
else if (Array.isArray(source[key])) { | ||
const targetVal = new Array(source[key].length); | ||
let index = 0; | ||
for (let item of source[key]) { | ||
if (isObject(item) || Array.isArray(item)) { | ||
targetVal[index] = new Proxy(this.mergeDeepProxy({}, item), this.handler); | ||
} | ||
else { | ||
targetVal[index] = item; | ||
} | ||
index++; | ||
} | ||
targetValMain[key] = new Proxy(targetVal, this.handler); | ||
} | ||
else { | ||
targetValMain[key] = source[key]; | ||
} | ||
} | ||
if (sources.length) { | ||
target = new Proxy(targetValMain, this.handler); | ||
} | ||
else { | ||
target = targetValMain; | ||
} | ||
} | ||
else if (Array.isArray(source)) { | ||
const targetVal = new Array(source.length); | ||
for (let i = 0, len = source.length; i < len; i++) { | ||
if (isObject(source[i]) || Array.isArray(source[i])) { | ||
targetVal[i] = this.mergeDeepProxy(source[i]); | ||
} | ||
else { | ||
targetVal[i] = source[i]; | ||
} | ||
} | ||
if (sources.length) { | ||
target = new Proxy(targetVal, this.handler); | ||
} | ||
else { | ||
target = targetVal; | ||
} | ||
} | ||
if (!sources.length) { | ||
return target; | ||
} | ||
return this.mergeDeepProxy(target, ...sources); | ||
} | ||
loadWasmMatcher(pathToWasmFile) { | ||
@@ -445,4 +535,3 @@ return __awaiter(this, void 0, void 0, function* () { | ||
same(newValue, oldValue) { | ||
return ((['number', 'string', 'undefined', 'boolean'].includes(typeof newValue) || | ||
newValue === null) && | ||
return ((["number", "string", "undefined", "boolean"].includes(typeof newValue) || newValue === null) && | ||
oldValue === newValue); | ||
@@ -457,4 +546,2 @@ } | ||
this.listeners = new Map(); | ||
this.updateQueue = []; | ||
this.jobsRunning = 0; | ||
} | ||
@@ -469,4 +556,3 @@ match(first, second, nested = true) { | ||
if (!nested && | ||
this.getIndicesCount(this.options.delimiter, first) < | ||
this.getIndicesCount(this.options.delimiter, second)) { | ||
this.getIndicesCount(this.options.delimiter, first) < this.getIndicesCount(this.options.delimiter, second)) { | ||
// first < second because first is a listener path and may be longer but not shorter | ||
@@ -518,3 +604,3 @@ return false; | ||
split(path) { | ||
return path === '' ? [] : path.split(this.options.delimiter); | ||
return path === "" ? [] : path.split(this.options.delimiter); | ||
} | ||
@@ -528,5 +614,3 @@ isWildcard(path) { | ||
cleanNotRecursivePath(path) { | ||
return this.isNotRecursive(path) | ||
? path.substring(0, path.length - 1) | ||
: path; | ||
return this.isNotRecursive(path) ? path.substring(0, path.length - 1) : path; | ||
} | ||
@@ -537,3 +621,3 @@ hasParams(path) { | ||
getParamsInfo(path) { | ||
let paramsInfo = { replaced: '', original: path, params: {} }; | ||
let paramsInfo = { replaced: "", original: path, params: {} }; | ||
let partIndex = 0; | ||
@@ -544,6 +628,6 @@ let fullReplaced = []; | ||
original: part, | ||
replaced: '', | ||
name: '', | ||
replaced: "", | ||
name: "", | ||
}; | ||
const reg = new RegExp(`\\${this.options.param}([^\\${this.options.delimiter}\\${this.options.param}]+)`, 'g'); | ||
const reg = new RegExp(`\\${this.options.param}([^\\${this.options.delimiter}\\${this.options.param}]+)`, "g"); | ||
let param = reg.exec(part); | ||
@@ -579,47 +663,2 @@ if (param) { | ||
} | ||
waitForAll(userPaths, fn) { | ||
const paths = {}; | ||
for (let path of userPaths) { | ||
paths[path] = { dirty: false }; | ||
if (this.hasParams(path)) { | ||
paths[path].paramsInfo = this.getParamsInfo(path); | ||
} | ||
paths[path].isWildcard = this.isWildcard(path); | ||
paths[path].isRecursive = !this.isNotRecursive(path); | ||
} | ||
this.waitingListeners.set(userPaths, { fn, paths }); | ||
fn(paths); | ||
return function unsubscribe() { | ||
this.waitingListeners.delete(userPaths); | ||
}; | ||
} | ||
executeWaitingListeners(updatePath) { | ||
if (this.destroyed) | ||
return; | ||
for (const waitingListener of this.waitingListeners.values()) { | ||
const { fn, paths } = waitingListener; | ||
let dirty = 0; | ||
let all = 0; | ||
for (let path in paths) { | ||
const pathInfo = paths[path]; | ||
let match = false; | ||
if (pathInfo.isRecursive) | ||
updatePath = this.cutPath(updatePath, path); | ||
if (pathInfo.isWildcard && this.match(path, updatePath)) | ||
match = true; | ||
if (updatePath === path) | ||
match = true; | ||
if (match) { | ||
pathInfo.dirty = true; | ||
} | ||
if (pathInfo.dirty) { | ||
dirty++; | ||
} | ||
all++; | ||
} | ||
if (dirty === all) { | ||
fn(paths); | ||
} | ||
} | ||
} | ||
subscribeAll(userPaths, fn, options = defaultListenerOptions) { | ||
@@ -654,3 +693,3 @@ if (this.destroyed) | ||
fn, | ||
options: Object.assign({}, defaultListenerOptions, options), | ||
options: Object.assign(Object.assign({}, defaultListenerOptions), options), | ||
groupId: null, | ||
@@ -671,3 +710,3 @@ }; | ||
if (debug) { | ||
console.log('[getListenerCollectionMatch]', { | ||
console.log("[getListenerCollectionMatch]", { | ||
listenerPath, | ||
@@ -708,3 +747,3 @@ scopedListenerPath, | ||
} | ||
let listenersCollection = this.getCleanListenersCollection(Object.assign({}, collCfg, { match: this.getListenerCollectionMatch(collCfg.path, collCfg.isRecursive, collCfg.isWildcard) })); | ||
let listenersCollection = this.getCleanListenersCollection(Object.assign(Object.assign({}, collCfg), { match: this.getListenerCollectionMatch(collCfg.path, collCfg.isRecursive, collCfg.isWildcard) })); | ||
this.id++; | ||
@@ -723,4 +762,3 @@ listenersCollection.listeners.set(this.id, listener); | ||
return () => { }; | ||
this.jobsRunning++; | ||
const type = 'subscribe'; | ||
const type = "subscribe"; | ||
let listener = this.getCleanListener(fn, options); | ||
@@ -735,5 +773,3 @@ if (options.group) | ||
listenersCollection.count++; | ||
if (!options.group || | ||
(options.group && | ||
subscribeAllOptions.all.length - 1 === subscribeAllOptions.index)) { | ||
if (!options.group || (options.group && subscribeAllOptions.all.length - 1 === subscribeAllOptions.index)) { | ||
const cleanPath = this.cleanNotRecursivePath(listenersCollection.path); | ||
@@ -805,3 +841,2 @@ if (!listenersCollection.isWildcard) { | ||
this.debugSubscribe(listener, listenersCollection, listenerPath); | ||
this.jobsRunning--; | ||
return this.unsubscribe(listenerPath, this.id); | ||
@@ -825,24 +860,7 @@ } | ||
return; | ||
if (this.jobsRunning === 0) { | ||
this.queueRuns = 0; | ||
const queue = [...this.subscribeQueue]; | ||
for (let i = 0, len = queue.length; i < len; i++) { | ||
queue[i](); | ||
} | ||
this.subscribeQueue.length = 0; | ||
const queue = [...this.subscribeQueue]; | ||
for (let i = 0, len = queue.length; i < len; i++) { | ||
queue[i](); | ||
} | ||
else { | ||
this.queueRuns++; | ||
if (this.queueRuns >= this.options.maxQueueRuns) { | ||
this.queueRuns = 0; | ||
throw new Error('Maximal number of queue runs exhausted.'); | ||
} | ||
else { | ||
Promise.resolve() | ||
.then(() => this.runQueuedListeners()) | ||
.catch((e) => { | ||
throw e; | ||
}); | ||
} | ||
} | ||
this.subscribeQueue.length = 0; | ||
} | ||
@@ -856,10 +874,5 @@ getQueueNotifyListeners(groupedListeners, queue = []) { | ||
let alreadyInQueue = false; | ||
let resolvedIdPath = singleListener.listener.id + | ||
':' + | ||
singleListener.eventInfo.path.resolved; | ||
let resolvedIdPath = singleListener.listener.id + ":" + singleListener.eventInfo.path.resolved; | ||
if (!singleListener.eventInfo.path.resolved) { | ||
resolvedIdPath = | ||
singleListener.listener.id + | ||
':' + | ||
singleListener.eventInfo.path.listener; | ||
resolvedIdPath = singleListener.listener.id + ":" + singleListener.eventInfo.path.listener; | ||
} | ||
@@ -877,29 +890,17 @@ for (const excludedListener of queue) { | ||
if (!this.isMuted(singleListener.listener.fn)) { | ||
if (singleListener.listener.options.queue && this.jobsRunning) { | ||
this.subscribeQueue.push(() => { | ||
let resolvedIdPath = singleListener.listener.id + ":" + singleListener.eventInfo.path.resolved; | ||
if (!singleListener.eventInfo.path.resolved) { | ||
resolvedIdPath = singleListener.listener.id + ":" + singleListener.eventInfo.path.listener; | ||
} | ||
queue.push({ | ||
id: singleListener.listener.id, | ||
resolvedPath: singleListener.eventInfo.path.resolved, | ||
resolvedIdPath, | ||
originalFn: singleListener.listener.fn, | ||
fn: () => { | ||
singleListener.listener.fn(singleListener.value(), singleListener.eventInfo); | ||
}); | ||
} | ||
else { | ||
let resolvedIdPath = singleListener.listener.id + | ||
':' + | ||
singleListener.eventInfo.path.resolved; | ||
if (!singleListener.eventInfo.path.resolved) { | ||
resolvedIdPath = | ||
singleListener.listener.id + | ||
':' + | ||
singleListener.eventInfo.path.listener; | ||
} | ||
queue.push({ | ||
id: singleListener.listener.id, | ||
resolvedPath: singleListener.eventInfo.path.resolved, | ||
resolvedIdPath, | ||
originalFn: singleListener.listener.fn, | ||
fn: () => { | ||
singleListener.listener.fn(singleListener.value(), singleListener.eventInfo); | ||
}, | ||
options: singleListener.listener.options, | ||
groupId: singleListener.listener.groupId, | ||
}); | ||
} | ||
}, | ||
options: singleListener.listener.options, | ||
groupId: singleListener.listener.groupId, | ||
}); | ||
} | ||
@@ -921,36 +922,20 @@ this.debugListener(time, singleListener); | ||
for (const bulk of bulkListener.value) { | ||
bulkValue.push(Object.assign({}, bulk, { value: bulk.value() })); | ||
bulkValue.push(Object.assign(Object.assign({}, bulk), { value: bulk.value() })); | ||
} | ||
if (!this.isMuted(bulkListener.listener.fn)) { | ||
if (bulkListener.listener.options.queue && this.jobsRunning) { | ||
this.subscribeQueue.push(() => { | ||
if (!this.jobsRunning) { | ||
bulkListener.listener.fn(bulkValue, bulkListener.eventInfo); | ||
return true; | ||
} | ||
return false; | ||
}); | ||
let resolvedIdPath = bulkListener.listener.id + ":" + bulkListener.eventInfo.path.resolved; | ||
if (!bulkListener.eventInfo.path.resolved) { | ||
resolvedIdPath = bulkListener.listener.id + ":" + bulkListener.eventInfo.path.listener; | ||
} | ||
else { | ||
let resolvedIdPath = bulkListener.listener.id + | ||
':' + | ||
bulkListener.eventInfo.path.resolved; | ||
if (!bulkListener.eventInfo.path.resolved) { | ||
resolvedIdPath = | ||
bulkListener.listener.id + | ||
':' + | ||
bulkListener.eventInfo.path.listener; | ||
} | ||
queue.push({ | ||
id: bulkListener.listener.id, | ||
resolvedPath: bulkListener.eventInfo.path.resolved, | ||
resolvedIdPath, | ||
originalFn: bulkListener.listener.fn, | ||
fn: () => { | ||
bulkListener.listener.fn(bulkValue, bulkListener.eventInfo); | ||
}, | ||
options: bulkListener.listener.options, | ||
groupId: bulkListener.listener.groupId, | ||
}); | ||
} | ||
queue.push({ | ||
id: bulkListener.listener.id, | ||
resolvedPath: bulkListener.eventInfo.path.resolved, | ||
resolvedIdPath, | ||
originalFn: bulkListener.listener.fn, | ||
fn: () => { | ||
bulkListener.listener.fn(bulkValue, bulkListener.eventInfo); | ||
}, | ||
options: bulkListener.listener.options, | ||
groupId: bulkListener.listener.groupId, | ||
}); | ||
} | ||
@@ -982,4 +967,4 @@ this.debugListener(time, bulkListener); | ||
} | ||
getSubscribedListeners(updatePath, newValue, options, type = 'update', originalPath = null) { | ||
options = Object.assign({}, defaultUpdateOptions, options); | ||
getSubscribedListeners(updatePath, newValue, options, type = "update", originalPath = null) { | ||
options = Object.assign(Object.assign({}, defaultUpdateOptions), options); | ||
const listeners = {}; | ||
@@ -1064,6 +1049,6 @@ for (let [listenerPath, listenersCollection] of this.listeners) { | ||
} | ||
notifySubscribedListeners(updatePath, newValue, options, type = 'update', originalPath = null) { | ||
notifySubscribedListeners(updatePath, newValue, options, type = "update", originalPath = null) { | ||
return this.getQueueNotifyListeners(this.getSubscribedListeners(updatePath, newValue, options, type, originalPath)); | ||
} | ||
getNestedListeners(updatePath, newValue, options, type = 'update', originalPath = null) { | ||
getNestedListeners(updatePath, newValue, options, type = "update", originalPath = null) { | ||
const listeners = {}; | ||
@@ -1141,3 +1126,3 @@ for (let [listenerPath, listenersCollection] of this.listeners) { | ||
if (listener.options.debug) { | ||
console.log('[getNestedListeners] Listener was not fired because there was no match.', { | ||
console.log("[getNestedListeners] Listener was not fired because there was no match.", { | ||
listener, | ||
@@ -1154,10 +1139,10 @@ listenersCollection, | ||
} | ||
notifyNestedListeners(updatePath, newValue, options, type = 'update', queue, originalPath = null) { | ||
notifyNestedListeners(updatePath, newValue, options, type = "update", queue, originalPath = null) { | ||
return this.getQueueNotifyListeners(this.getNestedListeners(updatePath, newValue, options, type, originalPath), queue); | ||
} | ||
getNotifyOnlyListeners(updatePath, newValue, options, type = 'update', originalPath = null) { | ||
getNotifyOnlyListeners(updatePath, newValue, options, type = "update", originalPath = null) { | ||
const listeners = {}; | ||
if (typeof options.only !== 'object' || | ||
if (typeof options.only !== "object" || | ||
!Array.isArray(options.only) || | ||
typeof options.only[0] === 'undefined' || | ||
typeof options.only[0] === "undefined" || | ||
!this.canBeNested(newValue)) { | ||
@@ -1241,3 +1226,3 @@ return listeners; | ||
} | ||
notifyOnly(updatePath, newValue, options, type = 'update', originalPath = '') { | ||
notifyOnly(updatePath, newValue, options, type = "update", originalPath = "") { | ||
const queue = this.getQueueNotifyListeners(this.getNotifyOnlyListeners(updatePath, newValue, options, type, originalPath)); | ||
@@ -1247,7 +1232,7 @@ this.sortAndRunQueue(queue, updatePath); | ||
canBeNested(newValue) { | ||
return typeof newValue === 'object' && newValue !== null; | ||
return typeof newValue === "object" && newValue !== null; | ||
} | ||
getUpdateValues(oldValue, split, fn) { | ||
let newValue = fn; | ||
if (typeof fn === 'function') { | ||
if (typeof fn === "function") { | ||
newValue = fn(this.pathGet(split, this.data)); | ||
@@ -1257,3 +1242,3 @@ } | ||
} | ||
wildcardNotify(groupedListenersPack, waitingPaths) { | ||
wildcardNotify(groupedListenersPack) { | ||
let queue = []; | ||
@@ -1263,11 +1248,6 @@ for (const groupedListeners of groupedListenersPack) { | ||
} | ||
for (const path of waitingPaths) { | ||
this.executeWaitingListeners(path); | ||
} | ||
this.jobsRunning--; | ||
return queue; | ||
} | ||
wildcardUpdate(updatePath, fn, options = defaultUpdateOptions, multi = false) { | ||
++this.jobsRunning; | ||
options = Object.assign({}, defaultUpdateOptions, options); | ||
options = Object.assign(Object.assign({}, defaultUpdateOptions), options); | ||
const scanned = this.scan.get(updatePath); | ||
@@ -1284,16 +1264,14 @@ const updated = {}; | ||
const groupedListenersPack = []; | ||
const waitingPaths = []; | ||
for (const path in updated) { | ||
const newValue = updated[path]; | ||
if (options.only.length) { | ||
groupedListenersPack.push(this.getNotifyOnlyListeners(path, newValue, options, 'update', updatePath)); | ||
groupedListenersPack.push(this.getNotifyOnlyListeners(path, newValue, options, "update", updatePath)); | ||
} | ||
else { | ||
groupedListenersPack.push(this.getSubscribedListeners(path, newValue, options, 'update', updatePath)); | ||
groupedListenersPack.push(this.getSubscribedListeners(path, newValue, options, "update", updatePath)); | ||
if (this.canBeNested(newValue)) { | ||
groupedListenersPack.push(this.getNestedListeners(path, newValue, options, 'update', updatePath)); | ||
groupedListenersPack.push(this.getNestedListeners(path, newValue, options, "update", updatePath)); | ||
} | ||
} | ||
options.debug && this.options.log('Wildcard update', { path, newValue }); | ||
waitingPaths.push(path); | ||
options.debug && this.options.log("Wildcard update", { path, newValue }); | ||
} | ||
@@ -1303,26 +1281,15 @@ if (multi) { | ||
return function () { | ||
const queue = self.wildcardNotify(groupedListenersPack, waitingPaths); | ||
const queue = self.wildcardNotify(groupedListenersPack); | ||
self.sortAndRunQueue(queue, updatePath); | ||
}; | ||
} | ||
const queue = this.wildcardNotify(groupedListenersPack, waitingPaths); | ||
const queue = this.wildcardNotify(groupedListenersPack); | ||
this.sortAndRunQueue(queue, updatePath); | ||
} | ||
runUpdateQueue() { | ||
if (this.destroyed) | ||
return; | ||
while (this.updateQueue.length && | ||
this.updateQueue.length < this.options.maxSimultaneousJobs) { | ||
const params = this.updateQueue.shift(); | ||
params.options.queue = false; // prevent infinite loop | ||
this.update(params.updatePath, params.fnOrValue, params.options, params.multi); | ||
} | ||
} | ||
updateNotify(updatePath, newValue, options) { | ||
const queue = this.notifySubscribedListeners(updatePath, newValue, options); | ||
if (this.canBeNested(newValue)) { | ||
this.notifyNestedListeners(updatePath, newValue, options, 'update', queue); | ||
this.notifyNestedListeners(updatePath, newValue, options, "update", queue); | ||
} | ||
this.sortAndRunQueue(queue, updatePath); | ||
this.executeWaitingListeners(updatePath); | ||
} | ||
@@ -1346,3 +1313,3 @@ updateNotifyAll(updateStack) { | ||
if (this.canBeNested(current.newValue)) { | ||
this.notifyNestedListeners(current.updatePath, value, current.options, 'update', queue); | ||
this.notifyNestedListeners(current.updatePath, value, current.options, "update", queue); | ||
} | ||
@@ -1354,3 +1321,2 @@ } | ||
this.notifyOnly(updatePath, newValue, options); | ||
this.executeWaitingListeners(updatePath); | ||
} | ||
@@ -1369,26 +1335,9 @@ update(updatePath, fnOrValue, options = Object.assign({}, defaultUpdateOptions), multi = false) { | ||
} | ||
const jobsRunning = this.jobsRunning; | ||
if ((this.options.queue || options.queue) && jobsRunning) { | ||
if (jobsRunning > this.options.maxSimultaneousJobs) { | ||
throw new Error('Maximal simultaneous jobs limit reached.'); | ||
} | ||
this.updateQueue.push({ updatePath, fnOrValue, options, multi }); | ||
const result = Promise.resolve().then(() => { | ||
this.runUpdateQueue(); | ||
}); | ||
if (multi) { | ||
return function () { | ||
return result; | ||
}; | ||
} | ||
return result; | ||
} | ||
if (this.isWildcard(updatePath)) { | ||
return this.wildcardUpdate(updatePath, fnOrValue, options, multi); | ||
} | ||
++this.jobsRunning; | ||
const split = this.split(updatePath); | ||
const { oldValue, newValue } = this.getUpdateValues(this.pathGet(split, this.data), split, fnOrValue); | ||
if (options.debug) { | ||
this.options.log(`Updating ${updatePath} ${options.source ? `from ${options.source}` : ''}`, { | ||
this.options.log(`Updating ${updatePath} ${options.source ? `from ${options.source}` : ""}`, { | ||
oldValue, | ||
@@ -1399,3 +1348,2 @@ newValue, | ||
if (this.same(newValue, oldValue) && !options.force) { | ||
--this.jobsRunning; | ||
if (multi) | ||
@@ -1408,5 +1356,4 @@ return function () { | ||
this.pathSet(split, newValue, this.data); | ||
options = Object.assign({}, defaultUpdateOptions, options); | ||
options = Object.assign(Object.assign({}, defaultUpdateOptions), options); | ||
if (options.only === null) { | ||
--this.jobsRunning; | ||
if (multi) | ||
@@ -1417,3 +1364,2 @@ return function () { }; | ||
if (options.only.length) { | ||
--this.jobsRunning; | ||
if (multi) { | ||
@@ -1429,3 +1375,2 @@ const self = this; | ||
if (multi) { | ||
--this.jobsRunning; | ||
const self = this; | ||
@@ -1437,3 +1382,2 @@ return function multiUpdate() { | ||
this.updateNotify(updatePath, newValue, options); | ||
--this.jobsRunning; | ||
return newValue; | ||
@@ -1462,3 +1406,3 @@ } | ||
let value = fnOrValue; | ||
if (typeof value === 'function') { | ||
if (typeof value === "function") { | ||
value = value(self.pathGet(split, self.data)); | ||
@@ -1520,3 +1464,3 @@ } | ||
return; | ||
if (typeof userPath === 'undefined' || userPath === '') { | ||
if (typeof userPath === "undefined" || userPath === "") { | ||
return this.data; | ||
@@ -1543,3 +1487,3 @@ } | ||
return false; | ||
if (typeof pathOrListenerFunction === 'function') { | ||
if (typeof pathOrListenerFunction === "function") { | ||
return this.isMutedListener(pathOrListenerFunction); | ||
@@ -1568,3 +1512,3 @@ } | ||
mute(pathOrListenerFunction) { | ||
if (typeof pathOrListenerFunction === 'function') { | ||
if (typeof pathOrListenerFunction === "function") { | ||
return this.mutedListeners.add(pathOrListenerFunction); | ||
@@ -1575,3 +1519,3 @@ } | ||
unmute(pathOrListenerFunction) { | ||
if (typeof pathOrListenerFunction === 'function') { | ||
if (typeof pathOrListenerFunction === "function") { | ||
return this.mutedListeners.delete(pathOrListenerFunction); | ||
@@ -1583,3 +1527,3 @@ } | ||
if (listener.options.debug) { | ||
this.options.log('listener subscribed', { | ||
this.options.log("listener subscribed", { | ||
listenerPath, | ||
@@ -1592,5 +1536,4 @@ listener, | ||
debugListener(time, groupedListener) { | ||
if (groupedListener.eventInfo.options.debug || | ||
groupedListener.listener.options.debug) { | ||
this.options.log('Listener fired', { | ||
if (groupedListener.eventInfo.options.debug || groupedListener.listener.options.debug) { | ||
this.options.log("Listener fired", { | ||
time: Date.now() - time, | ||
@@ -1602,10 +1545,7 @@ info: groupedListener, | ||
debugTime(groupedListener) { | ||
return groupedListener.listener.options.debug || | ||
groupedListener.eventInfo.options.debug | ||
? Date.now() | ||
: 0; | ||
return groupedListener.listener.options.debug || groupedListener.eventInfo.options.debug ? Date.now() : 0; | ||
} | ||
startTrace(name, additionalData = null) { | ||
this.traceId++; | ||
const id = this.traceId + ':' + name; | ||
const id = this.traceId + ":" + name; | ||
this.traceMap.set(id, { | ||
@@ -1644,2 +1584,2 @@ id, | ||
export default DeepState; | ||
export { DeepState as default }; |
723
index.ts
@@ -1,4 +0,4 @@ | ||
import WildcardObject from './wildcard-object-scan'; | ||
import Path from './ObjectPath'; | ||
import init, { is_match } from './wildcard_matcher.js'; | ||
import WildcardObject from "./wildcard-object-scan"; | ||
import Path from "./ObjectPath"; | ||
import init, { is_match } from "./wildcard_matcher.js"; | ||
@@ -20,6 +20,3 @@ export interface PathInfo { | ||
export type ListenerFunction = ( | ||
value: any, | ||
eventInfo: ListenerFunctionEventInfo | ||
) => void; | ||
export type ListenerFunction = (value: any, eventInfo: ListenerFunctionEventInfo) => void; | ||
export type Match = (path: string, debug?: boolean) => boolean; | ||
@@ -34,3 +31,2 @@ | ||
experimentalMatch?: boolean; | ||
queue?: boolean; | ||
maxSimultaneousJobs?: number; | ||
@@ -49,3 +45,2 @@ maxQueueRuns?: number; | ||
data?: any; | ||
queue?: boolean; | ||
ignore?: string[]; | ||
@@ -177,6 +172,5 @@ group?: boolean; | ||
only: [], | ||
source: '', | ||
source: "", | ||
debug: false, | ||
data: undefined, | ||
queue: false, | ||
force: false, | ||
@@ -186,7 +180,3 @@ }; | ||
export interface Multi { | ||
update: ( | ||
updatePath: string, | ||
fn: Updater | any, | ||
options?: UpdateOptions | ||
) => Multi; | ||
update: (updatePath: string, fn: Updater | any, options?: UpdateOptions) => Multi; | ||
done: () => void; | ||
@@ -200,2 +190,16 @@ getStack: () => UpdateStack[]; | ||
/** | ||
* Is object - helper function to determine if specified variable is an object | ||
* | ||
* @param {any} item | ||
* @returns {boolean} | ||
*/ | ||
function isObject(item: unknown) { | ||
return item && typeof item === "object" && item !== null && item.constructor && item.constructor.name === "Object"; | ||
} | ||
export interface UnknownObject { | ||
[key: string]: unknown; | ||
} | ||
function getDefaultOptions(): Options { | ||
@@ -211,3 +215,2 @@ return { | ||
experimentalMatch: false, | ||
queue: false, | ||
maxSimultaneousJobs: 1000, | ||
@@ -223,18 +226,9 @@ maxQueueRuns: 1000, | ||
debug: false, | ||
source: '', | ||
source: "", | ||
data: undefined, | ||
queue: false, | ||
group: false, | ||
}; | ||
const handler = { | ||
get(obj, prop) {}, | ||
set(obj, prop, value) { | ||
return true; | ||
}, | ||
}; | ||
class DeepState<T> { | ||
private listeners: Listeners; | ||
private waitingListeners: WaitingListeners; | ||
private data: T | object; | ||
@@ -246,12 +240,6 @@ private options: Options; | ||
private scan: any; | ||
private jobsRunning = 0; | ||
private updateQueue = []; | ||
private subscribeQueue = []; | ||
private listenersIgnoreCache: WeakMap< | ||
Listener, | ||
{ truthy: string[]; falsy: string[] } | ||
> = new WeakMap(); | ||
private listenersIgnoreCache: WeakMap<Listener, { truthy: string[]; falsy: string[] }> = new WeakMap(); | ||
private is_match: any; | ||
private destroyed = false; | ||
private queueRuns = 0; | ||
private resolved: Promise<unknown> | any; | ||
@@ -267,9 +255,48 @@ private muted: Set<string>; | ||
private collections: number = 0; | ||
public proxy; | ||
private proxyPath = []; | ||
private handler = { | ||
get: (obj, prop) => { | ||
if (obj.hasOwnProperty(prop)) this.proxyPath.push(prop); | ||
return obj[prop]; | ||
}, | ||
set: (obj, prop, value) => { | ||
this.proxyPath.push(prop); | ||
const path = this.proxyPath.join("."); | ||
if (typeof value === "function") { | ||
value = value(obj[prop]); | ||
} | ||
let final = value; | ||
if (isObject(value)) { | ||
final = new Proxy(this.mergeDeepProxy({}, value), this.handler); | ||
} else if (Array.isArray(value)) { | ||
final = new Proxy(this.mergeDeepProxy([], value), this.handler); | ||
} | ||
this.proxyPath = []; | ||
this.update(path, final); | ||
obj[prop] = final; | ||
return true; | ||
}, | ||
}; | ||
public proxy: T; | ||
/** | ||
* @property $$$ proxy shorthand | ||
*/ | ||
public $$$: T; | ||
constructor(data: T | object = {}, options: Options = {}) { | ||
this.listeners = new Map(); | ||
this.waitingListeners = new Map(); | ||
this.handler.get = this.handler.get.bind(this); | ||
this.handler.set = this.handler.set.bind(this); | ||
const self = this; | ||
this.data = data; | ||
this.proxy = new Proxy(this.data, handler); | ||
this.proxy = new Proxy(this.mergeDeepProxy({}, data) as object, { | ||
get(obj, prop) { | ||
if (obj.hasOwnProperty(prop)) self.proxyPath = [prop]; | ||
return obj[prop]; | ||
}, | ||
set(obj, prop, value) { | ||
return self.handler.set(obj, prop, value); | ||
}, | ||
}) as unknown as T; | ||
this.$$$ = this.proxy; | ||
this.options = { ...getDefaultOptions(), ...options }; | ||
@@ -286,19 +313,59 @@ this.id = 0; | ||
this.mutedListeners = new Set(); | ||
this.scan = new WildcardObject<T>( | ||
this.data, | ||
this.options.delimiter, | ||
this.options.wildcard | ||
); | ||
this.scan = new WildcardObject<T>(this.data, this.options.delimiter, this.options.wildcard); | ||
this.destroyed = false; | ||
} | ||
private mergeDeepProxy<T>(target: any, ...sources: any[]): T { | ||
const source = sources.shift(); | ||
if (isObject(target) && isObject(source)) { | ||
const targetValMain = {}; | ||
for (const key in source) { | ||
if (isObject(source[key])) { | ||
targetValMain[key] = new Proxy(this.mergeDeepProxy({}, source[key]), this.handler); | ||
} else if (Array.isArray(source[key])) { | ||
const targetVal = new Array(source[key].length); | ||
let index = 0; | ||
for (let item of source[key]) { | ||
if (isObject(item) || Array.isArray(item)) { | ||
targetVal[index] = new Proxy(this.mergeDeepProxy({}, item), this.handler); | ||
} else { | ||
targetVal[index] = item; | ||
} | ||
index++; | ||
} | ||
targetValMain[key] = new Proxy(targetVal, this.handler); | ||
} else { | ||
targetValMain[key] = source[key]; | ||
} | ||
} | ||
if (sources.length) { | ||
target = new Proxy(targetValMain, this.handler); | ||
} else { | ||
target = targetValMain; | ||
} | ||
} else if (Array.isArray(source)) { | ||
const targetVal = new Array(source.length); | ||
for (let i = 0, len = source.length; i < len; i++) { | ||
if (isObject(source[i]) || Array.isArray(source[i])) { | ||
targetVal[i] = this.mergeDeepProxy(source[i]); | ||
} else { | ||
targetVal[i] = source[i]; | ||
} | ||
} | ||
if (sources.length) { | ||
target = new Proxy(targetVal, this.handler); | ||
} else { | ||
target = targetVal; | ||
} | ||
} | ||
if (!sources.length) { | ||
return target; | ||
} | ||
return this.mergeDeepProxy(target, ...sources); | ||
} | ||
public async loadWasmMatcher(pathToWasmFile: string) { | ||
await init(pathToWasmFile); | ||
this.is_match = is_match; | ||
this.scan = new WildcardObject( | ||
this.data, | ||
this.options.delimiter, | ||
this.options.wildcard, | ||
this.is_match | ||
); | ||
this.scan = new WildcardObject(this.data, this.options.delimiter, this.options.wildcard, this.is_match); | ||
} | ||
@@ -308,4 +375,3 @@ | ||
return ( | ||
(['number', 'string', 'undefined', 'boolean'].includes(typeof newValue) || | ||
newValue === null) && | ||
(["number", "string", "undefined", "boolean"].includes(typeof newValue) || newValue === null) && | ||
oldValue === newValue | ||
@@ -323,19 +389,11 @@ ); | ||
this.listeners = new Map(); | ||
this.updateQueue = []; | ||
this.jobsRunning = 0; | ||
} | ||
private match( | ||
first: string, | ||
second: string, | ||
nested: boolean = true | ||
): boolean { | ||
private match(first: string, second: string, nested: boolean = true): boolean { | ||
if (this.is_match) return this.is_match(first, second); | ||
if (first === second) return true; | ||
if (first === this.options.wildcard || second === this.options.wildcard) | ||
return true; | ||
if (first === this.options.wildcard || second === this.options.wildcard) return true; | ||
if ( | ||
!nested && | ||
this.getIndicesCount(this.options.delimiter, first) < | ||
this.getIndicesCount(this.options.delimiter, second) | ||
this.getIndicesCount(this.options.delimiter, first) < this.getIndicesCount(this.options.delimiter, second) | ||
) { | ||
@@ -382,6 +440,3 @@ // first < second because first is a listener path and may be longer but not shorter | ||
if (longer === shorter) return longer; | ||
const shorterPartsLen = this.getIndicesCount( | ||
this.options.delimiter, | ||
shorter | ||
); | ||
const shorterPartsLen = this.getIndicesCount(this.options.delimiter, shorter); | ||
const longerParts = this.getIndicesOf(this.options.delimiter, longer); | ||
@@ -400,3 +455,3 @@ return longer.substr(0, longerParts[shorterPartsLen]); | ||
private split(path: string) { | ||
return path === '' ? [] : path.split(this.options.delimiter); | ||
return path === "" ? [] : path.split(this.options.delimiter); | ||
} | ||
@@ -413,5 +468,3 @@ | ||
private cleanNotRecursivePath(path: string): string { | ||
return this.isNotRecursive(path) | ||
? path.substring(0, path.length - 1) | ||
: path; | ||
return this.isNotRecursive(path) ? path.substring(0, path.length - 1) : path; | ||
} | ||
@@ -424,3 +477,3 @@ | ||
private getParamsInfo(path: string): ParamsInfo { | ||
let paramsInfo: ParamsInfo = { replaced: '', original: path, params: {} }; | ||
let paramsInfo: ParamsInfo = { replaced: "", original: path, params: {} }; | ||
let partIndex = 0; | ||
@@ -431,9 +484,6 @@ let fullReplaced = []; | ||
original: part, | ||
replaced: '', | ||
name: '', | ||
replaced: "", | ||
name: "", | ||
}; | ||
const reg = new RegExp( | ||
`\\${this.options.param}([^\\${this.options.delimiter}\\${this.options.param}]+)`, | ||
'g' | ||
); | ||
const reg = new RegExp(`\\${this.options.param}([^\\${this.options.delimiter}\\${this.options.param}]+)`, "g"); | ||
let param = reg.exec(part); | ||
@@ -449,6 +499,3 @@ if (param) { | ||
reg.lastIndex = 0; | ||
paramsInfo.params[partIndex].replaced = part.replace( | ||
reg, | ||
this.options.wildcard | ||
); | ||
paramsInfo.params[partIndex].replaced = part.replace(reg, this.options.wildcard); | ||
fullReplaced.push(paramsInfo.params[partIndex].replaced); | ||
@@ -474,45 +521,2 @@ partIndex++; | ||
public waitForAll(userPaths: string[], fn: WaitingListenerFunction) { | ||
const paths = {}; | ||
for (let path of userPaths) { | ||
paths[path] = { dirty: false }; | ||
if (this.hasParams(path)) { | ||
paths[path].paramsInfo = this.getParamsInfo(path); | ||
} | ||
paths[path].isWildcard = this.isWildcard(path); | ||
paths[path].isRecursive = !this.isNotRecursive(path); | ||
} | ||
this.waitingListeners.set(userPaths, { fn, paths }); | ||
fn(paths); | ||
return function unsubscribe() { | ||
this.waitingListeners.delete(userPaths); | ||
}; | ||
} | ||
private executeWaitingListeners(updatePath: string) { | ||
if (this.destroyed) return; | ||
for (const waitingListener of this.waitingListeners.values()) { | ||
const { fn, paths } = waitingListener; | ||
let dirty = 0; | ||
let all = 0; | ||
for (let path in paths) { | ||
const pathInfo = paths[path]; | ||
let match = false; | ||
if (pathInfo.isRecursive) updatePath = this.cutPath(updatePath, path); | ||
if (pathInfo.isWildcard && this.match(path, updatePath)) match = true; | ||
if (updatePath === path) match = true; | ||
if (match) { | ||
pathInfo.dirty = true; | ||
} | ||
if (pathInfo.dirty) { | ||
dirty++; | ||
} | ||
all++; | ||
} | ||
if (dirty === all) { | ||
fn(paths); | ||
} | ||
} | ||
} | ||
public subscribeAll( | ||
@@ -562,6 +566,3 @@ userPaths: string[], | ||
private getCleanListener( | ||
fn: ListenerFunction, | ||
options: ListenerOptions = defaultListenerOptions | ||
): Listener { | ||
private getCleanListener(fn: ListenerFunction, options: ListenerOptions = defaultListenerOptions): Listener { | ||
return { | ||
@@ -574,7 +575,3 @@ fn, | ||
private getListenerCollectionMatch( | ||
listenerPath: string, | ||
isRecursive: boolean, | ||
isWildcard: boolean | ||
) { | ||
private getListenerCollectionMatch(listenerPath: string, isRecursive: boolean, isWildcard: boolean) { | ||
listenerPath = this.cleanNotRecursivePath(listenerPath); | ||
@@ -587,9 +584,6 @@ const self = this; | ||
} else { | ||
scopedListenerPath = self.cutPath( | ||
self.cleanNotRecursivePath(listenerPath), | ||
path | ||
); | ||
scopedListenerPath = self.cutPath(self.cleanNotRecursivePath(listenerPath), path); | ||
} | ||
if (debug) { | ||
console.log('[getListenerCollectionMatch]', { | ||
console.log("[getListenerCollectionMatch]", { | ||
listenerPath, | ||
@@ -602,4 +596,3 @@ scopedListenerPath, | ||
} | ||
if (isWildcard && self.match(scopedListenerPath, path, isRecursive)) | ||
return true; | ||
if (isWildcard && self.match(scopedListenerPath, path, isRecursive)) return true; | ||
return scopedListenerPath === path; | ||
@@ -609,6 +602,3 @@ }; | ||
private getListenersCollection( | ||
listenerPath: string, | ||
listener: Listener | ||
): ListenersCollection { | ||
private getListenersCollection(listenerPath: string, listener: Listener): ListenersCollection { | ||
if (this.listeners.has(listenerPath)) { | ||
@@ -638,7 +628,3 @@ let listenersCollection = this.listeners.get(listenerPath); | ||
...collCfg, | ||
match: this.getListenerCollectionMatch( | ||
collCfg.path, | ||
collCfg.isRecursive, | ||
collCfg.isWildcard | ||
), | ||
match: this.getListenerCollectionMatch(collCfg.path, collCfg.isRecursive, collCfg.isWildcard), | ||
}); | ||
@@ -663,11 +649,7 @@ this.id++; | ||
if (this.destroyed) return () => {}; | ||
this.jobsRunning++; | ||
const type = 'subscribe'; | ||
const type = "subscribe"; | ||
let listener = this.getCleanListener(fn, options); | ||
if (options.group) listener.groupId = subscribeAllOptions.groupId; | ||
this.listenersIgnoreCache.set(listener, { truthy: [], falsy: [] }); | ||
const listenersCollection = this.getListenersCollection( | ||
listenerPath, | ||
listener | ||
); | ||
const listenersCollection = this.getListenersCollection(listenerPath, listener); | ||
if (options.debug) { | ||
@@ -677,7 +659,3 @@ console.log(); | ||
listenersCollection.count++; | ||
if ( | ||
!options.group || | ||
(options.group && | ||
subscribeAllOptions.all.length - 1 === subscribeAllOptions.index) | ||
) { | ||
if (!options.group || (options.group && subscribeAllOptions.all.length - 1 === subscribeAllOptions.index)) { | ||
const cleanPath = this.cleanNotRecursivePath(listenersCollection.path); | ||
@@ -746,3 +724,2 @@ if (!listenersCollection.isWildcard) { | ||
this.debugSubscribe(listener, listenersCollection, listenerPath); | ||
this.jobsRunning--; | ||
return this.unsubscribe(listenerPath, this.id); | ||
@@ -766,28 +743,10 @@ } | ||
if (this.subscribeQueue.length === 0) return; | ||
if (this.jobsRunning === 0) { | ||
this.queueRuns = 0; | ||
const queue = [...this.subscribeQueue]; | ||
for (let i = 0, len = queue.length; i < len; i++) { | ||
queue[i](); | ||
} | ||
this.subscribeQueue.length = 0; | ||
} else { | ||
this.queueRuns++; | ||
if (this.queueRuns >= this.options.maxQueueRuns) { | ||
this.queueRuns = 0; | ||
throw new Error('Maximal number of queue runs exhausted.'); | ||
} else { | ||
Promise.resolve() | ||
.then(() => this.runQueuedListeners()) | ||
.catch((e) => { | ||
throw e; | ||
}); | ||
} | ||
const queue = [...this.subscribeQueue]; | ||
for (let i = 0, len = queue.length; i < len; i++) { | ||
queue[i](); | ||
} | ||
this.subscribeQueue.length = 0; | ||
} | ||
private getQueueNotifyListeners( | ||
groupedListeners: GroupedListeners, | ||
queue: Queue[] = [] | ||
): Queue[] { | ||
private getQueueNotifyListeners(groupedListeners: GroupedListeners, queue: Queue[] = []): Queue[] { | ||
for (const path in groupedListeners) { | ||
@@ -798,11 +757,5 @@ if (this.isMuted(path)) continue; | ||
let alreadyInQueue = false; | ||
let resolvedIdPath = | ||
singleListener.listener.id + | ||
':' + | ||
singleListener.eventInfo.path.resolved; | ||
let resolvedIdPath = singleListener.listener.id + ":" + singleListener.eventInfo.path.resolved; | ||
if (!singleListener.eventInfo.path.resolved) { | ||
resolvedIdPath = | ||
singleListener.listener.id + | ||
':' + | ||
singleListener.eventInfo.path.listener; | ||
resolvedIdPath = singleListener.listener.id + ":" + singleListener.eventInfo.path.listener; | ||
} | ||
@@ -820,35 +773,17 @@ for (const excludedListener of queue) { | ||
if (!this.isMuted(singleListener.listener.fn)) { | ||
if (singleListener.listener.options.queue && this.jobsRunning) { | ||
this.subscribeQueue.push(() => { | ||
singleListener.listener.fn( | ||
singleListener.value(), | ||
singleListener.eventInfo | ||
); | ||
}); | ||
} else { | ||
let resolvedIdPath = | ||
singleListener.listener.id + | ||
':' + | ||
singleListener.eventInfo.path.resolved; | ||
if (!singleListener.eventInfo.path.resolved) { | ||
resolvedIdPath = | ||
singleListener.listener.id + | ||
':' + | ||
singleListener.eventInfo.path.listener; | ||
} | ||
queue.push({ | ||
id: singleListener.listener.id, | ||
resolvedPath: singleListener.eventInfo.path.resolved, | ||
resolvedIdPath, | ||
originalFn: singleListener.listener.fn, | ||
fn: () => { | ||
singleListener.listener.fn( | ||
singleListener.value(), | ||
singleListener.eventInfo | ||
); | ||
}, | ||
options: singleListener.listener.options, | ||
groupId: singleListener.listener.groupId, | ||
}); | ||
let resolvedIdPath = singleListener.listener.id + ":" + singleListener.eventInfo.path.resolved; | ||
if (!singleListener.eventInfo.path.resolved) { | ||
resolvedIdPath = singleListener.listener.id + ":" + singleListener.eventInfo.path.listener; | ||
} | ||
queue.push({ | ||
id: singleListener.listener.id, | ||
resolvedPath: singleListener.eventInfo.path.resolved, | ||
resolvedIdPath, | ||
originalFn: singleListener.listener.fn, | ||
fn: () => { | ||
singleListener.listener.fn(singleListener.value(), singleListener.eventInfo); | ||
}, | ||
options: singleListener.listener.options, | ||
groupId: singleListener.listener.groupId, | ||
}); | ||
} | ||
@@ -873,33 +808,17 @@ this.debugListener(time, singleListener); | ||
if (!this.isMuted(bulkListener.listener.fn)) { | ||
if (bulkListener.listener.options.queue && this.jobsRunning) { | ||
this.subscribeQueue.push(() => { | ||
if (!this.jobsRunning) { | ||
bulkListener.listener.fn(bulkValue, bulkListener.eventInfo); | ||
return true; | ||
} | ||
return false; | ||
}); | ||
} else { | ||
let resolvedIdPath = | ||
bulkListener.listener.id + | ||
':' + | ||
bulkListener.eventInfo.path.resolved; | ||
if (!bulkListener.eventInfo.path.resolved) { | ||
resolvedIdPath = | ||
bulkListener.listener.id + | ||
':' + | ||
bulkListener.eventInfo.path.listener; | ||
} | ||
queue.push({ | ||
id: bulkListener.listener.id, | ||
resolvedPath: bulkListener.eventInfo.path.resolved, | ||
resolvedIdPath, | ||
originalFn: bulkListener.listener.fn, | ||
fn: () => { | ||
bulkListener.listener.fn(bulkValue, bulkListener.eventInfo); | ||
}, | ||
options: bulkListener.listener.options, | ||
groupId: bulkListener.listener.groupId, | ||
}); | ||
let resolvedIdPath = bulkListener.listener.id + ":" + bulkListener.eventInfo.path.resolved; | ||
if (!bulkListener.eventInfo.path.resolved) { | ||
resolvedIdPath = bulkListener.listener.id + ":" + bulkListener.eventInfo.path.listener; | ||
} | ||
queue.push({ | ||
id: bulkListener.listener.id, | ||
resolvedPath: bulkListener.eventInfo.path.resolved, | ||
resolvedIdPath, | ||
originalFn: bulkListener.listener.fn, | ||
fn: () => { | ||
bulkListener.listener.fn(bulkValue, bulkListener.eventInfo); | ||
}, | ||
options: bulkListener.listener.options, | ||
groupId: bulkListener.listener.groupId, | ||
}); | ||
} | ||
@@ -935,3 +854,3 @@ this.debugListener(time, bulkListener); | ||
options: UpdateOptions, | ||
type: string = 'update', | ||
type: string = "update", | ||
originalPath: string = null | ||
@@ -948,4 +867,3 @@ ): GroupedListeners { | ||
const cutPath = this.cutPath(updatePath, listenerPath); | ||
const traverse = | ||
listenersCollection.isRecursive || listenersCollection.isWildcard; | ||
const traverse = listenersCollection.isRecursive || listenersCollection.isWildcard; | ||
const value = traverse ? () => this.get(cutPath) : () => newValue; | ||
@@ -956,9 +874,6 @@ const bulkValue = [{ value, path: updatePath, params }]; | ||
if (listener.options.debug) { | ||
console.log( | ||
`[getSubscribedListeners] Listener was not fired because it was ignored.`, | ||
{ | ||
listener, | ||
listenersCollection, | ||
} | ||
); | ||
console.log(`[getSubscribedListeners] Listener was not fired because it was ignored.`, { | ||
listener, | ||
listenersCollection, | ||
}); | ||
} | ||
@@ -1009,10 +924,7 @@ continue; | ||
showMatch = true; | ||
console.log( | ||
`[getSubscribedListeners] Listener was not fired because there was no match.`, | ||
{ | ||
listener, | ||
listenersCollection, | ||
updatePath, | ||
} | ||
); | ||
console.log(`[getSubscribedListeners] Listener was not fired because there was no match.`, { | ||
listener, | ||
listenersCollection, | ||
updatePath, | ||
}); | ||
} | ||
@@ -1032,14 +944,6 @@ } | ||
options: UpdateOptions, | ||
type: string = 'update', | ||
type: string = "update", | ||
originalPath: string = null | ||
): Queue[] { | ||
return this.getQueueNotifyListeners( | ||
this.getSubscribedListeners( | ||
updatePath, | ||
newValue, | ||
options, | ||
type, | ||
originalPath | ||
) | ||
); | ||
return this.getQueueNotifyListeners(this.getSubscribedListeners(updatePath, newValue, options, type, originalPath)); | ||
} | ||
@@ -1051,3 +955,3 @@ | ||
options: UpdateOptions, | ||
type: string = 'update', | ||
type: string = "update", | ||
originalPath: string = null | ||
@@ -1061,10 +965,6 @@ ): GroupedListeners { | ||
if (this.match(currentCutPath, updatePath)) { | ||
const restPath = this.trimPath( | ||
listenerPath.substr(currentCutPath.length) | ||
const restPath = this.trimPath(listenerPath.substr(currentCutPath.length)); | ||
const wildcardNewValues = new WildcardObject(newValue, this.options.delimiter, this.options.wildcard).get( | ||
restPath | ||
); | ||
const wildcardNewValues = new WildcardObject( | ||
newValue, | ||
this.options.delimiter, | ||
this.options.wildcard | ||
).get(restPath); | ||
const params = listenersCollection.paramsInfo | ||
@@ -1077,5 +977,3 @@ ? this.getParams(listenersCollection.paramsInfo, updatePath) | ||
const value = () => wildcardNewValues[currentRestPath]; | ||
const fullPath = [updatePath, currentRestPath].join( | ||
this.options.delimiter | ||
); | ||
const fullPath = [updatePath, currentRestPath].join(this.options.delimiter); | ||
for (const [listenerId, listener] of listenersCollection.listeners) { | ||
@@ -1133,11 +1031,8 @@ const eventInfo = { | ||
if (listener.options.debug) { | ||
console.log( | ||
'[getNestedListeners] Listener was not fired because there was no match.', | ||
{ | ||
listener, | ||
listenersCollection, | ||
currentCutPath, | ||
updatePath, | ||
} | ||
); | ||
console.log("[getNestedListeners] Listener was not fired because there was no match.", { | ||
listener, | ||
listenersCollection, | ||
currentCutPath, | ||
updatePath, | ||
}); | ||
} | ||
@@ -1154,3 +1049,3 @@ } | ||
options: UpdateOptions, | ||
type: string = 'update', | ||
type: string = "update", | ||
queue: Queue[], | ||
@@ -1160,9 +1055,3 @@ originalPath: string = null | ||
return this.getQueueNotifyListeners( | ||
this.getNestedListeners( | ||
updatePath, | ||
newValue, | ||
options, | ||
type, | ||
originalPath | ||
), | ||
this.getNestedListeners(updatePath, newValue, options, type, originalPath), | ||
queue | ||
@@ -1176,3 +1065,3 @@ ); | ||
options: UpdateOptions, | ||
type: string = 'update', | ||
type: string = "update", | ||
originalPath: string = null | ||
@@ -1182,5 +1071,5 @@ ): GroupedListeners { | ||
if ( | ||
typeof options.only !== 'object' || | ||
typeof options.only !== "object" || | ||
!Array.isArray(options.only) || | ||
typeof options.only[0] === 'undefined' || | ||
typeof options.only[0] === "undefined" || | ||
!this.canBeNested(newValue) | ||
@@ -1191,7 +1080,5 @@ ) { | ||
for (const notifyPath of options.only) { | ||
const wildcardScanNewValue = new WildcardObject( | ||
newValue, | ||
this.options.delimiter, | ||
this.options.wildcard | ||
).get(notifyPath); | ||
const wildcardScanNewValue = new WildcardObject(newValue, this.options.delimiter, this.options.wildcard).get( | ||
notifyPath | ||
); | ||
listeners[notifyPath] = { bulk: [], single: [] }; | ||
@@ -1222,7 +1109,3 @@ for (const wildcardPath in wildcardScanNewValue) { | ||
if (listener.options.bulk) { | ||
if ( | ||
!listeners[notifyPath].bulk.some( | ||
(bulkListener) => bulkListener.listener === listener | ||
) | ||
) { | ||
if (!listeners[notifyPath].bulk.some((bulkListener) => bulkListener.listener === listener)) { | ||
listeners[notifyPath].bulk.push({ | ||
@@ -1279,13 +1162,7 @@ listener, | ||
options: UpdateOptions, | ||
type: string = 'update', | ||
originalPath: string = '' | ||
type: string = "update", | ||
originalPath: string = "" | ||
) { | ||
const queue = this.getQueueNotifyListeners( | ||
this.getNotifyOnlyListeners( | ||
updatePath, | ||
newValue, | ||
options, | ||
type, | ||
originalPath | ||
) | ||
this.getNotifyOnlyListeners(updatePath, newValue, options, type, originalPath) | ||
); | ||
@@ -1296,3 +1173,3 @@ this.sortAndRunQueue(queue, updatePath); | ||
private canBeNested(newValue): boolean { | ||
return typeof newValue === 'object' && newValue !== null; | ||
return typeof newValue === "object" && newValue !== null; | ||
} | ||
@@ -1302,3 +1179,3 @@ | ||
let newValue = fn; | ||
if (typeof fn === 'function') { | ||
if (typeof fn === "function") { | ||
newValue = fn(this.pathGet(split, this.data)); | ||
@@ -1309,3 +1186,3 @@ } | ||
private wildcardNotify(groupedListenersPack, waitingPaths) { | ||
private wildcardNotify(groupedListenersPack) { | ||
let queue = []; | ||
@@ -1315,6 +1192,2 @@ for (const groupedListeners of groupedListenersPack) { | ||
} | ||
for (const path of waitingPaths) { | ||
this.executeWaitingListeners(path); | ||
} | ||
this.jobsRunning--; | ||
return queue; | ||
@@ -1329,3 +1202,2 @@ } | ||
) { | ||
++this.jobsRunning; | ||
options = { ...defaultUpdateOptions, ...options }; | ||
@@ -1336,7 +1208,3 @@ const scanned = this.scan.get(updatePath); | ||
const split = this.split(path); | ||
const { oldValue, newValue } = this.getUpdateValues( | ||
scanned[path], | ||
split, | ||
fn | ||
); | ||
const { oldValue, newValue } = this.getUpdateValues(scanned[path], split, fn); | ||
if (!this.same(newValue, oldValue) || options.force) { | ||
@@ -1349,39 +1217,13 @@ this.pathSet(split, newValue, this.data); | ||
const groupedListenersPack = []; | ||
const waitingPaths = []; | ||
for (const path in updated) { | ||
const newValue = updated[path]; | ||
if (options.only.length) { | ||
groupedListenersPack.push( | ||
this.getNotifyOnlyListeners( | ||
path, | ||
newValue, | ||
options, | ||
'update', | ||
updatePath | ||
) | ||
); | ||
groupedListenersPack.push(this.getNotifyOnlyListeners(path, newValue, options, "update", updatePath)); | ||
} else { | ||
groupedListenersPack.push( | ||
this.getSubscribedListeners( | ||
path, | ||
newValue, | ||
options, | ||
'update', | ||
updatePath | ||
) | ||
); | ||
groupedListenersPack.push(this.getSubscribedListeners(path, newValue, options, "update", updatePath)); | ||
if (this.canBeNested(newValue)) { | ||
groupedListenersPack.push( | ||
this.getNestedListeners( | ||
path, | ||
newValue, | ||
options, | ||
'update', | ||
updatePath | ||
) | ||
); | ||
groupedListenersPack.push(this.getNestedListeners(path, newValue, options, "update", updatePath)); | ||
} | ||
} | ||
options.debug && this.options.log('Wildcard update', { path, newValue }); | ||
waitingPaths.push(path); | ||
options.debug && this.options.log("Wildcard update", { path, newValue }); | ||
} | ||
@@ -1391,44 +1233,16 @@ if (multi) { | ||
return function () { | ||
const queue = self.wildcardNotify(groupedListenersPack, waitingPaths); | ||
const queue = self.wildcardNotify(groupedListenersPack); | ||
self.sortAndRunQueue(queue, updatePath); | ||
}; | ||
} | ||
const queue = this.wildcardNotify(groupedListenersPack, waitingPaths); | ||
const queue = this.wildcardNotify(groupedListenersPack); | ||
this.sortAndRunQueue(queue, updatePath); | ||
} | ||
private runUpdateQueue() { | ||
if (this.destroyed) return; | ||
while ( | ||
this.updateQueue.length && | ||
this.updateQueue.length < this.options.maxSimultaneousJobs | ||
) { | ||
const params = this.updateQueue.shift(); | ||
params.options.queue = false; // prevent infinite loop | ||
this.update( | ||
params.updatePath, | ||
params.fnOrValue, | ||
params.options, | ||
params.multi | ||
); | ||
} | ||
} | ||
private updateNotify( | ||
updatePath: string, | ||
newValue: unknown, | ||
options: UpdateOptions | ||
) { | ||
private updateNotify(updatePath: string, newValue: unknown, options: UpdateOptions) { | ||
const queue = this.notifySubscribedListeners(updatePath, newValue, options); | ||
if (this.canBeNested(newValue)) { | ||
this.notifyNestedListeners( | ||
updatePath, | ||
newValue, | ||
options, | ||
'update', | ||
queue | ||
); | ||
this.notifyNestedListeners(updatePath, newValue, options, "update", queue); | ||
} | ||
this.sortAndRunQueue(queue, updatePath); | ||
this.executeWaitingListeners(updatePath); | ||
} | ||
@@ -1451,17 +1265,5 @@ | ||
} | ||
queue = queue.concat( | ||
this.notifySubscribedListeners( | ||
current.updatePath, | ||
value, | ||
current.options | ||
) | ||
); | ||
queue = queue.concat(this.notifySubscribedListeners(current.updatePath, value, current.options)); | ||
if (this.canBeNested(current.newValue)) { | ||
this.notifyNestedListeners( | ||
current.updatePath, | ||
value, | ||
current.options, | ||
'update', | ||
queue | ||
); | ||
this.notifyNestedListeners(current.updatePath, value, current.options, "update", queue); | ||
} | ||
@@ -1474,3 +1276,2 @@ } | ||
this.notifyOnly(updatePath, newValue, options); | ||
this.executeWaitingListeners(updatePath); | ||
} | ||
@@ -1494,42 +1295,15 @@ | ||
} | ||
const jobsRunning = this.jobsRunning; | ||
if ((this.options.queue || options.queue) && jobsRunning) { | ||
if (jobsRunning > this.options.maxSimultaneousJobs) { | ||
throw new Error('Maximal simultaneous jobs limit reached.'); | ||
} | ||
this.updateQueue.push({ updatePath, fnOrValue, options, multi }); | ||
const result = Promise.resolve().then(() => { | ||
this.runUpdateQueue(); | ||
}); | ||
if (multi) { | ||
return function () { | ||
return result; | ||
}; | ||
} | ||
return result; | ||
} | ||
if (this.isWildcard(updatePath)) { | ||
return this.wildcardUpdate(updatePath, fnOrValue, options, multi); | ||
} | ||
++this.jobsRunning; | ||
const split = this.split(updatePath); | ||
const { oldValue, newValue } = this.getUpdateValues( | ||
this.pathGet(split, this.data), | ||
split, | ||
fnOrValue | ||
); | ||
const { oldValue, newValue } = this.getUpdateValues(this.pathGet(split, this.data), split, fnOrValue); | ||
if (options.debug) { | ||
this.options.log( | ||
`Updating ${updatePath} ${ | ||
options.source ? `from ${options.source}` : '' | ||
}`, | ||
{ | ||
oldValue, | ||
newValue, | ||
} | ||
); | ||
this.options.log(`Updating ${updatePath} ${options.source ? `from ${options.source}` : ""}`, { | ||
oldValue, | ||
newValue, | ||
}); | ||
} | ||
if (this.same(newValue, oldValue) && !options.force) { | ||
--this.jobsRunning; | ||
if (multi) | ||
@@ -1541,7 +1315,5 @@ return function () { | ||
} | ||
this.pathSet(split, newValue, this.data); | ||
options = { ...defaultUpdateOptions, ...options }; | ||
if (options.only === null) { | ||
--this.jobsRunning; | ||
if (multi) return function () {}; | ||
@@ -1551,3 +1323,2 @@ return newValue; | ||
if (options.only.length) { | ||
--this.jobsRunning; | ||
if (multi) { | ||
@@ -1563,3 +1334,2 @@ const self = this; | ||
if (multi) { | ||
--this.jobsRunning; | ||
const self = this; | ||
@@ -1571,3 +1341,2 @@ return function multiUpdate() { | ||
this.updateNotify(updatePath, newValue, options); | ||
--this.jobsRunning; | ||
return newValue; | ||
@@ -1592,11 +1361,7 @@ } | ||
const multiObject: Multi = { | ||
update( | ||
updatePath: string, | ||
fnOrValue: Updater | any, | ||
options: UpdateOptions = defaultUpdateOptions | ||
) { | ||
update(updatePath: string, fnOrValue: Updater | any, options: UpdateOptions = defaultUpdateOptions) { | ||
if (grouped) { | ||
const split = self.split(updatePath); | ||
let value = fnOrValue; | ||
if (typeof value === 'function') { | ||
if (typeof value === "function") { | ||
value = value(self.pathGet(split, self.data)); | ||
@@ -1659,3 +1424,3 @@ } | ||
if (this.destroyed) return; | ||
if (typeof userPath === 'undefined' || userPath === '') { | ||
if (typeof userPath === "undefined" || userPath === "") { | ||
return this.data; | ||
@@ -1684,3 +1449,3 @@ } | ||
if (!this.options.useMute) return false; | ||
if (typeof pathOrListenerFunction === 'function') { | ||
if (typeof pathOrListenerFunction === "function") { | ||
return this.isMutedListener(pathOrListenerFunction); | ||
@@ -1707,3 +1472,3 @@ } | ||
public mute(pathOrListenerFunction: string | ListenerFunction) { | ||
if (typeof pathOrListenerFunction === 'function') { | ||
if (typeof pathOrListenerFunction === "function") { | ||
return this.mutedListeners.add(pathOrListenerFunction); | ||
@@ -1715,3 +1480,3 @@ } | ||
public unmute(pathOrListenerFunction: string | ListenerFunction) { | ||
if (typeof pathOrListenerFunction === 'function') { | ||
if (typeof pathOrListenerFunction === "function") { | ||
return this.mutedListeners.delete(pathOrListenerFunction); | ||
@@ -1722,9 +1487,5 @@ } | ||
private debugSubscribe( | ||
listener: Listener, | ||
listenersCollection: ListenersCollection, | ||
listenerPath: string | ||
) { | ||
private debugSubscribe(listener: Listener, listenersCollection: ListenersCollection, listenerPath: string) { | ||
if (listener.options.debug) { | ||
this.options.log('listener subscribed', { | ||
this.options.log("listener subscribed", { | ||
listenerPath, | ||
@@ -1738,7 +1499,4 @@ listener, | ||
private debugListener(time: number, groupedListener: GroupedListener) { | ||
if ( | ||
groupedListener.eventInfo.options.debug || | ||
groupedListener.listener.options.debug | ||
) { | ||
this.options.log('Listener fired', { | ||
if (groupedListener.eventInfo.options.debug || groupedListener.listener.options.debug) { | ||
this.options.log("Listener fired", { | ||
time: Date.now() - time, | ||
@@ -1751,6 +1509,3 @@ info: groupedListener, | ||
private debugTime(groupedListener: GroupedListener): number { | ||
return groupedListener.listener.options.debug || | ||
groupedListener.eventInfo.options.debug | ||
? Date.now() | ||
: 0; | ||
return groupedListener.listener.options.debug || groupedListener.eventInfo.options.debug ? Date.now() : 0; | ||
} | ||
@@ -1760,3 +1515,3 @@ | ||
this.traceId++; | ||
const id = this.traceId + ':' + name; | ||
const id = this.traceId + ":" + name; | ||
this.traceMap.set(id, { | ||
@@ -1763,0 +1518,0 @@ id, |
"use strict"; | ||
var __values = (this && this.__values) || function (o) { | ||
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; | ||
var __values = (this && this.__values) || function(o) { | ||
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; | ||
if (m) return m.call(o); | ||
return { | ||
if (o && typeof o.length === "number") return { | ||
next: function () { | ||
@@ -11,2 +11,3 @@ if (o && i >= o.length) o = void 0; | ||
}; | ||
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); | ||
}; | ||
@@ -13,0 +14,0 @@ exports.__esModule = true; |
{ | ||
"name": "deep-state-observer", | ||
"version": "3.26.9", | ||
"version": "4.0.0", | ||
"description": "Deep state observer is an state management library that will fire listeners only when specified object node (which also can be a wildcard) was changed.", | ||
@@ -5,0 +5,0 @@ "main": "index.cjs.js", |
@@ -87,2 +87,31 @@ [![GitHub license](https://img.shields.io/github/license/neuronetio/deep-state-observer?style=flat-square)](https://github.com/neuronetio/deep-state-observer/blob/master/LICENSE) | ||
## Update and proxy | ||
You can use proxy instead of `state.update` function. | ||
Proxy is better because values are linted; | ||
```js | ||
const state = new State({ x: { y: { z: 10 } } }); | ||
state.update("x.y.z", 20); | ||
// is equivalent of | ||
state.proxy.x.y.z = 20; | ||
// is equivalent of (state.$$$ is just shorthand for state.proxy) | ||
state.$$$.x.y.z = 20; | ||
// and with function | ||
state.update("x.y.z", (value) => { | ||
return value + 10; | ||
}); | ||
// is equivalent of | ||
state.proxy.x.y.z = (value) => { | ||
return value + 10; | ||
}; | ||
// is equivalent of | ||
state.$$$.x.y.z = (value) => { | ||
return value + 10; | ||
}; | ||
``` | ||
## Wildcards | ||
@@ -406,22 +435,2 @@ | ||
## queue | ||
You can wait with update until all other tasks are finished. | ||
```javascript | ||
const state = new State({ test: 1, other: "x" }); | ||
const values = []; | ||
state.subscribe("test", (value) => { | ||
state.update("other", "xx", { queue: true }); | ||
values.push(value); | ||
}); | ||
// values.length === 1 & values[0] === 1 | ||
state.update("test", 2); | ||
// values.length ===2 & values[1] === 2 | ||
// state.get('other') === 'x' | ||
setTimeout(() => { | ||
// state.get('other') === 'xx' because updating 'other' was waiting for 'test' listener to end | ||
}, 100); | ||
``` | ||
## multi | ||
@@ -428,0 +437,0 @@ |
@@ -48,2 +48,16 @@ import resolve from "@rollup/plugin-node-resolve"; | ||
}, | ||
{ | ||
input: "test.ts", | ||
output: { | ||
file: "test.esm.js", | ||
format: "esm", | ||
}, | ||
plugins: [ | ||
typescript(), | ||
resolve({ | ||
browser: true, | ||
}), | ||
commonjs({ extensions: [".js", ".ts"] }), | ||
], | ||
}, | ||
]; |
"use strict"; | ||
// forked from https://github.com/joonhocho/superwild | ||
exports.__esModule = true; | ||
exports.Match = void 0; | ||
function Matcher(pattern, wchar) { | ||
@@ -5,0 +6,0 @@ if (wchar === void 0) { wchar = '*'; } |
@@ -895,96 +895,2 @@ const State = require("../index.cjs.js"); | ||
it("should execute listeners only when all values where changed", () => { | ||
const state = new State({ | ||
first: { one: 1 }, | ||
second: { two: 2 }, | ||
third: { three: 3 }, | ||
}); | ||
const values = []; | ||
state.waitForAll(["first", "second", "third"], (paths) => { | ||
const value = {}; | ||
for (const path in paths) { | ||
value[path] = state.get(path); | ||
} | ||
values.push(value); | ||
}); | ||
expect(values.length).toEqual(1); | ||
expect(values[0]).toEqual({ | ||
first: { one: 1 }, | ||
second: { two: 2 }, | ||
third: { three: 3 }, | ||
}); | ||
state.update("first.one", 11); | ||
expect(values.length).toEqual(1); | ||
expect(state.get("")).toEqual({ | ||
first: { one: 11 }, | ||
second: { two: 2 }, | ||
third: { three: 3 }, | ||
}); | ||
state.update("second.two", 22); | ||
expect(values.length).toEqual(1); | ||
expect(state.get("")).toEqual({ | ||
first: { one: 11 }, | ||
second: { two: 22 }, | ||
third: { three: 3 }, | ||
}); | ||
state.update("third.three", 33); | ||
expect(values.length).toEqual(2); | ||
expect(state.get("")).toEqual({ | ||
first: { one: 11 }, | ||
second: { two: 22 }, | ||
third: { three: 33 }, | ||
}); | ||
expect(values[1]).toEqual({ | ||
first: { one: 11 }, | ||
second: { two: 22 }, | ||
third: { three: 33 }, | ||
}); | ||
}); | ||
it("should work with wait option", () => { | ||
const state = new State({ test: 1 }, { queue: true }); | ||
const values = []; | ||
state.subscribe("test", (value) => { | ||
values.push(value); | ||
}); | ||
expect(values.length).toEqual(1); | ||
expect(values[0]).toEqual(1); | ||
state.update("test", 2); | ||
expect(values[1]).toEqual(2); | ||
}); | ||
it("should wait until all jobs are finished", () => { | ||
const state = new State({ test: 1, other: "x" }, { queue: true }); | ||
const values = []; | ||
state.subscribe("test", (value) => { | ||
state.update("other", "xx"); | ||
values.push(value); | ||
}); | ||
expect(values.length).toEqual(1); | ||
expect(values[0]).toEqual(1); | ||
state.update("test", 2); | ||
expect(values[1]).toEqual(2); | ||
expect(state.get("other")).toEqual("x"); | ||
setTimeout(() => { | ||
expect(state.get("other")).toEqual("xx"); | ||
}, 100); | ||
}); | ||
it("should wait until all jobs are finished with update:queue options", () => { | ||
const state = new State({ test: 1, other: "x" }); | ||
const values = []; | ||
state.subscribe("test", (value) => { | ||
state.update("other", "xx", { queue: true }); | ||
values.push(value); | ||
}); | ||
expect(values.length).toEqual(1); | ||
expect(values[0]).toEqual(1); | ||
state.update("test", 2); | ||
expect(values[1]).toEqual(2); | ||
expect(state.get("other")).toEqual("x"); | ||
setTimeout(() => { | ||
expect(state.get("other")).toEqual("xx"); | ||
}, 100); | ||
}); | ||
it("should ignore ignored changes", () => { | ||
@@ -1220,2 +1126,3 @@ const state = new State({ one: { two: { three: { four: { five: 0 } } } } }); | ||
state.update("x.*.o", "oo"); | ||
expect(values.length).toEqual(2); | ||
@@ -1222,0 +1129,0 @@ expect(values[1]).toEqual("oo"); |
"use strict"; | ||
var __values = (this && this.__values) || function (o) { | ||
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; | ||
var __values = (this && this.__values) || function(o) { | ||
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; | ||
if (m) return m.call(o); | ||
return { | ||
if (o && typeof o.length === "number") return { | ||
next: function () { | ||
@@ -11,2 +11,3 @@ if (o && i >= o.length) o = void 0; | ||
}; | ||
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); | ||
}; | ||
@@ -13,0 +14,0 @@ exports.__esModule = true; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
512367
39
0
12623
621
5