Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@wixc3/patterns

Package Overview
Dependencies
Maintainers
85
Versions
63
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@wixc3/patterns - npm Package Compare versions

Comparing version 2.2.0 to 3.0.0

README.md

4

dist/cjs/create-disposables.d.ts

@@ -0,1 +1,5 @@

/**
* Disposables allow adding of disposal async functions,
* when dispose is called, these functions will be run sequentially
*/
export declare function createDisposables(): {

@@ -2,0 +6,0 @@ dispose(): Promise<void>;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createDisposables = void 0;
/**
* Disposables allow adding of disposal async functions,
* when dispose is called, these functions will be run sequentially
*/
function createDisposables() {

@@ -5,0 +9,0 @@ const disposables = new Set();

@@ -0,1 +1,25 @@

/**
* Cancelable debouncing of calls to trigger
* @example <caption>waitTime</caption>
* ```ts
* const debounced = new Debouncer(a => console.log(a), 1, 100)
* debounce.trigger('first')
* debounce.trigger('second')
* // after 1ms
* // => 'second'
* ```
* @example <caption>maxWaitTime</caption>
* ```ts
* const debounced = new Debouncer(a => console.log(a), 3, 4)
* debounce.trigger('first')
* // after 1ms
* debounce.trigger('second')
* // after 1ms
* // => 'second'
* debounce.trigger('third')
* // after 2ms, 4ms elapsed since the first trigger
* // => 'third'
* ```
*/
export declare class Debouncer<T extends (...args: unknown[]) => unknown> {

@@ -5,10 +29,32 @@ private cb;

private maxWaitTime;
private _setTimeout;
private _clearTimeout;
private timeout;
private maxTimeout;
constructor(cb: T, waitTime: number, maxWaitTime: number, _setTimeout?: (cb: () => void, wait: number) => number, _clearTimeout?: (id: number) => void);
private args;
private defer;
/**
*
* @param cb - trigger callback
* @param waitTime - time to wait before invoking cb
* @param maxWaitTime - maximum wait time from first triggering
*/
constructor(cb: T, waitTime: number, maxWaitTime: number);
private execute;
/**
*
* @param args - arguments to pass to cb
* @returns Promised resolved with cb return value
*/
trigger(...args: Parameters<T>): Promise<ReturnType<T>>;
/**
* Cancels pending invocation of cb
* @example
* ```ts
* const debounced = new Debouncer(a => console.log(a), 3, 4)
* debounce.trigger('first')
* debounce.cancel()
* // console.log will not be invoked
* ```
*/
cancel(): void;
}
//# sourceMappingURL=debouncer.d.ts.map

116

dist/cjs/debouncer.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Debouncer = void 0;
const promise_assist_1 = require("promise-assist");
/**
* Cancelable debouncing of calls to trigger
* @example <caption>waitTime</caption>
* ```ts
* const debounced = new Debouncer(a => console.log(a), 1, 100)
* debounce.trigger('first')
* debounce.trigger('second')
* // after 1ms
* // => 'second'
* ```
* @example <caption>maxWaitTime</caption>
* ```ts
* const debounced = new Debouncer(a => console.log(a), 3, 4)
* debounce.trigger('first')
* // after 1ms
* debounce.trigger('second')
* // after 1ms
* // => 'second'
* debounce.trigger('third')
* // after 2ms, 4ms elapsed since the first trigger
* // => 'third'
* ```
*/
class Debouncer {

@@ -8,54 +33,69 @@ cb;

maxWaitTime;
_setTimeout;
_clearTimeout;
timeout;
maxTimeout;
constructor(cb, waitTime, maxWaitTime, _setTimeout = (...args) =>
// eslint-disable-next-line
setTimeout(...args), _clearTimeout = (id) =>
// eslint-disable-next-line
clearTimeout(id)) {
args = [];
defer = (0, promise_assist_1.deferred)();
/**
*
* @param cb - trigger callback
* @param waitTime - time to wait before invoking cb
* @param maxWaitTime - maximum wait time from first triggering
*/
constructor(cb, waitTime, maxWaitTime) {
this.cb = cb;
this.waitTime = waitTime;
this.maxWaitTime = maxWaitTime;
this._setTimeout = _setTimeout;
this._clearTimeout = _clearTimeout;
}
execute() {
try {
const result = this.cb(...this.args);
this.defer.resolve(result);
}
catch (ex) {
this.defer.reject(ex);
}
this.defer = (0, promise_assist_1.deferred)();
}
/**
*
* @param args - arguments to pass to cb
* @returns Promised resolved with cb return value
*/
trigger(...args) {
return new Promise((res, rej) => {
if (this.timeout) {
this._clearTimeout(this.timeout);
this.args = args;
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(() => {
this.execute();
if (this.maxTimeout) {
clearTimeout(this.maxTimeout);
}
this.timeout = this._setTimeout(() => {
try {
res(this.cb(...args));
}, this.waitTime);
if (!this.maxTimeout) {
this.maxTimeout = setTimeout(() => {
this.execute();
if (this.timeout) {
clearTimeout(this.timeout);
}
catch (ex) {
rej(ex);
}
if (this.maxTimeout) {
this._clearTimeout(this.maxTimeout);
}
}, this.waitTime);
if (!this.maxTimeout) {
this.maxTimeout = this._setTimeout(() => {
try {
res(this.cb(...args));
}
catch (ex) {
rej(ex);
}
if (this.timeout) {
this._clearTimeout(this.timeout);
}
}, this.maxWaitTime);
}
});
}, this.maxWaitTime);
}
return this.defer.promise;
}
/**
* Cancels pending invocation of cb
* @example
* ```ts
* const debounced = new Debouncer(a => console.log(a), 3, 4)
* debounce.trigger('first')
* debounce.cancel()
* // console.log will not be invoked
* ```
*/
cancel() {
if (this.timeout) {
this._clearTimeout(this.timeout);
clearTimeout(this.timeout);
}
if (this.maxTimeout) {
this._clearTimeout(this.maxTimeout);
clearTimeout(this.maxTimeout);
}

@@ -62,0 +102,0 @@ }

@@ -1,14 +0,53 @@

import { SetMultiMap } from './set-multi-map';
export declare type EventListener<T> = (event: T) => void;
export declare class EventEmitter<T extends {}> {
listeners: SetMultiMap<keyof T, EventListener<any>>;
listenersOnce: SetMultiMap<keyof T, EventListener<any>>;
on<K extends keyof T>(eventName: K, listener: EventListener<T[K]>): void;
subscribe<K extends keyof T>(eventName: K, listener: EventListener<T[K]>): void;
once<K extends keyof T>(eventName: K, listener: EventListener<T[K]>): void;
off<K extends keyof T>(eventName: K, listener: EventListener<T[K]>): void;
unsubscribe<K extends keyof T>(eventName: K, listener: EventListener<T[K]>): void;
emit<K extends keyof T>(eventName: K, eventValue: T[K]): void;
clear(): void;
/**
* A simple event emitter
*/
export declare class EventEmitter<Events extends Record<string, unknown>, EventId extends keyof Events = keyof Events> {
private events;
private emitOnce;
/**
* Check if an event has subscribers
* @returns true if event has subscribers
*/
hasSubscribers: (event: EventId) => boolean;
/**
* Subscribe a handler for event
*/
subscribe: <Event_1 extends EventId>(event: Event_1, handler: (data: Events[Event_1]) => void) => void;
/**
* {@inheritDoc EventEmitter.subscribe}
*/
on: <Event_1 extends EventId>(event: Event_1, handler: (data: Events[Event_1]) => void) => void;
/**
* Adds a handler that will be called at most once
*/
once: <Event_1 extends EventId>(event: Event_1, handler: (data: Events[Event_1]) => void) => void;
/**
* Unsubscribe a handler from event
*/
unsubscribe: <Event_1 extends EventId>(event: Event_1, handler: (data: Events[Event_1]) => void) => void;
/**
* {@inheritDoc EventEmitter.unsubscribe}
*/
off: <Event_1 extends EventId>(event: Event_1, handler: (data: Events[Event_1]) => void) => void;
/**
* Drop all subscriptions of a signal
* @param event - signal id
*/
delete: <Event_1 extends EventId>(event: Event_1) => void;
/**
* Drop all subscriptions
*/
clear: () => void;
/**
* {@inheritDoc Signal.notify}
* @param event - eve
* @param data - event data
*/
notify: <Event_1 extends EventId>(event: Event_1, data: Events[Event_1]) => void;
/**
* {@inheritDoc EventEmitter.notify}
*/
emit: <Event_1 extends EventId>(event: Event_1, data: Events[Event_1]) => void;
}
export declare type IEventEmitter<T extends Record<string, any>> = Pick<EventEmitter<T>, keyof EventEmitter<T>>;
//# sourceMappingURL=event-emitter.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventEmitter = void 0;
const set_multi_map_1 = require("./set-multi-map");
// eslint-disable-next-line @typescript-eslint/ban-types
const signal_1 = require("./signal");
/**
* A simple event emitter
*/
class EventEmitter {
listeners = new set_multi_map_1.SetMultiMap();
listenersOnce = new set_multi_map_1.SetMultiMap();
on(eventName, listener) {
this.listeners.add(eventName, listener);
}
subscribe(eventName, listener) {
this.on(eventName, listener);
}
once(eventName, listener) {
this.listenersOnce.add(eventName, listener);
}
off(eventName, listener) {
this.listeners.delete(eventName, listener);
this.listenersOnce.delete(eventName, listener);
}
unsubscribe(eventName, listener) {
this.off(eventName, listener);
}
emit(eventName, eventValue) {
const listeners = this.listeners.get(eventName);
if (listeners) {
for (const listener of listeners) {
listener(eventValue);
}
}
const listenersOnce = this.listenersOnce.get(eventName);
if (listenersOnce) {
for (const listener of listenersOnce) {
listener(eventValue);
}
this.listenersOnce.deleteKey(eventName);
}
}
clear() {
this.listeners.clear();
this.listenersOnce.clear();
}
events = new Map();
emitOnce = new Map();
/**
* Check if an event has subscribers
* @returns true if event has subscribers
*/
hasSubscribers = (event) => this.events.has(event);
/**
* Subscribe a handler for event
*/
subscribe = (event, handler) => {
const bucket = this.events.get(event);
bucket ? bucket.add(handler) : this.events.set(event, new signal_1.Signal([handler]));
};
/**
* {@inheritDoc EventEmitter.subscribe}
*/
on = this.subscribe;
/**
* Adds a handler that will be called at most once
*/
once = (event, handler) => {
this.off(event, handler);
const bucket = this.emitOnce.get(event);
bucket ? bucket.add(handler) : this.emitOnce.set(event, new signal_1.Signal([handler]));
};
/**
* Unsubscribe a handler from event
*/
unsubscribe = (event, handler) => {
let bucket = this.events.get(event);
bucket?.delete(handler);
bucket?.size === 0 && this.events.delete(event);
bucket = this.emitOnce.get(event);
bucket?.delete(handler);
bucket?.size === 0 && this.events.delete(event);
};
/**
* {@inheritDoc EventEmitter.unsubscribe}
*/
off = this.unsubscribe;
/**
* Drop all subscriptions of a signal
* @param event - signal id
*/
delete = (event) => {
this.events.delete(event);
this.emitOnce.delete(event);
};
/**
* Drop all subscriptions
*/
clear = () => {
this.events = new Map();
this.emitOnce = new Map();
};
/**
* {@inheritDoc Signal.notify}
* @param event - eve
* @param data - event data
*/
notify = (event, data) => {
this.events.get(event)?.notify(data);
this.emitOnce.get(event)?.notify(data);
this.emitOnce.delete(event);
};
/**
* {@inheritDoc EventEmitter.notify}
*/
emit = this.notify;
}
exports.EventEmitter = EventEmitter;
//# sourceMappingURL=event-emitter.js.map

@@ -0,7 +1,7 @@

export interface LRUCacheConfig {
maxSize?: number;
}
/**
* BASIC (not optimal) implementation of the LRU cache
*/
export interface LRUCacheConfig {
maxSize?: number;
}
export declare class LRUCache<K, V> {

@@ -8,0 +8,0 @@ private config;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LRUCache = void 0;
/**
* BASIC (not optimal) implementation of the LRU cache
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.LRUCache = void 0;
class LRUCache {

@@ -8,0 +8,0 @@ config;

@@ -0,1 +1,13 @@

/**
* Maps keys to a set of values
* @example
* ```ts
* const m = new SetMultiMap([['a',1],['a',2]])
* m.add('a',3)
* m.has('a',1) // => true
* m.has('a',2) // => true
* m.has('a',3) // => true
* m.has('a',4) // => false
* ```
*/
export declare class SetMultiMap<K, V> implements Iterable<[K, V]> {

@@ -2,0 +14,0 @@ private map;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isSetMultiMap = exports.SetMultiMap = void 0;
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */
const common_1 = require("@wixc3/common");
/**
* Maps keys to a set of values
* @example
* ```ts
* const m = new SetMultiMap([['a',1],['a',2]])
* m.add('a',3)
* m.has('a',1) // => true
* m.has('a',2) // => true
* m.has('a',3) // => true
* m.has('a',4) // => false
* ```
*/
class SetMultiMap {

@@ -9,0 +18,0 @@ map = new Map();

@@ -0,11 +1,7 @@

export declare type Listener<T> = (data: T) => void;
/**
* Signal is a simple event emitter for one type of event.
*
* Use Signals a public api for emitting events.
* Naming a signal is like naming the event the it triggers.
* If the name sounds like a property try to add a `on` prefix or `Change/Signal` suffix.
* All methods are bound to the Signal instance
*
* Simple example:
* ```
* @example
* ```ts
* const foodArrived = new Signal<Food>();

@@ -20,4 +16,4 @@ *

*
* Usage in a class:
* ```
* @example Usage in a class:
* ```ts
* class LoginService {

@@ -29,19 +25,32 @@ * public onLoginSuccess = new Signal<User>();

* ```
* @remarks
* Use Signals a public api for emitting events.
* Naming a signal is like naming the event the it triggers.
* If the name sounds like a property try to add a `on` prefix or `Change/Signal` suffix.
* All methods are bound to the Signal instance
*
* Notice that the Signals are public.
* We don't need to implement specific subscriptions on the class, unless we need to expose it as a remote service.
*
*/
export declare type Listener<T> = (data: T) => void;
export declare class Signal<T> extends Set<Listener<T>> {
subscribe: (i: Listener<T>) => void;
unsubscribe: (i: Listener<T>) => void;
/**
* Subscribe a notification callback
* @param handler - Will be executed with a data arg when a notification occurs
*/
subscribe: (handler: Listener<T>) => void;
/**
* Unsubscribe an existing callback
*/
unsubscribe: (handler: Listener<T>) => void;
/**
* Notify all subscribers with arg data
*/
notify: (data: T) => void;
}
/**
* Same as signal but for multiple types differentiating by the key.
*
* Usage
*
* const ms = new MultiSignal<{ onChange: { id: 'onChange' }; onDelete: { id: 'onDelete' } }>();
* Basic type safe event emitter
* @example
* ```ts
* const ms = new EventEmitter<{ onChange: { id: 'onChange' }; onDelete: { id: 'onDelete' } }>();
* ms.subscribe('onChange', (event) => {

@@ -55,14 +64,12 @@ * event.id; // 'onChange'

* ms.notify('onChange', { id: 'onChange' }); // event is type safe
* ```
* @example <caption>payload type mismatch</caption>
* ```ts
* ms.notify('onChange', { id: 'onDelete' }); // ERROR!!!
* ```
* @example <caption>payload type mismatch</caption>
* ```ts
* ms.notify('onSomethingElse', { id: 'onDelete' }); // ERROR!!!
*
* ```
*/
export declare class MultiSignal<T extends Record<string, unknown>, K extends keyof T = keyof T> {
private signals;
has: <Id extends K>(id: Id) => boolean;
subscribe: <Id extends K>(id: Id, h: (data: T[Id]) => void) => void;
unsubscribe: <Id extends K>(id: Id, h: (data: T[Id]) => void) => void;
delete: <Id extends K>(id: Id) => void;
notify: <Id extends K>(id: Id, data: T[Id]) => void;
}
//# sourceMappingURL=signal.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Signal = void 0;
/**
* Signal is a simple event emitter for one type of event.
*
* Use Signals a public api for emitting events.
* Naming a signal is like naming the event the it triggers.
* If the name sounds like a property try to add a `on` prefix or `Change/Signal` suffix.
* All methods are bound to the Signal instance
*
* Simple example:
* ```
* @example
* ```ts
* const foodArrived = new Signal<Food>();

@@ -21,4 +18,4 @@ *

*
* Usage in a class:
* ```
* @example Usage in a class:
* ```ts
* class LoginService {

@@ -30,19 +27,31 @@ * public onLoginSuccess = new Signal<User>();

* ```
* @remarks
* Use Signals a public api for emitting events.
* Naming a signal is like naming the event the it triggers.
* If the name sounds like a property try to add a `on` prefix or `Change/Signal` suffix.
* All methods are bound to the Signal instance
*
* Notice that the Signals are public.
* We don't need to implement specific subscriptions on the class, unless we need to expose it as a remote service.
*
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MultiSignal = exports.Signal = void 0;
class Signal extends Set {
subscribe = (i) => {
this.add(i);
/**
* Subscribe a notification callback
* @param handler - Will be executed with a data arg when a notification occurs
*/
subscribe = (handler) => {
this.add(handler);
};
unsubscribe = (i) => {
this.delete(i);
/**
* Unsubscribe an existing callback
*/
unsubscribe = (handler) => {
this.delete(handler);
};
/**
* Notify all subscribers with arg data
*/
notify = (data) => {
for (const listener of this) {
listener(data);
for (const handler of this) {
handler(data);
}

@@ -53,7 +62,7 @@ };

/**
* Same as signal but for multiple types differentiating by the key.
*
* Usage
*
* const ms = new MultiSignal<{ onChange: { id: 'onChange' }; onDelete: { id: 'onDelete' } }>();
* Basic type safe event emitter
* @example
* ```ts
* const ms = new EventEmitter<{ onChange: { id: 'onChange' }; onDelete: { id: 'onDelete' } }>();
* ms.subscribe('onChange', (event) => {

@@ -67,26 +76,12 @@ * event.id; // 'onChange'

* ms.notify('onChange', { id: 'onChange' }); // event is type safe
* ```
* @example <caption>payload type mismatch</caption>
* ```ts
* ms.notify('onChange', { id: 'onDelete' }); // ERROR!!!
* ```
* @example <caption>payload type mismatch</caption>
* ```ts
* ms.notify('onSomethingElse', { id: 'onDelete' }); // ERROR!!!
*
* ```
*/
class MultiSignal {
signals = new Map();
has = (id) => this.signals.has(id);
subscribe = (id, h) => {
const bucket = this.signals.get(id);
bucket ? bucket.add(h) : this.signals.set(id, new Signal([h]));
};
unsubscribe = (id, h) => {
const bucket = this.signals.get(id);
bucket?.delete(h);
bucket?.size === 0 && this.signals.delete(id);
};
delete = (id) => {
this.signals.delete(id);
};
notify = (id, data) => {
this.signals.get(id)?.notify(data);
};
}
exports.MultiSignal = MultiSignal;
//# sourceMappingURL=signal.js.map

@@ -0,1 +1,5 @@

/**
* Disposables allow adding of disposal async functions,
* when dispose is called, these functions will be run sequentially
*/
export declare function createDisposables(): {

@@ -2,0 +6,0 @@ dispose(): Promise<void>;

@@ -0,1 +1,5 @@

/**
* Disposables allow adding of disposal async functions,
* when dispose is called, these functions will be run sequentially
*/
export function createDisposables() {

@@ -2,0 +6,0 @@ const disposables = new Set();

@@ -0,1 +1,25 @@

/**
* Cancelable debouncing of calls to trigger
* @example <caption>waitTime</caption>
* ```ts
* const debounced = new Debouncer(a => console.log(a), 1, 100)
* debounce.trigger('first')
* debounce.trigger('second')
* // after 1ms
* // => 'second'
* ```
* @example <caption>maxWaitTime</caption>
* ```ts
* const debounced = new Debouncer(a => console.log(a), 3, 4)
* debounce.trigger('first')
* // after 1ms
* debounce.trigger('second')
* // after 1ms
* // => 'second'
* debounce.trigger('third')
* // after 2ms, 4ms elapsed since the first trigger
* // => 'third'
* ```
*/
export declare class Debouncer<T extends (...args: unknown[]) => unknown> {

@@ -5,10 +29,32 @@ private cb;

private maxWaitTime;
private _setTimeout;
private _clearTimeout;
private timeout;
private maxTimeout;
constructor(cb: T, waitTime: number, maxWaitTime: number, _setTimeout?: (cb: () => void, wait: number) => number, _clearTimeout?: (id: number) => void);
private args;
private defer;
/**
*
* @param cb - trigger callback
* @param waitTime - time to wait before invoking cb
* @param maxWaitTime - maximum wait time from first triggering
*/
constructor(cb: T, waitTime: number, maxWaitTime: number);
private execute;
/**
*
* @param args - arguments to pass to cb
* @returns Promised resolved with cb return value
*/
trigger(...args: Parameters<T>): Promise<ReturnType<T>>;
/**
* Cancels pending invocation of cb
* @example
* ```ts
* const debounced = new Debouncer(a => console.log(a), 3, 4)
* debounce.trigger('first')
* debounce.cancel()
* // console.log will not be invoked
* ```
*/
cancel(): void;
}
//# sourceMappingURL=debouncer.d.ts.map

@@ -0,1 +1,26 @@

import { deferred } from 'promise-assist';
/**
* Cancelable debouncing of calls to trigger
* @example <caption>waitTime</caption>
* ```ts
* const debounced = new Debouncer(a => console.log(a), 1, 100)
* debounce.trigger('first')
* debounce.trigger('second')
* // after 1ms
* // => 'second'
* ```
* @example <caption>maxWaitTime</caption>
* ```ts
* const debounced = new Debouncer(a => console.log(a), 3, 4)
* debounce.trigger('first')
* // after 1ms
* debounce.trigger('second')
* // after 1ms
* // => 'second'
* debounce.trigger('third')
* // after 2ms, 4ms elapsed since the first trigger
* // => 'third'
* ```
*/
export class Debouncer {

@@ -5,54 +30,69 @@ cb;

maxWaitTime;
_setTimeout;
_clearTimeout;
timeout;
maxTimeout;
constructor(cb, waitTime, maxWaitTime, _setTimeout = (...args) =>
// eslint-disable-next-line
setTimeout(...args), _clearTimeout = (id) =>
// eslint-disable-next-line
clearTimeout(id)) {
args = [];
defer = deferred();
/**
*
* @param cb - trigger callback
* @param waitTime - time to wait before invoking cb
* @param maxWaitTime - maximum wait time from first triggering
*/
constructor(cb, waitTime, maxWaitTime) {
this.cb = cb;
this.waitTime = waitTime;
this.maxWaitTime = maxWaitTime;
this._setTimeout = _setTimeout;
this._clearTimeout = _clearTimeout;
}
execute() {
try {
const result = this.cb(...this.args);
this.defer.resolve(result);
}
catch (ex) {
this.defer.reject(ex);
}
this.defer = deferred();
}
/**
*
* @param args - arguments to pass to cb
* @returns Promised resolved with cb return value
*/
trigger(...args) {
return new Promise((res, rej) => {
if (this.timeout) {
this._clearTimeout(this.timeout);
this.args = args;
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(() => {
this.execute();
if (this.maxTimeout) {
clearTimeout(this.maxTimeout);
}
this.timeout = this._setTimeout(() => {
try {
res(this.cb(...args));
}, this.waitTime);
if (!this.maxTimeout) {
this.maxTimeout = setTimeout(() => {
this.execute();
if (this.timeout) {
clearTimeout(this.timeout);
}
catch (ex) {
rej(ex);
}
if (this.maxTimeout) {
this._clearTimeout(this.maxTimeout);
}
}, this.waitTime);
if (!this.maxTimeout) {
this.maxTimeout = this._setTimeout(() => {
try {
res(this.cb(...args));
}
catch (ex) {
rej(ex);
}
if (this.timeout) {
this._clearTimeout(this.timeout);
}
}, this.maxWaitTime);
}
});
}, this.maxWaitTime);
}
return this.defer.promise;
}
/**
* Cancels pending invocation of cb
* @example
* ```ts
* const debounced = new Debouncer(a => console.log(a), 3, 4)
* debounce.trigger('first')
* debounce.cancel()
* // console.log will not be invoked
* ```
*/
cancel() {
if (this.timeout) {
this._clearTimeout(this.timeout);
clearTimeout(this.timeout);
}
if (this.maxTimeout) {
this._clearTimeout(this.maxTimeout);
clearTimeout(this.maxTimeout);
}

@@ -59,0 +99,0 @@ }

@@ -1,14 +0,53 @@

import { SetMultiMap } from './set-multi-map';
export declare type EventListener<T> = (event: T) => void;
export declare class EventEmitter<T extends {}> {
listeners: SetMultiMap<keyof T, EventListener<any>>;
listenersOnce: SetMultiMap<keyof T, EventListener<any>>;
on<K extends keyof T>(eventName: K, listener: EventListener<T[K]>): void;
subscribe<K extends keyof T>(eventName: K, listener: EventListener<T[K]>): void;
once<K extends keyof T>(eventName: K, listener: EventListener<T[K]>): void;
off<K extends keyof T>(eventName: K, listener: EventListener<T[K]>): void;
unsubscribe<K extends keyof T>(eventName: K, listener: EventListener<T[K]>): void;
emit<K extends keyof T>(eventName: K, eventValue: T[K]): void;
clear(): void;
/**
* A simple event emitter
*/
export declare class EventEmitter<Events extends Record<string, unknown>, EventId extends keyof Events = keyof Events> {
private events;
private emitOnce;
/**
* Check if an event has subscribers
* @returns true if event has subscribers
*/
hasSubscribers: (event: EventId) => boolean;
/**
* Subscribe a handler for event
*/
subscribe: <Event_1 extends EventId>(event: Event_1, handler: (data: Events[Event_1]) => void) => void;
/**
* {@inheritDoc EventEmitter.subscribe}
*/
on: <Event_1 extends EventId>(event: Event_1, handler: (data: Events[Event_1]) => void) => void;
/**
* Adds a handler that will be called at most once
*/
once: <Event_1 extends EventId>(event: Event_1, handler: (data: Events[Event_1]) => void) => void;
/**
* Unsubscribe a handler from event
*/
unsubscribe: <Event_1 extends EventId>(event: Event_1, handler: (data: Events[Event_1]) => void) => void;
/**
* {@inheritDoc EventEmitter.unsubscribe}
*/
off: <Event_1 extends EventId>(event: Event_1, handler: (data: Events[Event_1]) => void) => void;
/**
* Drop all subscriptions of a signal
* @param event - signal id
*/
delete: <Event_1 extends EventId>(event: Event_1) => void;
/**
* Drop all subscriptions
*/
clear: () => void;
/**
* {@inheritDoc Signal.notify}
* @param event - eve
* @param data - event data
*/
notify: <Event_1 extends EventId>(event: Event_1, data: Events[Event_1]) => void;
/**
* {@inheritDoc EventEmitter.notify}
*/
emit: <Event_1 extends EventId>(event: Event_1, data: Events[Event_1]) => void;
}
export declare type IEventEmitter<T extends Record<string, any>> = Pick<EventEmitter<T>, keyof EventEmitter<T>>;
//# sourceMappingURL=event-emitter.d.ts.map

@@ -1,42 +0,77 @@

import { SetMultiMap } from './set-multi-map';
// eslint-disable-next-line @typescript-eslint/ban-types
import { Signal } from './signal';
/**
* A simple event emitter
*/
export class EventEmitter {
listeners = new SetMultiMap();
listenersOnce = new SetMultiMap();
on(eventName, listener) {
this.listeners.add(eventName, listener);
}
subscribe(eventName, listener) {
this.on(eventName, listener);
}
once(eventName, listener) {
this.listenersOnce.add(eventName, listener);
}
off(eventName, listener) {
this.listeners.delete(eventName, listener);
this.listenersOnce.delete(eventName, listener);
}
unsubscribe(eventName, listener) {
this.off(eventName, listener);
}
emit(eventName, eventValue) {
const listeners = this.listeners.get(eventName);
if (listeners) {
for (const listener of listeners) {
listener(eventValue);
}
}
const listenersOnce = this.listenersOnce.get(eventName);
if (listenersOnce) {
for (const listener of listenersOnce) {
listener(eventValue);
}
this.listenersOnce.deleteKey(eventName);
}
}
clear() {
this.listeners.clear();
this.listenersOnce.clear();
}
events = new Map();
emitOnce = new Map();
/**
* Check if an event has subscribers
* @returns true if event has subscribers
*/
hasSubscribers = (event) => this.events.has(event);
/**
* Subscribe a handler for event
*/
subscribe = (event, handler) => {
const bucket = this.events.get(event);
bucket ? bucket.add(handler) : this.events.set(event, new Signal([handler]));
};
/**
* {@inheritDoc EventEmitter.subscribe}
*/
on = this.subscribe;
/**
* Adds a handler that will be called at most once
*/
once = (event, handler) => {
this.off(event, handler);
const bucket = this.emitOnce.get(event);
bucket ? bucket.add(handler) : this.emitOnce.set(event, new Signal([handler]));
};
/**
* Unsubscribe a handler from event
*/
unsubscribe = (event, handler) => {
let bucket = this.events.get(event);
bucket?.delete(handler);
bucket?.size === 0 && this.events.delete(event);
bucket = this.emitOnce.get(event);
bucket?.delete(handler);
bucket?.size === 0 && this.events.delete(event);
};
/**
* {@inheritDoc EventEmitter.unsubscribe}
*/
off = this.unsubscribe;
/**
* Drop all subscriptions of a signal
* @param event - signal id
*/
delete = (event) => {
this.events.delete(event);
this.emitOnce.delete(event);
};
/**
* Drop all subscriptions
*/
clear = () => {
this.events = new Map();
this.emitOnce = new Map();
};
/**
* {@inheritDoc Signal.notify}
* @param event - eve
* @param data - event data
*/
notify = (event, data) => {
this.events.get(event)?.notify(data);
this.emitOnce.get(event)?.notify(data);
this.emitOnce.delete(event);
};
/**
* {@inheritDoc EventEmitter.notify}
*/
emit = this.notify;
}
//# sourceMappingURL=event-emitter.js.map

@@ -0,7 +1,7 @@

export interface LRUCacheConfig {
maxSize?: number;
}
/**
* BASIC (not optimal) implementation of the LRU cache
*/
export interface LRUCacheConfig {
maxSize?: number;
}
export declare class LRUCache<K, V> {

@@ -8,0 +8,0 @@ private config;

@@ -0,1 +1,13 @@

/**
* Maps keys to a set of values
* @example
* ```ts
* const m = new SetMultiMap([['a',1],['a',2]])
* m.add('a',3)
* m.has('a',1) // => true
* m.has('a',2) // => true
* m.has('a',3) // => true
* m.has('a',4) // => false
* ```
*/
export declare class SetMultiMap<K, V> implements Iterable<[K, V]> {

@@ -2,0 +14,0 @@ private map;

@@ -1,5 +0,14 @@

/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { chain, forEach } from '@wixc3/common';
import { forEach, chain } from '@wixc3/common';
/**
* Maps keys to a set of values
* @example
* ```ts
* const m = new SetMultiMap([['a',1],['a',2]])
* m.add('a',3)
* m.has('a',1) // => true
* m.has('a',2) // => true
* m.has('a',3) // => true
* m.has('a',4) // => false
* ```
*/
export class SetMultiMap {

@@ -6,0 +15,0 @@ map = new Map();

@@ -0,11 +1,7 @@

export declare type Listener<T> = (data: T) => void;
/**
* Signal is a simple event emitter for one type of event.
*
* Use Signals a public api for emitting events.
* Naming a signal is like naming the event the it triggers.
* If the name sounds like a property try to add a `on` prefix or `Change/Signal` suffix.
* All methods are bound to the Signal instance
*
* Simple example:
* ```
* @example
* ```ts
* const foodArrived = new Signal<Food>();

@@ -20,4 +16,4 @@ *

*
* Usage in a class:
* ```
* @example Usage in a class:
* ```ts
* class LoginService {

@@ -29,19 +25,32 @@ * public onLoginSuccess = new Signal<User>();

* ```
* @remarks
* Use Signals a public api for emitting events.
* Naming a signal is like naming the event the it triggers.
* If the name sounds like a property try to add a `on` prefix or `Change/Signal` suffix.
* All methods are bound to the Signal instance
*
* Notice that the Signals are public.
* We don't need to implement specific subscriptions on the class, unless we need to expose it as a remote service.
*
*/
export declare type Listener<T> = (data: T) => void;
export declare class Signal<T> extends Set<Listener<T>> {
subscribe: (i: Listener<T>) => void;
unsubscribe: (i: Listener<T>) => void;
/**
* Subscribe a notification callback
* @param handler - Will be executed with a data arg when a notification occurs
*/
subscribe: (handler: Listener<T>) => void;
/**
* Unsubscribe an existing callback
*/
unsubscribe: (handler: Listener<T>) => void;
/**
* Notify all subscribers with arg data
*/
notify: (data: T) => void;
}
/**
* Same as signal but for multiple types differentiating by the key.
*
* Usage
*
* const ms = new MultiSignal<{ onChange: { id: 'onChange' }; onDelete: { id: 'onDelete' } }>();
* Basic type safe event emitter
* @example
* ```ts
* const ms = new EventEmitter<{ onChange: { id: 'onChange' }; onDelete: { id: 'onDelete' } }>();
* ms.subscribe('onChange', (event) => {

@@ -55,14 +64,12 @@ * event.id; // 'onChange'

* ms.notify('onChange', { id: 'onChange' }); // event is type safe
* ```
* @example <caption>payload type mismatch</caption>
* ```ts
* ms.notify('onChange', { id: 'onDelete' }); // ERROR!!!
* ```
* @example <caption>payload type mismatch</caption>
* ```ts
* ms.notify('onSomethingElse', { id: 'onDelete' }); // ERROR!!!
*
* ```
*/
export declare class MultiSignal<T extends Record<string, unknown>, K extends keyof T = keyof T> {
private signals;
has: <Id extends K>(id: Id) => boolean;
subscribe: <Id extends K>(id: Id, h: (data: T[Id]) => void) => void;
unsubscribe: <Id extends K>(id: Id, h: (data: T[Id]) => void) => void;
delete: <Id extends K>(id: Id) => void;
notify: <Id extends K>(id: Id, data: T[Id]) => void;
}
//# sourceMappingURL=signal.d.ts.map
/**
* Signal is a simple event emitter for one type of event.
*
* Use Signals a public api for emitting events.
* Naming a signal is like naming the event the it triggers.
* If the name sounds like a property try to add a `on` prefix or `Change/Signal` suffix.
* All methods are bound to the Signal instance
*
* Simple example:
* ```
* @example
* ```ts
* const foodArrived = new Signal<Food>();

@@ -20,4 +15,4 @@ *

*
* Usage in a class:
* ```
* @example Usage in a class:
* ```ts
* class LoginService {

@@ -29,17 +24,31 @@ * public onLoginSuccess = new Signal<User>();

* ```
* @remarks
* Use Signals a public api for emitting events.
* Naming a signal is like naming the event the it triggers.
* If the name sounds like a property try to add a `on` prefix or `Change/Signal` suffix.
* All methods are bound to the Signal instance
*
* Notice that the Signals are public.
* We don't need to implement specific subscriptions on the class, unless we need to expose it as a remote service.
*
*/
export class Signal extends Set {
subscribe = (i) => {
this.add(i);
/**
* Subscribe a notification callback
* @param handler - Will be executed with a data arg when a notification occurs
*/
subscribe = (handler) => {
this.add(handler);
};
unsubscribe = (i) => {
this.delete(i);
/**
* Unsubscribe an existing callback
*/
unsubscribe = (handler) => {
this.delete(handler);
};
/**
* Notify all subscribers with arg data
*/
notify = (data) => {
for (const listener of this) {
listener(data);
for (const handler of this) {
handler(data);
}

@@ -49,7 +58,7 @@ };

/**
* Same as signal but for multiple types differentiating by the key.
*
* Usage
*
* const ms = new MultiSignal<{ onChange: { id: 'onChange' }; onDelete: { id: 'onDelete' } }>();
* Basic type safe event emitter
* @example
* ```ts
* const ms = new EventEmitter<{ onChange: { id: 'onChange' }; onDelete: { id: 'onDelete' } }>();
* ms.subscribe('onChange', (event) => {

@@ -63,25 +72,12 @@ * event.id; // 'onChange'

* ms.notify('onChange', { id: 'onChange' }); // event is type safe
* ```
* @example <caption>payload type mismatch</caption>
* ```ts
* ms.notify('onChange', { id: 'onDelete' }); // ERROR!!!
* ```
* @example <caption>payload type mismatch</caption>
* ```ts
* ms.notify('onSomethingElse', { id: 'onDelete' }); // ERROR!!!
*
* ```
*/
export class MultiSignal {
signals = new Map();
has = (id) => this.signals.has(id);
subscribe = (id, h) => {
const bucket = this.signals.get(id);
bucket ? bucket.add(h) : this.signals.set(id, new Signal([h]));
};
unsubscribe = (id, h) => {
const bucket = this.signals.get(id);
bucket?.delete(h);
bucket?.size === 0 && this.signals.delete(id);
};
delete = (id) => {
this.signals.delete(id);
};
notify = (id, data) => {
this.signals.get(id)?.notify(data);
};
}
//# sourceMappingURL=signal.js.map
{
"name": "@wixc3/patterns",
"version": "2.2.0",
"version": "3.0.0",
"description": "A utility for saving objects to be disposed",

@@ -5,0 +5,0 @@ "main": "dist/cjs/index.js",

@@ -0,1 +1,5 @@

/**
* Disposables allow adding of disposal async functions,
* when dispose is called, these functions will be run sequentially
*/
export function createDisposables() {

@@ -2,0 +6,0 @@ const disposables = new Set<() => unknown>();

@@ -0,52 +1,94 @@

import { deferred } from 'promise-assist';
/**
* Cancelable debouncing of calls to trigger
* @example <caption>waitTime</caption>
* ```ts
* const debounced = new Debouncer(a => console.log(a), 1, 100)
* debounce.trigger('first')
* debounce.trigger('second')
* // after 1ms
* // => 'second'
* ```
* @example <caption>maxWaitTime</caption>
* ```ts
* const debounced = new Debouncer(a => console.log(a), 3, 4)
* debounce.trigger('first')
* // after 1ms
* debounce.trigger('second')
* // after 1ms
* // => 'second'
* debounce.trigger('third')
* // after 2ms, 4ms elapsed since the first trigger
* // => 'third'
* ```
*/
export class Debouncer<T extends (...args: unknown[]) => unknown> {
private timeout: number | undefined;
private maxTimeout: number | undefined;
constructor(
private cb: T,
private waitTime: number,
private maxWaitTime: number,
private _setTimeout: (cb: () => void, wait: number) => number = (...args) =>
// eslint-disable-next-line
setTimeout(...args),
private _clearTimeout: (id: number) => void = (id) =>
// eslint-disable-next-line
clearTimeout(id)
) {}
private args = [] as any as Parameters<T>;
private defer = deferred<ReturnType<T>>();
/**
*
* @param cb - trigger callback
* @param waitTime - time to wait before invoking cb
* @param maxWaitTime - maximum wait time from first triggering
*/
constructor(private cb: T, private waitTime: number, private maxWaitTime: number) {}
private execute() {
try {
const result = this.cb(...this.args) as ReturnType<T>;
this.defer.resolve(result);
} catch (ex) {
this.defer.reject(ex);
}
this.defer = deferred();
}
/**
*
* @param args - arguments to pass to cb
* @returns Promised resolved with cb return value
*/
trigger(...args: Parameters<T>) {
return new Promise<ReturnType<T>>((res, rej) => {
if (this.timeout) {
this._clearTimeout(this.timeout);
this.args = args;
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(() => {
this.execute();
if (this.maxTimeout) {
clearTimeout(this.maxTimeout);
}
this.timeout = this._setTimeout(() => {
try {
res(this.cb(...args) as ReturnType<T>);
} catch (ex) {
rej(ex);
}, this.waitTime);
if (!this.maxTimeout) {
this.maxTimeout = setTimeout(() => {
this.execute();
if (this.timeout) {
clearTimeout(this.timeout);
}
if (this.maxTimeout) {
this._clearTimeout(this.maxTimeout);
}
}, this.waitTime);
if (!this.maxTimeout) {
this.maxTimeout = this._setTimeout(() => {
try {
res(this.cb(...args) as ReturnType<T>);
} catch (ex) {
rej(ex);
}
if (this.timeout) {
this._clearTimeout(this.timeout);
}
}, this.maxWaitTime);
}
});
}, this.maxWaitTime);
}
return this.defer.promise;
}
/**
* Cancels pending invocation of cb
* @example
* ```ts
* const debounced = new Debouncer(a => console.log(a), 3, 4)
* debounce.trigger('first')
* debounce.cancel()
* // console.log will not be invoked
* ```
*/
cancel() {
if (this.timeout) {
this._clearTimeout(this.timeout);
clearTimeout(this.timeout);
}
if (this.maxTimeout) {
this._clearTimeout(this.maxTimeout);
clearTimeout(this.maxTimeout);
}
}
}

@@ -1,51 +0,89 @@

import { SetMultiMap } from './set-multi-map';
import { Signal } from './signal';
export type EventListener<T> = (event: T) => void;
/**
* A simple event emitter
*/
export class EventEmitter<Events extends Record<string, unknown>, EventId extends keyof Events = keyof Events> {
private events = new Map<EventId, Signal<any>>();
private emitOnce = new Map<EventId, Signal<any>>();
// eslint-disable-next-line @typescript-eslint/ban-types
export class EventEmitter<T extends {}> {
public listeners = new SetMultiMap<keyof T, EventListener<any>>();
public listenersOnce = new SetMultiMap<keyof T, EventListener<any>>();
/**
* Check if an event has subscribers
* @returns true if event has subscribers
*/
hasSubscribers = (event: EventId) => this.events.has(event);
public on<K extends keyof T>(eventName: K, listener: EventListener<T[K]>) {
this.listeners.add(eventName, listener);
}
/**
* Subscribe a handler for event
*/
subscribe = <Event extends EventId>(event: Event, handler: (data: Events[Event]) => void) => {
const bucket = this.events.get(event);
bucket ? bucket.add(handler) : this.events.set(event, new Signal([handler]));
};
public subscribe<K extends keyof T>(eventName: K, listener: EventListener<T[K]>) {
this.on(eventName, listener);
}
/**
* {@inheritDoc EventEmitter.subscribe}
*/
on = this.subscribe;
public once<K extends keyof T>(eventName: K, listener: EventListener<T[K]>) {
this.listenersOnce.add(eventName, listener);
}
/**
* Adds a handler that will be called at most once
*/
once = <Event extends EventId>(event: Event, handler: (data: Events[Event]) => void) => {
this.off(event, handler);
const bucket = this.emitOnce.get(event);
bucket ? bucket.add(handler) : this.emitOnce.set(event, new Signal([handler]));
};
public off<K extends keyof T>(eventName: K, listener: EventListener<T[K]>) {
this.listeners.delete(eventName, listener);
this.listenersOnce.delete(eventName, listener);
}
/**
* Unsubscribe a handler from event
*/
unsubscribe = <Event extends EventId>(event: Event, handler: (data: Events[Event]) => void) => {
let bucket = this.events.get(event);
bucket?.delete(handler);
bucket?.size === 0 && this.events.delete(event);
bucket = this.emitOnce.get(event);
bucket?.delete(handler);
bucket?.size === 0 && this.events.delete(event);
};
public unsubscribe<K extends keyof T>(eventName: K, listener: EventListener<T[K]>) {
this.off(eventName, listener);
}
/**
* {@inheritDoc EventEmitter.unsubscribe}
*/
off = this.unsubscribe;
public emit<K extends keyof T>(eventName: K, eventValue: T[K]) {
const listeners = this.listeners.get(eventName);
if (listeners) {
for (const listener of listeners) {
listener(eventValue);
}
}
const listenersOnce = this.listenersOnce.get(eventName);
if (listenersOnce) {
for (const listener of listenersOnce) {
listener(eventValue);
}
this.listenersOnce.deleteKey(eventName);
}
}
/**
* Drop all subscriptions of a signal
* @param event - signal id
*/
delete = <Event extends EventId>(event: Event) => {
this.events.delete(event);
this.emitOnce.delete(event);
};
public clear() {
this.listeners.clear();
this.listenersOnce.clear();
}
/**
* Drop all subscriptions
*/
clear = () => {
this.events = new Map<EventId, Signal<any>>();
this.emitOnce = new Map<EventId, Signal<any>>();
};
/**
* {@inheritDoc Signal.notify}
* @param event - eve
* @param data - event data
*/
notify = <Event extends EventId>(event: Event, data: Events[Event]) => {
this.events.get(event)?.notify(data);
this.emitOnce.get(event)?.notify(data);
this.emitOnce.delete(event);
};
/**
* {@inheritDoc EventEmitter.notify}
*/
emit = this.notify;
}
export type IEventEmitter<T extends Record<string, any>> = Pick<EventEmitter<T>, keyof EventEmitter<T>>;

@@ -1,5 +0,1 @@

/**
* BASIC (not optimal) implementation of the LRU cache
*/
export interface LRUCacheConfig {

@@ -9,2 +5,5 @@ maxSize?: number;

/**
* BASIC (not optimal) implementation of the LRU cache
*/
export class LRUCache<K, V> {

@@ -11,0 +10,0 @@ private cache = new Map<K, V>();

@@ -1,6 +0,15 @@

/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { chain, forEach } from '@wixc3/common';
import { forEach, chain } from '@wixc3/common';
/**
* Maps keys to a set of values
* @example
* ```ts
* const m = new SetMultiMap([['a',1],['a',2]])
* m.add('a',3)
* m.has('a',1) // => true
* m.has('a',2) // => true
* m.has('a',3) // => true
* m.has('a',4) // => false
* ```
*/
export class SetMultiMap<K, V> implements Iterable<[K, V]> {

@@ -10,3 +19,3 @@ private map = new Map<K, Set<V>>();

constructor(entries?: Iterable<[K, V]>) {
forEach(entries, ([key, val]:[K,V]) => {
forEach(entries, ([key, val]: [K, V]) => {
this.add(key, val);

@@ -18,4 +27,4 @@ });

return chain(this.map)
.map(([_, { size }]:[unknown,{size:number}]) => size)
.reduce((sum:number, size:number) => sum + size, 0).value;
.map(([_, { size }]) => size)
.reduce((sum: number, size: number) => sum + size, 0).value;
}

@@ -22,0 +31,0 @@

@@ -0,11 +1,8 @@

export type Listener<T> = (data: T) => void;
/**
* Signal is a simple event emitter for one type of event.
*
* Use Signals a public api for emitting events.
* Naming a signal is like naming the event the it triggers.
* If the name sounds like a property try to add a `on` prefix or `Change/Signal` suffix.
* All methods are bound to the Signal instance
*
* Simple example:
* ```
* @example
* ```ts
* const foodArrived = new Signal<Food>();

@@ -20,4 +17,4 @@ *

*
* Usage in a class:
* ```
* @example Usage in a class:
* ```ts
* class LoginService {

@@ -29,20 +26,31 @@ * public onLoginSuccess = new Signal<User>();

* ```
*
* @remarks
* Use Signals a public api for emitting events.
* Naming a signal is like naming the event the it triggers.
* If the name sounds like a property try to add a `on` prefix or `Change/Signal` suffix.
* All methods are bound to the Signal instance
*
* Notice that the Signals are public.
* We don't need to implement specific subscriptions on the class, unless we need to expose it as a remote service.
*
*/
export type Listener<T> = (data: T) => void;
export class Signal<T> extends Set<Listener<T>> {
subscribe = (i: Listener<T>) => {
this.add(i);
/**
* Subscribe a notification callback
* @param handler - Will be executed with a data arg when a notification occurs
*/
subscribe = (handler: Listener<T>) => {
this.add(handler);
};
unsubscribe = (i: Listener<T>) => {
this.delete(i);
/**
* Unsubscribe an existing callback
*/
unsubscribe = (handler: Listener<T>) => {
this.delete(handler);
};
/**
* Notify all subscribers with arg data
*/
notify = (data: T) => {
for (const listener of this) {
listener(data);
for (const handler of this) {
handler(data);
}

@@ -53,7 +61,7 @@ };

/**
* Same as signal but for multiple types differentiating by the key.
*
* Usage
*
* const ms = new MultiSignal<{ onChange: { id: 'onChange' }; onDelete: { id: 'onDelete' } }>();
* Basic type safe event emitter
* @example
* ```ts
* const ms = new EventEmitter<{ onChange: { id: 'onChange' }; onDelete: { id: 'onDelete' } }>();
* ms.subscribe('onChange', (event) => {

@@ -67,24 +75,11 @@ * event.id; // 'onChange'

* ms.notify('onChange', { id: 'onChange' }); // event is type safe
* ```
* @example <caption>payload type mismatch</caption>
* ```ts
* ms.notify('onChange', { id: 'onDelete' }); // ERROR!!!
* ```
* @example <caption>payload type mismatch</caption>
* ```ts
* ms.notify('onSomethingElse', { id: 'onDelete' }); // ERROR!!!
*
* ```
*/
export class MultiSignal<T extends Record<string, unknown>, K extends keyof T = keyof T> {
private signals = new Map<K, Signal<any>>();
has = <Id extends K>(id: Id) => this.signals.has(id);
subscribe = <Id extends K>(id: Id, h: (data: T[Id]) => void) => {
const bucket = this.signals.get(id);
bucket ? bucket.add(h) : this.signals.set(id, new Signal([h]));
};
unsubscribe = <Id extends K>(id: Id, h: (data: T[Id]) => void) => {
const bucket = this.signals.get(id);
bucket?.delete(h);
bucket?.size === 0 && this.signals.delete(id);
};
delete = <Id extends K>(id: Id) => {
this.signals.delete(id);
};
notify = <Id extends K>(id: Id, data: T[Id]) => {
this.signals.get(id)?.notify(data);
};
}

@@ -1,77 +0,73 @@

/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { expect } from 'chai';
import sinon from 'sinon';
import { spy, SinonFakeTimers, useFakeTimers, SinonSpy } from 'sinon';
import { Debouncer } from '../debouncer';
import { reportError } from '@wixc3/common';
describe('Debounce', () => {
class MockTimeout {
private counter = 0;
private cbCounter = 0;
private cbMap: Map<number, { cb: () => void; time: number }> = new Map();
setTimeout = (cb: () => void, wait: number) => {
this.cbCounter++;
this.cbMap.set(this.cbCounter, {
cb,
time: this.counter + wait,
});
return this.cbCounter;
};
clearTimeOut = (id: number) => {
this.cbMap.delete(id);
};
tick(num = 1) {
this.counter += num;
for (const [id, { cb, time }] of this.cbMap.entries()) {
if (this.counter === time) {
cb();
this.cbMap.delete(id);
}
}
}
}
let promises: Promise<any>[];
let clock: SinonFakeTimers;
let callback: SinonSpy;
beforeEach(() => {
promises = [];
callback = spy();
clock = useFakeTimers();
});
afterEach(async function () {
this.timeout(100);
clock.tick(100_000);
clock.restore();
await Promise.all(promises);
});
it('should delay call by requested timeout', () => {
const spy = sinon.spy();
const timeoutMock = new MockTimeout();
const throttle = new Debouncer(spy, 1, 100, timeoutMock.setTimeout, timeoutMock.clearTimeOut);
throttle.trigger().catch(reportError);
expect(spy).to.have.not.been.called;
timeoutMock.tick();
expect(spy.callCount).to.equal(1);
const throttle = new Debouncer(callback, 1, 100);
promises.push(throttle.trigger());
expect(callback).to.have.callCount(0);
clock.tick(1);
expect(callback).to.have.callCount(1);
});
it('should delay calls while there are calls coming, call cb only once', () => {
const spy = sinon.spy();
const timeoutMock = new MockTimeout();
const throttle = new Debouncer(spy, 2, 100, timeoutMock.setTimeout, timeoutMock.clearTimeOut);
throttle.trigger().catch(reportError);
timeoutMock.tick();
throttle.trigger().catch(reportError);
timeoutMock.tick();
throttle.trigger().catch(reportError);
timeoutMock.tick();
throttle.trigger().catch(reportError);
expect(spy).to.have.not.been.called;
timeoutMock.tick(2);
expect(spy.callCount).to.equal(1);
const throttle = new Debouncer(callback, 2, 100);
promises.push(throttle.trigger());
clock.tick(1);
promises.push(throttle.trigger());
clock.tick(1);
promises.push(throttle.trigger());
clock.tick(1);
promises.push(throttle.trigger());
expect(callback).to.have.callCount(0);
clock.tick(2);
expect(callback).to.have.callCount(1);
});
it('if delaying call for more than max time, should trigger cb, go back to delaying', () => {
const spy = sinon.spy();
const timeoutMock = new MockTimeout();
const throttle = new Debouncer(spy, 2, 6, timeoutMock.setTimeout, timeoutMock.clearTimeOut);
const maxTimeout = 5;
const throttle = new Debouncer(callback, 3, maxTimeout);
for (let i = 0; i < maxTimeout + 1; i++) {
promises.push(throttle.trigger());
clock.tick(1);
}
expect(callback.callCount).to.equal(1);
clock.tick(2);
expect(callback.callCount).to.equal(2);
});
it(`passes the last trigger's arguments at waitTime`, () => {
const throttle = new Debouncer(callback, 2, 6);
promises.push(throttle.trigger(0));
clock.tick(1);
promises.push(throttle.trigger(1));
expect(callback).to.have.callCount(0);
clock.tick(1);
clock.tick(1);
expect(callback).to.have.been.calledOnceWith(1);
});
it(`passes the last trigger's arguments at maxWaitTime`, () => {
const throttle = new Debouncer(callback, 2, 6);
for (let i = 0; i < 6; i++) {
timeoutMock.tick();
// eslint-disable-next-line @typescript-eslint/no-floating-promises
throttle.trigger();
clock.tick(1);
promises.push(throttle.trigger(i));
}
expect(spy).to.have.not.been.called;
timeoutMock.tick();
expect(spy.callCount).to.equal(1);
for (let i = 0; i < 3; i++) {
timeoutMock.tick();
// eslint-disable-next-line @typescript-eslint/no-floating-promises
throttle.trigger();
}
expect(spy.callCount).to.equal(1);
expect(callback).to.have.callCount(0);
clock.tick(1);
expect(callback).to.have.been.calledOnceWith(5);
});
});
import chai, { expect } from 'chai';
import { Signal } from '..';
import { Signal } from '../signal';
import { stub } from 'sinon';

@@ -4,0 +4,0 @@ import sinonChai from 'sinon-chai';

@@ -6,3 +6,6 @@ {

"module": "esnext"
}
},
"references": [
{ "path": "../../common/src/tsconfig.esm.json" }
]
}

@@ -5,3 +5,6 @@ {

"outDir": "../dist/cjs"
}
},
"references": [
{ "path": "../../common/src" }
]
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc