@ -1,11 +1,9 @@
|
|||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import {PathEventEmitter} from './dist/index.mjs';
|
import {PES} from './dist/index.mjs';
|
||||||
|
|
||||||
const emitter = new PathEventEmitter('data');
|
console.log(PES`storage${'Test/Test'}:d`);
|
||||||
emitter.on('*', console.log);
|
|
||||||
emitter.emit('data/asd', {});
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ztimson/utils",
|
"name": "@ztimson/utils",
|
||||||
"version": "0.26.2",
|
"version": "0.26.1",
|
||||||
"description": "Utility library",
|
"description": "Utility library",
|
||||||
"author": "Zak Timson",
|
"author": "Zak Timson",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
208
src/aset.ts
208
src/aset.ts
@ -2,16 +2,11 @@ import {isEqual} from './objects.ts';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An array which functions as a set. It guarantees unique elements
|
* An array which functions as a set. It guarantees unique elements
|
||||||
* and provides set functions for comparisons.
|
* and provides set functions for comparisons
|
||||||
*
|
|
||||||
* This optimized version uses a Map internally for efficient lookups
|
|
||||||
* while maintaining an array for ordered iteration and array-like methods.
|
|
||||||
*/
|
*/
|
||||||
export class ASet<T> extends Array<T> {
|
export class ASet<T> extends Array {
|
||||||
private readonly _valueMap: Map<string, T>; // Stores string representation -> actual value for quick lookups
|
|
||||||
|
|
||||||
/** Number of elements in set */
|
/** Number of elements in set */
|
||||||
get size(): number {
|
get size() {
|
||||||
return this.length;
|
return this.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,236 +16,119 @@ export class ASet<T> extends Array<T> {
|
|||||||
*/
|
*/
|
||||||
constructor(elements: T[] = []) {
|
constructor(elements: T[] = []) {
|
||||||
super();
|
super();
|
||||||
this._valueMap = new Map<string, T>(); // Initialize the map
|
if(!!elements?.['forEach'])
|
||||||
|
|
||||||
if (Array.isArray(elements)) {
|
|
||||||
elements.forEach(el => this.add(el));
|
elements.forEach(el => this.add(el));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to generate a unique key for the map.
|
* Add elements to set if unique
|
||||||
* This is crucial for using a Map with custom equality.
|
|
||||||
* For primitive types, `String(el)` is often sufficient.
|
|
||||||
* For objects, you'll need a robust serialization or a unique ID.
|
|
||||||
* If isEqual handles deep equality, the key generation must reflect that.
|
|
||||||
*
|
|
||||||
* @param el Element to generate a key for
|
|
||||||
* @returns A string key
|
|
||||||
*/
|
|
||||||
private _getKey(el: T): string {
|
|
||||||
// IMPORTANT: This is a critical point for isEqual to work correctly with Map.
|
|
||||||
// If isEqual performs deep comparison, _getKey must produce the same string
|
|
||||||
// for deeply equal objects. JSON.stringify can work for simple objects,
|
|
||||||
// but can fail for objects with circular references, functions, or undefined values.
|
|
||||||
// For more complex scenarios, consider a library like 'fast-json-stable-stringify'
|
|
||||||
// or a custom hashing function that respects isEqual.
|
|
||||||
try {
|
|
||||||
return JSON.stringify(el);
|
|
||||||
} catch (e) {
|
|
||||||
// Fallback for objects that cannot be stringified (e.g., circular structures)
|
|
||||||
// This might lead to less optimal performance or incorrect uniqueness for such objects.
|
|
||||||
// A more robust solution for complex objects might involve unique object IDs
|
|
||||||
// or a custom comparison function for Map keys if JS allowed it.
|
|
||||||
return String(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add elements to set if unique.
|
|
||||||
* Optimized to use the internal Map for O(1) average time lookups.
|
|
||||||
* @param items
|
* @param items
|
||||||
*/
|
*/
|
||||||
add(...items: T[]): this {
|
add(...items: T[]) {
|
||||||
for (const item of items) {
|
items.filter(el => !this.has(el)).forEach(el => this.push(el));
|
||||||
const key = this._getKey(item);
|
|
||||||
if (!this._valueMap.has(key)) {
|
|
||||||
// Also ensures isEqual is respected by checking against existing values
|
|
||||||
let found = false;
|
|
||||||
for (const existingItem of this) {
|
|
||||||
if (isEqual(existingItem, item)) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
super.push(item); // Add to the array
|
|
||||||
this._valueMap.set(key, item); // Add to the map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all elements.
|
* Remove all elements
|
||||||
* Optimized to clear both the array and the map.
|
|
||||||
*/
|
*/
|
||||||
clear(): this {
|
clear() {
|
||||||
super.splice(0, this.length);
|
this.splice(0, this.length);
|
||||||
this._valueMap.clear();
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete elements from set.
|
* Delete elements from set
|
||||||
* Optimized to use the internal Map for O(1) average time lookups for key existence.
|
|
||||||
* Still requires array splice which can be O(N) in worst case for shifting.
|
|
||||||
* @param items Elements that will be deleted
|
* @param items Elements that will be deleted
|
||||||
*/
|
*/
|
||||||
delete(...items: T[]): this {
|
delete(...items: T[]) {
|
||||||
for (const item of items) {
|
items.forEach(el => {
|
||||||
const key = this._getKey(item);
|
const index = this.indexOf(el);
|
||||||
if (this._valueMap.has(key)) {
|
if(index != -1) this.splice(index, 1);
|
||||||
// Find the actual element in the array using isEqual
|
})
|
||||||
const index = super.findIndex((el: T) => isEqual(el, item));
|
|
||||||
if (index !== -1) {
|
|
||||||
super.splice(index, 1); // Remove from the array
|
|
||||||
this._valueMap.delete(key); // Remove from the map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create list of elements this set has which the comparison set does not.
|
* Create list of elements this set has which the comparison set does not
|
||||||
* Optimized to use `has` which is now faster.
|
|
||||||
* @param {ASet<T>} set Set to compare against
|
* @param {ASet<T>} set Set to compare against
|
||||||
* @return {ASet<T>} Different elements
|
* @return {ASet<T>} Different elements
|
||||||
*/
|
*/
|
||||||
difference(set: ASet<T>): ASet<T> {
|
difference(set: ASet<T>) {
|
||||||
const result = new ASet<T>();
|
return new ASet<T>(this.filter(el => !set.has(el)));
|
||||||
for (const el of this) {
|
|
||||||
if (!set.has(el)) {
|
|
||||||
result.add(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if set includes element.
|
* Check if set includes element
|
||||||
* Optimized to use the internal Map for O(1) average time lookups.
|
|
||||||
* @param {T} el Element to look for
|
* @param {T} el Element to look for
|
||||||
* @return {boolean} True if element was found, false otherwise
|
* @return {boolean} True if element was found, false otherwise
|
||||||
*/
|
*/
|
||||||
has(el: T): boolean {
|
has(el: T) {
|
||||||
const key = this._getKey(el);
|
return this.indexOf(el) != -1;
|
||||||
// First check map for existence. If it exists, then verify with isEqual
|
|
||||||
// as the key might be generic but isEqual is precise.
|
|
||||||
if (this._valueMap.has(key)) {
|
|
||||||
const storedValue = this._valueMap.get(key);
|
|
||||||
// This second check with isEqual is necessary if _getKey doesn't perfectly
|
|
||||||
// represent the equality criteria of isEqual, which is often the case
|
|
||||||
// for complex objects where JSON.stringify might produce different strings
|
|
||||||
// for isEqual objects (e.g., key order in objects).
|
|
||||||
// If _getKey is guaranteed to produce identical keys for isEqual objects,
|
|
||||||
// this isEqual check can be simplified or removed for performance.
|
|
||||||
return isEqual(storedValue, el);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find index number of element, or -1 if it doesn't exist. Matches by equality not reference.
|
* Find index number of element, or -1 if it doesn't exist. Matches by equality not reference
|
||||||
* This method still inherently needs to iterate the array to find the *index*.
|
|
||||||
* While `has` can be O(1), `indexOf` for custom equality remains O(N).
|
|
||||||
*
|
*
|
||||||
* @param {T} search Element to find
|
* @param {T} search Element to find
|
||||||
* @param {number} fromIndex Starting index position
|
* @param {number} fromIndex Starting index position
|
||||||
* @return {number} Element index number or -1 if missing
|
* @return {number} Element index number or -1 if missing
|
||||||
*/
|
*/
|
||||||
indexOf(search: T, fromIndex?: number): number {
|
indexOf(search: T, fromIndex?: number): number {
|
||||||
// Can't use the map directly for index lookup, must iterate the array
|
|
||||||
return super.findIndex((el: T) => isEqual(el, search), fromIndex);
|
return super.findIndex((el: T) => isEqual(el, search), fromIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create list of elements this set has in common with the comparison set.
|
* Create list of elements this set has in common with the comparison set
|
||||||
* Optimized to use `has` which is now faster.
|
|
||||||
* @param {ASet<T>} set Set to compare against
|
* @param {ASet<T>} set Set to compare against
|
||||||
* @return {ASet<T>} Set of common elements
|
* @return {boolean} Set of common elements
|
||||||
*/
|
*/
|
||||||
intersection(set: ASet<T>): ASet<T> {
|
intersection(set: ASet<T>) {
|
||||||
const result = new ASet<T>();
|
return new ASet<T>(this.filter(el => set.has(el)));
|
||||||
// Iterate over the smaller set for efficiency
|
|
||||||
const [smallerSet, largerSet] = this.size < set.size ? [this, set] : [set, this];
|
|
||||||
|
|
||||||
for (const el of smallerSet) {
|
|
||||||
if (largerSet.has(el)) {
|
|
||||||
result.add(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this set has no elements in common with the comparison set.
|
* Check if this set has no elements in common with the comparison set
|
||||||
* Optimized to use `intersection` and check its size.
|
|
||||||
* @param {ASet<T>} set Set to compare against
|
* @param {ASet<T>} set Set to compare against
|
||||||
* @return {boolean} True if nothing in common, false otherwise
|
* @return {boolean} True if nothing in common, false otherwise
|
||||||
*/
|
*/
|
||||||
isDisjointFrom(set: ASet<T>): boolean {
|
isDisjointFrom(set: ASet<T>) {
|
||||||
return this.intersection(set).size === 0;
|
return this.intersection(set).size == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if all elements in this set are included in the comparison set.
|
* Check if all elements in this set are included in the comparison set
|
||||||
* Optimized to use `has` which is now faster.
|
|
||||||
* @param {ASet<T>} set Set to compare against
|
* @param {ASet<T>} set Set to compare against
|
||||||
* @return {boolean} True if all elements are included, false otherwise
|
* @return {boolean} True if all elements are included, false otherwise
|
||||||
*/
|
*/
|
||||||
isSubsetOf(set: ASet<T>): boolean {
|
isSubsetOf(set: ASet<T>) {
|
||||||
if (this.size > set.size) { // A larger set cannot be a subset of a smaller one
|
return this.findIndex(el => !set.has(el)) == -1;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const el of this) {
|
|
||||||
if (!set.has(el)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if all elements from comparison set are included in this set.
|
* Check if all elements from comparison set are included in this set
|
||||||
* Optimized to use `has` which is now faster.
|
|
||||||
* @param {ASet<T>} set Set to compare against
|
* @param {ASet<T>} set Set to compare against
|
||||||
* @return {boolean} True if all elements are included, false otherwise
|
* @return {boolean} True if all elements are included, false otherwise
|
||||||
*/
|
*/
|
||||||
isSuperset(set: ASet<T>): boolean {
|
isSuperset(set: ASet<T>) {
|
||||||
if (this.size < set.size) { // A smaller set cannot be a superset of a larger one
|
return set.findIndex(el => !this.has(el)) == -1;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const el of set) {
|
|
||||||
if (!this.has(el)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create list of elements that are only in one set but not both (XOR).
|
* Create list of elements that are only in one set but not both (XOR)
|
||||||
* Uses optimized `difference` method.
|
|
||||||
* @param {ASet<T>} set Set to compare against
|
* @param {ASet<T>} set Set to compare against
|
||||||
* @return {ASet<T>} New set of unique elements
|
* @return {ASet<T>} New set of unique elements
|
||||||
*/
|
*/
|
||||||
symmetricDifference(set: ASet<T>): ASet<T> {
|
symmetricDifference(set: ASet<T>) {
|
||||||
return new ASet<T>([...this.difference(set), ...set.difference(this)]);
|
return new ASet([...this.difference(set), ...set.difference(this)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create joined list of elements included in this & the comparison set.
|
* Create joined list of elements included in this & the comparison set
|
||||||
* Uses optimized `add` method.
|
* @param {ASet<T>} set Set join
|
||||||
* @param {ASet<T> | Array<T>} set Set to join
|
|
||||||
* @return {ASet<T>} New set of both previous sets combined
|
* @return {ASet<T>} New set of both previous sets combined
|
||||||
*/
|
*/
|
||||||
union(set: ASet<T> | Array<T>): ASet<T> {
|
union(set: ASet<T> | Array<T>) {
|
||||||
const result = new ASet<T>(this);
|
return new ASet([...this, ...set]);
|
||||||
result.add(...Array.from(set));
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,51 +177,19 @@ export function includes(target: any, values: any, allowMissing = false): boolea
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deep check if two items are equal.
|
* Deep check if two objects are equal
|
||||||
* Handles primitives, objects, arrays, functions, Date, RegExp, and circular references.
|
|
||||||
*
|
*
|
||||||
* @param {any} a - first item to compare
|
* @param {any} a - first item to compare
|
||||||
* @param {any} b - second item to compare
|
* @param {any} b - second item to compare
|
||||||
* @param {WeakMap<object, object>} [seen] - Internal parameter to track circular references
|
|
||||||
* @returns {boolean} True if they match
|
* @returns {boolean} True if they match
|
||||||
*/
|
*/
|
||||||
export function isEqual(a: any, b: any, seen = new WeakMap<object, object>()): boolean {
|
export function isEqual(a: any, b: any): boolean {
|
||||||
// Simple cases
|
const ta = typeof a, tb = typeof b;
|
||||||
if(a === b) return true;
|
if((ta != 'object' || a == null) || (tb != 'object' || b == null))
|
||||||
if(a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
return ta == 'function' && tb == 'function' ? a.toString() == b.toString() : a === b;
|
||||||
if(a instanceof RegExp && b instanceof RegExp) return a.source === b.source && a.flags === b.flags;
|
const keys = Object.keys(a);
|
||||||
|
if(keys.length != Object.keys(b).length) return false;
|
||||||
// Null checks
|
return Object.keys(a).every(key => isEqual(a[key], b[key]));
|
||||||
if(typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
|
|
||||||
if(Number.isNaN(a) && Number.isNaN(b)) return true;
|
|
||||||
if(typeof a === 'function' && typeof b === 'function') return a.toString() === b.toString()
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Circular references
|
|
||||||
if(seen.has(a)) return seen.get(a) === b;
|
|
||||||
seen.set(a, b);
|
|
||||||
const isArrayA = Array.isArray(a);
|
|
||||||
const isArrayB = Array.isArray(b);
|
|
||||||
|
|
||||||
// Array checks
|
|
||||||
if(isArrayA && isArrayB) {
|
|
||||||
if(a.length !== b.length) return false;
|
|
||||||
for(let i = 0; i < a.length; i++) {
|
|
||||||
if(!isEqual(a[i], b[i], seen)) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if(isArrayA !== isArrayB) return false;
|
|
||||||
|
|
||||||
// Key & value deep comparison
|
|
||||||
const keysA = Object.keys(a);
|
|
||||||
const keysB = Object.keys(b);
|
|
||||||
if(keysA.length !== keysB.length) return false;
|
|
||||||
for(const key of keysA) {
|
|
||||||
if(!Object.prototype.hasOwnProperty.call(b, key) || !isEqual(a[key], b[key], seen)) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,12 +71,9 @@ export class PathEvent {
|
|||||||
/** List of methods */
|
/** List of methods */
|
||||||
methods!: ASet<Method>;
|
methods!: ASet<Method>;
|
||||||
|
|
||||||
/** Internal cache for PathEvent instances to avoid redundant parsing */
|
|
||||||
private static pathEventCache: Map<string, PathEvent> = new Map();
|
|
||||||
|
|
||||||
/** All/Wildcard specified */
|
/** All/Wildcard specified */
|
||||||
get all(): boolean { return this.methods.has('*') }
|
get all(): boolean { return this.methods.has('*') }
|
||||||
set all(v: boolean) { v ? this.methods = new ASet<Method>(['*']) : this.methods.delete('*'); }
|
set all(v: boolean) { v ? new ASet<Method>(['*']) : this.methods.delete('*'); }
|
||||||
/** None specified */
|
/** None specified */
|
||||||
get none(): boolean { return this.methods.has('n') }
|
get none(): boolean { return this.methods.has('n') }
|
||||||
set none(v: boolean) { v ? this.methods = new ASet<Method>(['n']) : this.methods.delete('n'); }
|
set none(v: boolean) { v ? this.methods = new ASet<Method>(['n']) : this.methods.delete('n'); }
|
||||||
@ -94,20 +91,10 @@ export class PathEvent {
|
|||||||
set delete(v: boolean) { v ? this.methods.delete('n').delete('*').add('d') : this.methods.delete('d'); }
|
set delete(v: boolean) { v ? this.methods.delete('n').delete('*').add('d') : this.methods.delete('d'); }
|
||||||
|
|
||||||
constructor(e: string | PathEvent) {
|
constructor(e: string | PathEvent) {
|
||||||
if(typeof e == 'object') {
|
if(typeof e == 'object') return Object.assign(this, e);
|
||||||
Object.assign(this, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check cache first
|
|
||||||
if (PathEvent.pathEventCache.has(e)) {
|
|
||||||
Object.assign(this, PathEvent.pathEventCache.get(e)!);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let [p, scope, method] = e.replaceAll(/\/{2,}/g, '/').split(':');
|
let [p, scope, method] = e.replaceAll(/\/{2,}/g, '/').split(':');
|
||||||
if(!method) method = scope || '*';
|
if(!method) method = scope || '*';
|
||||||
if(p == '*' || (!p && method == '*')) {
|
if(p == '*' || !p && method == '*') {
|
||||||
p = '';
|
p = '';
|
||||||
method = '*';
|
method = '*';
|
||||||
}
|
}
|
||||||
@ -117,14 +104,6 @@ export class PathEvent {
|
|||||||
this.fullPath = `${this.module}${this.module && this.path ? '/' : ''}${this.path}`;
|
this.fullPath = `${this.module}${this.module && this.path ? '/' : ''}${this.path}`;
|
||||||
this.name = temp.pop() || '';
|
this.name = temp.pop() || '';
|
||||||
this.methods = new ASet(<any>method.split(''));
|
this.methods = new ASet(<any>method.split(''));
|
||||||
|
|
||||||
// Store in cache
|
|
||||||
PathEvent.pathEventCache.set(e, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Clear the cache of all PathEvents */
|
|
||||||
static clearCache(): void {
|
|
||||||
PathEvent.pathEventCache.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,7 +115,7 @@ export class PathEvent {
|
|||||||
*/
|
*/
|
||||||
static combine(...paths: (string | PathEvent)[]): PathEvent {
|
static combine(...paths: (string | PathEvent)[]): PathEvent {
|
||||||
let hitNone = false;
|
let hitNone = false;
|
||||||
const combined = paths.map(p => p instanceof PathEvent ? p : new PathEvent(p))
|
const combined = paths.map(p => new PathEvent(p))
|
||||||
.toSorted((p1, p2) => {
|
.toSorted((p1, p2) => {
|
||||||
const l1 = p1.fullPath.length, l2 = p2.fullPath.length;
|
const l1 = p1.fullPath.length, l2 = p2.fullPath.length;
|
||||||
return l1 < l2 ? 1 : (l1 > l2 ? -1 : 0);
|
return l1 < l2 ? 1 : (l1 > l2 ? -1 : 0);
|
||||||
@ -145,9 +124,10 @@ export class PathEvent {
|
|||||||
if(p.none) hitNone = true;
|
if(p.none) hitNone = true;
|
||||||
if(!acc) return p;
|
if(!acc) return p;
|
||||||
if(hitNone) return acc;
|
if(hitNone) return acc;
|
||||||
acc.methods = new ASet([...acc.methods, ...p.methods]);
|
acc.methods = [...acc.methods, ...p.methods];
|
||||||
return acc;
|
return acc;
|
||||||
}, <any>null);
|
}, <any>null);
|
||||||
|
combined.methods = new ASet<Method>(combined.methods);
|
||||||
return combined;
|
return combined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,12 +139,11 @@ export class PathEvent {
|
|||||||
* @return {boolean} Whether there is any overlap
|
* @return {boolean} Whether there is any overlap
|
||||||
*/
|
*/
|
||||||
static filter(target: string | PathEvent | (string | PathEvent)[], ...filter: (string | PathEvent)[]): PathEvent[] {
|
static filter(target: string | PathEvent | (string | PathEvent)[], ...filter: (string | PathEvent)[]): PathEvent[] {
|
||||||
const parsedTarget = makeArray(target).map(pe => pe instanceof PathEvent ? pe : new PathEvent(pe));
|
const parsedTarget = makeArray(target).map(pe => new PathEvent(pe));
|
||||||
const parsedFilter = makeArray(filter).map(pe => pe instanceof PathEvent ? pe : new PathEvent(pe));
|
const parsedFilter = makeArray(filter).map(pe => new PathEvent(pe));
|
||||||
return parsedTarget.filter(t => !!parsedFilter.find(r => {
|
return parsedTarget.filter(t => !!parsedFilter.find(r => {
|
||||||
const wildcard = r.fullPath == '*' || t.fullPath == '*';
|
const wildcard = r.fullPath == '*' || t.fullPath == '*';
|
||||||
const p1 = r.fullPath.includes('*') ? r.fullPath.slice(0, r.fullPath.indexOf('*')) : r.fullPath;
|
const p1 = r.fullPath.slice(0, r.fullPath.indexOf('*')), p2 = t.fullPath.slice(0, t.fullPath.indexOf('*'))
|
||||||
const p2 = t.fullPath.includes('*') ? t.fullPath.slice(0, t.fullPath.indexOf('*')) : t.fullPath;
|
|
||||||
const scope = p1.startsWith(p2) || p2.startsWith(p1);
|
const scope = p1.startsWith(p2) || p2.startsWith(p1);
|
||||||
const methods = r.all || t.all || r.methods.intersection(t.methods).length;
|
const methods = r.all || t.all || r.methods.intersection(t.methods).length;
|
||||||
return (wildcard || scope) && methods;
|
return (wildcard || scope) && methods;
|
||||||
@ -179,13 +158,12 @@ export class PathEvent {
|
|||||||
* @return {boolean} Whether there is any overlap
|
* @return {boolean} Whether there is any overlap
|
||||||
*/
|
*/
|
||||||
static has(target: string | PathEvent | (string | PathEvent)[], ...has: (string | PathEvent)[]): boolean {
|
static has(target: string | PathEvent | (string | PathEvent)[], ...has: (string | PathEvent)[]): boolean {
|
||||||
const parsedTarget = makeArray(target).map(pe => pe instanceof PathEvent ? pe : new PathEvent(pe));
|
const parsedTarget = makeArray(target).map(pe => new PathEvent(pe));
|
||||||
const parsedRequired = makeArray(has).map(pe => pe instanceof PathEvent ? pe : new PathEvent(pe));
|
const parsedRequired = makeArray(has).map(pe => new PathEvent(pe));
|
||||||
return !!parsedRequired.find(r => !!parsedTarget.find(t => {
|
return !!parsedRequired.find(r => !!parsedTarget.find(t => {
|
||||||
const wildcard = r.fullPath == '*' || t.fullPath == '*';
|
const wildcard = r.fullPath == '*' || t.fullPath == '*';
|
||||||
const p1 = r.fullPath.includes('*') ? r.fullPath.slice(0, r.fullPath.indexOf('*')) : r.fullPath;
|
const p1 = r.fullPath.slice(0, r.fullPath.indexOf('*')), p2 = t.fullPath.slice(0, t.fullPath.indexOf('*'))
|
||||||
const p2 = t.fullPath.includes('*') ? t.fullPath.slice(0, t.fullPath.indexOf('*')) : t.fullPath;
|
const scope = p1.startsWith(p2);
|
||||||
const scope = p1.startsWith(p2); // Note: Original had || p2.startsWith(p1) here, but has implies target has required.
|
|
||||||
const methods = r.all || t.all || r.methods.intersection(t.methods).length;
|
const methods = r.all || t.all || r.methods.intersection(t.methods).length;
|
||||||
return (wildcard || scope) && methods;
|
return (wildcard || scope) && methods;
|
||||||
}));
|
}));
|
||||||
@ -315,7 +293,7 @@ export class PathEventEmitter implements IPathEventEmitter{
|
|||||||
constructor(public readonly prefix: string = '') { }
|
constructor(public readonly prefix: string = '') { }
|
||||||
|
|
||||||
emit(event: Event, ...args: any[]) {
|
emit(event: Event, ...args: any[]) {
|
||||||
const parsed = event instanceof PathEvent ? event : new PathEvent(`${this.prefix}/${event}`);
|
const parsed = PE`${this.prefix}/${event}`;
|
||||||
this.listeners.filter(l => PathEvent.has(l[0], parsed))
|
this.listeners.filter(l => PathEvent.has(l[0], parsed))
|
||||||
.forEach(async l => l[1](parsed, ...args));
|
.forEach(async l => l[1](parsed, ...args));
|
||||||
};
|
};
|
||||||
@ -326,7 +304,7 @@ export class PathEventEmitter implements IPathEventEmitter{
|
|||||||
|
|
||||||
on(event: Event | Event[], listener: PathListener): PathUnsubscribe {
|
on(event: Event | Event[], listener: PathListener): PathUnsubscribe {
|
||||||
makeArray(event).forEach(e => this.listeners.push([
|
makeArray(event).forEach(e => this.listeners.push([
|
||||||
e instanceof PathEvent ? e : new PathEvent(`${this.prefix}/${e}`),
|
new PathEvent(`${this.prefix}/${e}`),
|
||||||
listener
|
listener
|
||||||
]));
|
]));
|
||||||
return () => this.off(listener);
|
return () => this.off(listener);
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import {PathError, PathEvent, PathEventEmitter, PE, PES} from '../src';
|
import {PathError, PathEvent, PathEventEmitter, PE, PES} from '../src';
|
||||||
|
|
||||||
describe('Path Events', () => {
|
describe('Path Events', () => {
|
||||||
beforeEach(() => {
|
|
||||||
PathEvent.clearCache();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PE', () => {
|
describe('PE', () => {
|
||||||
it('creates PathEvent from template string', () => {
|
it('creates PathEvent from template string', () => {
|
||||||
const e = PE`users/system:cr`;
|
const e = PE`users/system:cr`;
|
||||||
|
Reference in New Issue
Block a user