mini-signals
Advanced tools
Comparing version 2.0.0-0 to 2.0.0-1
@@ -1,2 +0,1 @@ | ||
export { MiniSignalBinding } from './mini-signals-binding'; | ||
export { MiniSignal } from './mini-signals'; | ||
export * from './mini-signals'; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.MiniSignal = exports.MiniSignalBinding = void 0; | ||
var mini_signals_binding_1 = require("./mini-signals-binding"); | ||
Object.defineProperty(exports, "MiniSignalBinding", { enumerable: true, get: function () { return mini_signals_binding_1.MiniSignalBinding; } }); | ||
var mini_signals_1 = require("./mini-signals"); | ||
Object.defineProperty(exports, "MiniSignal", { enumerable: true, get: function () { return mini_signals_1.MiniSignal; } }); | ||
__exportStar(require("./mini-signals"), exports); |
@@ -1,15 +0,19 @@ | ||
import { type BoundFunction, MiniSignalBinding } from './mini-signals-binding'; | ||
export declare class MiniSignal<T extends any[] = any[], A extends any | undefined = any | undefined> { | ||
type CallBack<T extends any[]> = (...x: T) => void; | ||
declare const MiniSignalSymbol: unique symbol; | ||
type MiniSignalNode<T extends any[]> = { | ||
fn: CallBack<T>; | ||
next?: MiniSignalNode<T>; | ||
prev?: MiniSignalNode<T>; | ||
[MiniSignalSymbol]?: symbol; | ||
}; | ||
type MiniSignalRef<T extends any[], S extends any> = WeakRef<MiniSignalNode<T>> & S; | ||
export declare class MiniSignal<T extends any[] = any[], S extends any = { | ||
[MiniSignalSymbol]: true; | ||
}> { | ||
private _head?; | ||
private _tail?; | ||
hasHandlers(): boolean; | ||
private readonly symbol; | ||
private dispatching; | ||
hasListeners(): boolean; | ||
/** | ||
* Return an array of attached MiniSignalBinding. | ||
*/ | ||
handlers(): Array<MiniSignalBinding<T, A>>; | ||
/** | ||
* Return true if node is a MiniSignalBinding attached to this MiniSignal | ||
*/ | ||
has(node: MiniSignalBinding<T, A>): boolean; | ||
/** | ||
* Dispatches a signal to all registered listeners. | ||
@@ -21,11 +25,7 @@ */ | ||
*/ | ||
add(fn: BoundFunction<T>, thisArg?: A): MiniSignalBinding<T, A>; | ||
add(fn: CallBack<T>): MiniSignalRef<T, S>; | ||
/** | ||
* Register a new listener that will be executed only once. | ||
*/ | ||
once(fn: BoundFunction<T>, thisArg?: A): MiniSignalBinding; | ||
/** | ||
* Remove binding object. | ||
*/ | ||
detach(node: MiniSignalBinding<T, A>): this; | ||
detach(ref: MiniSignalRef<T, S>): this; | ||
/** | ||
@@ -35,3 +35,6 @@ * Detach all listeners. | ||
detachAll(): this; | ||
private _addMiniSignalBinding; | ||
private _destroyNode; | ||
private _disconnectNode; | ||
private _addNode; | ||
} | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.MiniSignal = void 0; | ||
const mini_signals_binding_1 = require("./mini-signals-binding"); | ||
const MiniSignalSymbol = Symbol('MiniSignalSymbol'); | ||
class MiniSignal { | ||
@@ -9,40 +9,24 @@ constructor() { | ||
this._tail = undefined; | ||
this.symbol = Symbol('MiniSignal'); | ||
this.dispatching = false; | ||
} | ||
hasHandlers() { | ||
hasListeners() { | ||
return !(this._head == null); | ||
} | ||
/** | ||
* Return an array of attached MiniSignalBinding. | ||
*/ | ||
handlers() { | ||
let node = this._head; | ||
const ee = []; | ||
while (node != null) { | ||
ee.push(node); | ||
node = node._next; | ||
} | ||
return ee; | ||
} | ||
/** | ||
* Return true if node is a MiniSignalBinding attached to this MiniSignal | ||
*/ | ||
has(node) { | ||
if (!(node instanceof mini_signals_binding_1.MiniSignalBinding)) { | ||
throw new Error('MiniSignal#has(): First arg must be a MiniSignalBinding object.'); | ||
} | ||
return node._owner === this; | ||
} | ||
/** | ||
* Dispatches a signal to all registered listeners. | ||
*/ | ||
dispatch(...args) { | ||
if (this.dispatching) { | ||
throw new Error('MiniSignal#dispatch(): Signal already dispatching.'); | ||
} | ||
let node = this._head; | ||
if (node == null) | ||
return false; | ||
this.dispatching = true; | ||
while (node != null) { | ||
if (node._once) | ||
this.detach(node); | ||
node._fn.apply(node._thisArg, args); | ||
node = node._next; | ||
node.fn(...args); | ||
node = node.next; | ||
} | ||
this.dispatching = false; | ||
return true; | ||
@@ -53,34 +37,51 @@ } | ||
*/ | ||
add(fn, thisArg) { | ||
add(fn) { | ||
if (typeof fn !== 'function') { | ||
throw new Error('MiniSignal#add(): First arg must be a Function.'); | ||
} | ||
return this._addMiniSignalBinding(new mini_signals_binding_1.MiniSignalBinding(fn, false, thisArg)); | ||
return this._addNode({ | ||
fn, | ||
[MiniSignalSymbol]: this.symbol | ||
}); | ||
} | ||
/** | ||
* Register a new listener that will be executed only once. | ||
* Remove binding object. | ||
*/ | ||
once(fn, thisArg) { | ||
if (typeof fn !== 'function') { | ||
throw new Error('MiniSignal#once(): First arg must be a Function.'); | ||
detach(ref) { | ||
if (!(ref instanceof WeakRef)) { | ||
throw new Error('MiniSignal#detach(): First arg must be a MiniSignalNode object.'); | ||
} | ||
return this._addMiniSignalBinding(new mini_signals_binding_1.MiniSignalBinding(fn, true, thisArg)); | ||
const node = ref.deref(); | ||
if (!node || !node[MiniSignalSymbol]) | ||
return this; | ||
if (node[MiniSignalSymbol] !== this.symbol) | ||
return this; // Error? | ||
this._disconnectNode(node); | ||
this._destroyNode(node); | ||
return this; | ||
} | ||
/** | ||
* Remove binding object. | ||
* Detach all listeners. | ||
*/ | ||
detach(node) { | ||
if (!(node instanceof mini_signals_binding_1.MiniSignalBinding)) { | ||
throw new Error('MiniSignal#detach(): First arg must be a MiniSignalBinding object.'); | ||
detachAll() { | ||
let n = this._head; | ||
if (n == null) | ||
return this; | ||
this._head = this._tail = undefined; | ||
while (n != null) { | ||
this._destroyNode(n); | ||
n = n.next; | ||
} | ||
if (node._owner !== this) | ||
return this; // todo: or error? | ||
if (node._prev !== undefined && node._prev !== null) | ||
node._prev._next = node._next; | ||
if (node._next !== undefined && node._next !== null) | ||
node._next._prev = node._prev; | ||
return this; | ||
} | ||
_destroyNode(node) { | ||
node.fn = undefined; | ||
node.prev = undefined; | ||
node[MiniSignalSymbol] = undefined; | ||
} | ||
_disconnectNode(node) { | ||
if (node === this._head) { | ||
// first node | ||
this._head = node._next; | ||
if (node._next === null) { | ||
this._head = node.next; | ||
if (node.next == null) { | ||
this._tail = undefined; | ||
@@ -91,24 +92,16 @@ } | ||
// last node | ||
this._tail = node._prev; | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
this._tail._next = undefined; | ||
this._tail = node.prev; | ||
if (this._tail != null) { | ||
this._tail.next = undefined; | ||
} | ||
} | ||
node._owner = null; | ||
return this; | ||
} | ||
/** | ||
* Detach all listeners. | ||
*/ | ||
detachAll() { | ||
let node = this._head; | ||
if (node == null) | ||
return this; | ||
this._head = this._tail = undefined; | ||
while (node != null) { | ||
node._owner = null; | ||
node = node._next; | ||
if (node.prev != null) { | ||
node.prev.next = node.next; | ||
} | ||
return this; | ||
if (node.next != null) { | ||
node.next.prev = node.prev; | ||
} | ||
node[MiniSignalSymbol] = undefined; | ||
} | ||
_addMiniSignalBinding(node) { | ||
_addNode(node) { | ||
if (this._head == null) { | ||
@@ -120,10 +113,9 @@ this._head = node; | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
this._tail._next = node; | ||
node._prev = this._tail; | ||
this._tail.next = node; | ||
node.prev = this._tail; | ||
this._tail = node; | ||
} | ||
node._owner = this; | ||
return node; | ||
return new WeakRef(node); | ||
} | ||
} | ||
exports.MiniSignal = MiniSignal; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const mini_signals_1 = require("../src/mini-signals"); | ||
const mini_signals_binding_1 = require("../src/mini-signals-binding"); | ||
const node_util_1 = require("node:util"); | ||
const chai_1 = require("chai"); | ||
const node_assert_1 = require("node:assert"); | ||
describe('MiniSignal', () => { | ||
it('inherits when used with require(util).inherits', () => { | ||
function Beast() { | ||
/* rawr, i'm a beast */ | ||
} | ||
(0, node_util_1.inherits)(Beast, mini_signals_1.MiniSignal); | ||
const moop = new Beast(); | ||
const meap = new Beast(); | ||
(0, chai_1.expect)(moop).is.instanceOf(Beast); | ||
(0, chai_1.expect)(moop).is.instanceOf(mini_signals_1.MiniSignal); | ||
moop.handlers(); | ||
meap.handlers(); | ||
/* istanbul ignore next */ | ||
moop.add(() => { | ||
throw new Error('I should not dispatch'); | ||
}); | ||
meap.dispatch('rawr'); | ||
meap.detachAll(); | ||
const pattern = []; | ||
const writer = (a) => { | ||
pattern.push(a); | ||
}; | ||
beforeEach(() => { | ||
pattern.length = 0; | ||
}); | ||
it('quick test', () => { | ||
const pattern = []; | ||
const e = new mini_signals_1.MiniSignal(); | ||
const foo = e.add(writer, 'foo'); | ||
e.add(writer, 'baz'); | ||
const bar = e.add(writer, 'bar'); | ||
const foo = e.add(writer); | ||
e.add(writer); | ||
const bar = e.add(writer); | ||
(0, chai_1.expect)(e instanceof mini_signals_1.MiniSignal); | ||
(0, chai_1.expect)(foo instanceof mini_signals_binding_1.MiniSignalBinding); | ||
e.dispatch('banana'); | ||
e.dispatch('apple'); | ||
foo.detach(); | ||
bar.detach(); | ||
e.detach(foo); | ||
e.detach(bar); | ||
e.dispatch('pear'); | ||
e.detachAll(); | ||
e.dispatch('raspberry'); | ||
(0, chai_1.expect)(pattern.join(';') === | ||
'foo:banana;baz:banana;bar:banana;foo:apple;baz:apple;bar:apple;baz:pear'); | ||
function writer(a) { | ||
pattern.push(String(this) + ':' + a); | ||
} | ||
(0, chai_1.expect)(pattern.join(';')).to.equal('banana;banana;banana;apple;apple;apple;pear'); | ||
}); | ||
describe('MiniSignal#once', () => { | ||
let e; | ||
let context; | ||
beforeEach(() => { | ||
e = new mini_signals_1.MiniSignal(); | ||
context = { bar: 'baz' }; | ||
describe('Readme Examples', () => { | ||
it('Example Usage', () => { | ||
const mySignal = new mini_signals_1.MiniSignal(); | ||
const binding = mySignal.add(onSignal); // add listener | ||
mySignal.dispatch('foo', 'bar'); // dispatch signal passing custom parameters | ||
mySignal.detach(binding); // remove a single listener | ||
function onSignal(foo, bar) { | ||
(0, chai_1.expect)(foo).to.equal('foo'); | ||
(0, chai_1.expect)(bar).to.equal('bar'); | ||
} | ||
}); | ||
it('should throw error for incorrect types', () => { | ||
(0, chai_1.expect)(() => { | ||
// @ts-expect-error testing error | ||
e.once(); | ||
}).throws('MiniSignal#once(): First arg must be a Function.'); | ||
(0, chai_1.expect)(() => { | ||
// @ts-expect-error testing error | ||
e.once(123); | ||
}).throws('MiniSignal#once(): First arg must be a Function.'); | ||
(0, chai_1.expect)(() => { | ||
// @ts-expect-error testing error | ||
e.once(true); | ||
}).throws('MiniSignal#once(): First arg must be a Function.'); | ||
(0, chai_1.expect)(e.handlers().length).equals(0); | ||
}); | ||
it('should not invoke twice', () => { | ||
it('Function#bind example', () => { | ||
const mySignal = new mini_signals_1.MiniSignal(); | ||
const context = {}; | ||
const cb = function (bar) { | ||
(0, chai_1.expect)(arguments).has.length(1); | ||
(0, chai_1.expect)(bar).equals('bar'); | ||
(0, chai_1.expect)(this).equals(context); | ||
(0, chai_1.expect)(arguments).has.length(1); | ||
e.dispatch('bar'); | ||
}.bind(context); | ||
e.once(cb); | ||
e.dispatch('bar'); | ||
mySignal.add(cb); | ||
mySignal.dispatch('bar'); | ||
}); | ||
it('Function#bind example with parameters', () => { | ||
const mySignal = new mini_signals_1.MiniSignal(); | ||
const context = {}; | ||
const cb = function (bar) { | ||
(0, chai_1.expect)(arguments).has.length(1); | ||
(0, chai_1.expect)(bar).equals('bar'); | ||
(0, chai_1.expect)(this).equals(context); | ||
}.bind(context, 'bar'); | ||
mySignal.add(cb); | ||
mySignal.dispatch(); | ||
}); | ||
}); | ||
@@ -99,16 +89,22 @@ describe('MiniSignal#add', () => { | ||
}).throws('MiniSignal#add(): First arg must be a Function.'); | ||
(0, chai_1.expect)(e.handlers().length).equals(0); | ||
(0, chai_1.expect)(!e.hasListeners); | ||
}); | ||
// Note: once is deprecated | ||
// These tests use the add method instead | ||
it('should not invoke twice', () => { | ||
const l = e.add(function (arg) { | ||
writer(arg); | ||
(0, chai_1.expect)(arg).equals('foo'); | ||
e.detach(l); | ||
}); | ||
e.dispatch('foo'); | ||
e.dispatch('bar'); | ||
e.dispatch('baz'); | ||
(0, chai_1.expect)(pattern.join(';')).equals('foo'); | ||
}); | ||
}); | ||
describe('MiniSignal#dispatch', () => { | ||
function writer() { | ||
pattern += String(this); | ||
} | ||
let e; | ||
let context = { bar: 'baz' }; | ||
let pattern = ''; | ||
beforeEach(() => { | ||
e = new mini_signals_1.MiniSignal(); | ||
context = { bar: 'baz' }; | ||
pattern = ''; | ||
}); | ||
@@ -128,10 +124,2 @@ it('should return false when there are not events to dispatch', () => { | ||
}); | ||
it('emits with context when context is specified', () => { | ||
e.add(function (bar) { | ||
(0, chai_1.expect)(bar).equals('bar'); | ||
(0, chai_1.expect)(this).equals(context); | ||
(0, chai_1.expect)(arguments).has.length(1); | ||
}, context); | ||
e.dispatch('bar'); | ||
}); | ||
it('can dispatch the function with multiple arguments', () => { | ||
@@ -188,21 +176,2 @@ for (let i = 0; i < 100; i++) { | ||
}); | ||
it('emits with context, multiple listeners (force loop)', () => { | ||
e.add(function (bar) { | ||
(0, chai_1.expect)(this).deep.equals({ foo: 'bar' }); | ||
(0, chai_1.expect)(bar).equals('bar'); | ||
}, { foo: 'bar' }); | ||
e.add(function (bar) { | ||
(0, chai_1.expect)(this).deep.equals({ bar: 'baz' }); | ||
(0, chai_1.expect)(bar).equals('bar'); | ||
}, { bar: 'baz' }); | ||
e.dispatch('bar'); | ||
}); | ||
it('emits with different contexts', () => { | ||
e.add(writer, 'foo'); | ||
e.add(writer, 'baz'); | ||
e.add(writer, 'bar'); | ||
e.add(writer, 'banana'); | ||
e.dispatch(); | ||
(0, chai_1.expect)(pattern).equals('foobazbarbanana'); | ||
}); | ||
it('should return true when there are events to dispatch', function (done) { | ||
@@ -219,3 +188,3 @@ e.add(() => { | ||
const e = new mini_signals_1.MiniSignal(); | ||
e.add(function (a, b, c, d, undef) { | ||
e.add(function (a, b, c, undef) { | ||
(0, chai_1.expect)(a).equals('foo'); | ||
@@ -244,3 +213,2 @@ (0, chai_1.expect)(b).equals(e); | ||
const e = new mini_signals_1.MiniSignal(); | ||
const pattern = []; | ||
function foo1() { | ||
@@ -263,15 +231,12 @@ pattern.push('foo1'); | ||
const e = new mini_signals_1.MiniSignal(); | ||
const pattern = []; | ||
function foo1() { | ||
e.add(() => { | ||
pattern.push('foo1'); | ||
} | ||
function foo2() { | ||
}); | ||
const l = e.add(() => { | ||
pattern.push('foo2'); | ||
} | ||
function foo3() { | ||
e.detach(l); | ||
}); | ||
e.add(() => { | ||
pattern.push('foo3'); | ||
} | ||
e.add(foo1); | ||
e.once(foo2); | ||
e.add(foo3); | ||
}); | ||
e.dispatch(); | ||
@@ -281,44 +246,13 @@ e.dispatch(); | ||
}); | ||
}); | ||
describe('MiniSignal#handlers', () => { | ||
/* istanbul ignore next */ | ||
function foo() { } | ||
it('returns an empty array if no handlers are added', () => { | ||
const e = new mini_signals_1.MiniSignal(); | ||
(0, chai_1.expect)(e.handlers()).is.a('array'); | ||
(0, chai_1.expect)(e.handlers().length).equals(0); | ||
it('cannot dispatch while dispatching', () => { | ||
const cb = function (bar) { | ||
(0, chai_1.expect)(bar).equals('bar'); | ||
(0, chai_1.expect)(arguments).has.length(1); | ||
e.dispatch('bar'); | ||
}; | ||
e.add(cb); | ||
(0, chai_1.expect)(() => { | ||
e.dispatch('bar'); | ||
}).throws('MiniSignal#dispatch(): Signal already dispatching.'); | ||
}); | ||
it('returns an array of MiniSignalBinding', () => { | ||
const e = new mini_signals_1.MiniSignal(); | ||
e.add(foo); | ||
e.add(foo); | ||
(0, chai_1.expect)(e.handlers()).is.a('array'); | ||
(0, chai_1.expect)(e.handlers().length).equals(2); | ||
e.handlers().forEach(function (h) { | ||
(0, chai_1.expect)(h).instanceOf(mini_signals_binding_1.MiniSignalBinding); | ||
}); | ||
}); | ||
it('is not vulnerable to modifications', () => { | ||
const e = new mini_signals_1.MiniSignal(); | ||
e.add(foo); | ||
e.add(foo); | ||
(0, chai_1.expect)(e.handlers().length).equals(2); | ||
e.handlers().length = 0; | ||
(0, chai_1.expect)(e.handlers().length).equals(2); | ||
e.handlers().forEach(function (h) { | ||
(0, chai_1.expect)(h).instanceOf(mini_signals_binding_1.MiniSignalBinding); | ||
}); | ||
}); | ||
it('can return a boolean as indication if handlers exist', () => { | ||
const e = new mini_signals_1.MiniSignal(); | ||
e.add(foo); | ||
e.add(foo); | ||
e.add(foo); | ||
e.add(foo); | ||
e.add(foo); | ||
e.add(foo); | ||
(0, chai_1.expect)(e.hasHandlers()).equals(true); | ||
e.detachAll(); | ||
(0, chai_1.expect)(e.hasHandlers()).equals(false); | ||
}); | ||
}); | ||
@@ -352,11 +286,11 @@ describe('MiniSignal#detach', () => { | ||
e.detach(); | ||
}).throws('MiniSignal#detach(): First arg must be a MiniSignalBinding object.'); | ||
}).throws('MiniSignal#detach(): First arg must be a MiniSignalNode object.'); | ||
(0, chai_1.expect)(() => { | ||
// @ts-expect-error testing error | ||
e.detach(1); | ||
}).throws('MiniSignal#detach(): First arg must be a MiniSignalBinding object.'); | ||
}).throws('MiniSignal#detach(): First arg must be a MiniSignalNode object.'); | ||
(0, chai_1.expect)(() => { | ||
// @ts-expect-error testing error | ||
e.detach(bar); | ||
}).throws('MiniSignal#detach(): First arg must be a MiniSignalBinding object.'); | ||
}).throws('MiniSignal#detach(): First arg must be a MiniSignalNode object.'); | ||
}); | ||
@@ -367,7 +301,7 @@ it('should only remove the event with the specified node', () => { | ||
const _bar = e.add(bar); | ||
(0, chai_1.expect)(e.handlers().length).equals(3); | ||
(0, chai_1.expect)(e.hasListeners()).equals(true); | ||
e.dispatch(); | ||
(0, chai_1.expect)(pattern.join(';')).equals('a;b;bar'); | ||
e.detach(_bar); | ||
(0, chai_1.expect)(e.handlers().length).equals(2); | ||
(0, chai_1.expect)(e.hasListeners()).equals(true); | ||
e.dispatch(); | ||
@@ -380,7 +314,7 @@ (0, chai_1.expect)(pattern.join(';')).equals('a;b;bar;a;b'); | ||
e.add(b); | ||
(0, chai_1.expect)(e.handlers().length).equals(3); | ||
(0, chai_1.expect)(e.hasListeners()).equals(true); | ||
e.dispatch(); | ||
(0, chai_1.expect)(pattern.join(';')).equals('bar;a;b'); | ||
e.detach(_bar); | ||
(0, chai_1.expect)(e.handlers().length).equals(2); | ||
(0, chai_1.expect)(e.hasListeners()).equals(true); | ||
e.dispatch(); | ||
@@ -393,7 +327,7 @@ (0, chai_1.expect)(pattern.join(';')).equals('bar;a;b;a;b'); | ||
e.add(b); | ||
(0, chai_1.expect)(e.handlers().length).equals(3); | ||
(0, chai_1.expect)(e.hasListeners()).equals(true); | ||
e.dispatch(); | ||
(0, chai_1.expect)(pattern.join(';')).equals('a;bar;b'); | ||
e.detach(_bar); | ||
(0, chai_1.expect)(e.handlers().length).equals(2); | ||
(0, chai_1.expect)(e.hasListeners()).equals(true); | ||
e.dispatch(); | ||
@@ -462,10 +396,7 @@ (0, chai_1.expect)(pattern.join(';')).equals('a;bar;b;a;b'); | ||
e2.detach(binding); | ||
(0, chai_1.expect)(binding._owner === e); | ||
(0, chai_1.expect)(e.hasHandlers()); | ||
(0, chai_1.expect)(e.hasListeners()); | ||
}); | ||
it('can be called multiple times', () => { | ||
const binding = e.add(foo); | ||
(0, chai_1.expect)(binding._owner === e); | ||
e.detach(binding); | ||
(0, chai_1.expect)(binding._owner === null); | ||
e.detach(binding); | ||
@@ -489,5 +420,5 @@ e.detach(binding); | ||
e.add(oops); | ||
(0, chai_1.expect)(e.handlers().length).equals(4); | ||
(0, chai_1.expect)(e.hasListeners()).equals(true); | ||
(0, chai_1.expect)(e.detachAll()).equals(e); | ||
(0, chai_1.expect)(e.handlers().length).equals(0); | ||
(0, chai_1.expect)(e.hasListeners()).equals(false); | ||
(0, chai_1.expect)(e.dispatch()).equals(false); | ||
@@ -497,3 +428,3 @@ }); | ||
(0, chai_1.expect)(e.detachAll()).equals(e); | ||
(0, chai_1.expect)(e.handlers().length).equals(0); | ||
(0, chai_1.expect)(e.hasListeners()).equals(false); | ||
(0, chai_1.expect)(e.dispatch()).equals(false); | ||
@@ -507,85 +438,31 @@ }); | ||
}); | ||
describe('MiniSignal#has', () => { | ||
/* istanbul ignore next */ | ||
function oops() { | ||
throw new Error('oops'); | ||
} | ||
let e; | ||
beforeEach(() => { | ||
e = new mini_signals_1.MiniSignal(); | ||
}); | ||
it('has returns true if bound', () => { | ||
const binding = e.add(oops); | ||
(0, node_assert_1.strict)(e.has(binding)); | ||
}); | ||
it('has returns false if bound to another signal', () => { | ||
const e2 = new mini_signals_1.MiniSignal(); | ||
const binding = e2.add(oops); | ||
(0, node_assert_1.strict)(e.has(binding) === false); | ||
}); | ||
it('has returns false if detached', () => { | ||
const binding = e.add(oops); | ||
(0, node_assert_1.strict)(e.has(binding)); | ||
binding.detach(); | ||
(0, node_assert_1.strict)(e.has(binding) === false); | ||
}); | ||
it('has returns false after detachAll', () => { | ||
const binding = e.add(oops); | ||
(0, node_assert_1.strict)(e.has(binding)); | ||
e.detachAll(); | ||
(0, node_assert_1.strict)(e.has(binding) === false); | ||
}); | ||
it('should throw error for incorrect types', () => { | ||
(0, chai_1.expect)(() => { | ||
e.has({}); | ||
}).throws('MiniSignal#has(): First arg must be a MiniSignalBinding object.'); | ||
}); | ||
describe('Garbage Collection', () => { | ||
it('should not leak memory', () => __awaiter(void 0, void 0, void 0, function* () { | ||
const e = new mini_signals_1.MiniSignal(); | ||
const w = e.add(() => { | ||
/* */ | ||
}); | ||
(0, chai_1.expect)(w.deref()).to.exist; | ||
e.dispatch(); | ||
(0, chai_1.expect)(w.deref()).to.exist; | ||
e.detach(w); | ||
yield new Promise(resolve => setTimeout(resolve, 0)); | ||
global.gc(); | ||
(0, chai_1.expect)(w.deref()).to.be.undefined; | ||
// should not throw an error when detaching gc ref | ||
e.detach(w); | ||
})); | ||
it('can clean up after itself when using add', () => __awaiter(void 0, void 0, void 0, function* () { | ||
const e = new mini_signals_1.MiniSignal(); | ||
const w = e.add(() => { | ||
e.detach(w); | ||
}); | ||
(0, chai_1.expect)(w.deref()).to.exist; | ||
e.dispatch(); | ||
(0, chai_1.expect)(w.deref()).to.exist; | ||
yield new Promise(resolve => setTimeout(resolve, 0)); | ||
global.gc(); | ||
(0, chai_1.expect)(w.deref()).to.be.undefined; | ||
})); | ||
}); | ||
describe('Readme Examples', () => { | ||
it('Example Usage', () => { | ||
const mySignal = new mini_signals_1.MiniSignal(); | ||
const binding = mySignal.add(onSignal); // add listener | ||
mySignal.dispatch('foo', 'bar'); // dispatch signal passing custom parameters | ||
binding.detach(); // remove a single listener | ||
function onSignal(foo, bar) { | ||
(0, node_assert_1.strict)(foo === 'foo'); | ||
(0, node_assert_1.strict)(bar === 'bar'); | ||
} | ||
}); | ||
it('Another Example', () => { | ||
const myObject = { | ||
foo: 'bar', | ||
updated: new mini_signals_1.MiniSignal(), | ||
}; | ||
myObject.updated.add(onUpdated, myObject); // add listener with context | ||
myObject.foo = 'baz'; | ||
myObject.updated.dispatch(); // dispatch signal | ||
function onUpdated() { | ||
(0, node_assert_1.strict)(this === myObject); | ||
(0, node_assert_1.strict)(this.foo === 'baz'); | ||
} | ||
}); | ||
it('Function#bind example', () => { | ||
const mySignal = new mini_signals_1.MiniSignal(); | ||
const context = {}; | ||
const cb = function (bar) { | ||
(0, chai_1.expect)(arguments).has.length(1); | ||
(0, chai_1.expect)(bar).equals('bar'); | ||
(0, chai_1.expect)(this).equals(context); | ||
}.bind(context); | ||
mySignal.add(cb); | ||
mySignal.dispatch('bar'); | ||
}); | ||
it('Function#bind example with parameters', () => { | ||
const mySignal = new mini_signals_1.MiniSignal(); | ||
const context = {}; | ||
const cb = function (bar) { | ||
(0, chai_1.expect)(arguments).has.length(1); | ||
(0, chai_1.expect)(bar).equals('bar'); | ||
(0, chai_1.expect)(this).equals(context); | ||
}.bind(context, 'bar'); | ||
mySignal.add(cb); | ||
mySignal.dispatch(); | ||
}); | ||
}); | ||
}); |
{ | ||
"name": "mini-signals", | ||
"version": "2.0.0-0", | ||
"version": "2.0.0-1", | ||
"description": "signals, in TypeScript, fast", | ||
@@ -8,12 +8,14 @@ "main": "dist/index.js", | ||
"files": [ | ||
"/dist" | ||
"/dist", | ||
"/docs" | ||
], | ||
"scripts": { | ||
"test": "npm-run-all test:*", | ||
"test:unit": "mocha --reporter spec --compilers ts:ts-node/register src/**/*.spec.ts", | ||
"test:unit": "mocha --reporter spec --require ts-node/register src/**/*.spec.ts -n expose-gc", | ||
"test:deopt": "node --trace_opt --trace_deopt ./bench/test-deopt.js | grep \"disabled optimization\" || true", | ||
"test:types": "tsd -f ./src/mini-signals.test-d.ts", | ||
"build": "npm-run-all build:*", | ||
"build:tsc": "tsc", | ||
"xxx-build:jsdoc2md": "jsdoc-parse ./src/mini-signals.ts | dmd > API.md", | ||
"coverage": "istanbul cover -- ./node_modules/mocha/bin/_mocha --compilers js:babel/register ./test/mini-signals-*.js", | ||
"xxx-build:doc": "typedoc --plugin typedoc-plugin-markdown --out docs src/index.ts", | ||
"coverage": "nyc --reporter=html --reporter=text --timeout=3000 npm run test:unit", | ||
"bench": "ts-node ./bench/index.js | tee ./bench/latest.md", | ||
@@ -52,3 +54,2 @@ "bench:emit": "ts-node ./bench/emit.js", | ||
"chg": "^0.4.0", | ||
"dmd": "^1.2.0", | ||
"eslint": "^8.36.0", | ||
@@ -60,13 +61,16 @@ "eslint-config-prettier": "^8.8.0", | ||
"eslint-plugin-promise": "^6.1.1", | ||
"eventemitter3": "^1.1.1", | ||
"istanbul": "^0.3.19", | ||
"jsdoc-parse": "^1.1.0", | ||
"mocha": "^2.2.5", | ||
"np": "^2.20.1", | ||
"eventemitter3": "^5.0.0", | ||
"mocha": "^10.2.0", | ||
"np": "^7.6.4", | ||
"npm-check": "^6.0.1", | ||
"npm-run-all": "^4.1.5", | ||
"nyc": "^15.1.0", | ||
"prettier": "2.8.7", | ||
"signals": "^1.0.0", | ||
"ts-node": "^10.9.1", | ||
"tsd": "^0.28.0", | ||
"typedoc": "^0.23.28", | ||
"typedoc-plugin-markdown": "^3.14.0", | ||
"typescript": "^5.0.2" | ||
} | ||
} |
@@ -15,2 +15,18 @@ # mini-signals | ||
## mini-signals 2.0.0 | ||
MiniSignals v2.0.0 has been rewritten in TypeScript and had it's API changed to improve performance and add type safety. | ||
New features: | ||
- `.add` now returns a weak node reference which can be used to remove the listener directly from the signal. Reduces memory leaks. | ||
- `.add` is now type safe. The type of the listener is checked against the type variable in the constructor. | ||
Breaking changes: | ||
- `.add` now returns a node reference instead of a object, which had a `detach` method. The node reference can be used to remove the listener directly from the signal. | ||
- `.once` has been removed. Use `.add` instead with a call to `.detach` in the listener. | ||
- The `thisArg` parameter has been removed from `.add`. Use `.add` with a call to `.bind` or use an arrow function with a closure. | ||
- `.dispatch` now throws an error if the signal is already dispatching. | ||
## Install | ||
@@ -27,15 +43,14 @@ | ||
```ts | ||
import { MiniSignal } from "mini-signals"; | ||
import { MiniSignal } from 'mini-signals'; | ||
const mySignal = new MiniSignal<[string, string]>(); | ||
const mySignal = new MiniSignal<[string, string]>(); // the type variable is optional and defines the parameters to be dispatched | ||
const binding = mySignal.add(onSignal); //add listener | ||
const binding = mySignal.add((foo: string, bar: string) => { // add listener, note the parameter types match the type variable in the constructor | ||
console.log('signal dispatched'); | ||
assert(foo === 'foo'); | ||
assert(bar === 'bar'); | ||
}); | ||
mySignal.dispatch("foo", "bar"); // dispatch signal passing custom parameters | ||
mySignal.dispatch('foo', 'bar'); // dispatch signal passing custom parameters | ||
binding.detach(); // remove a single listener | ||
function onSignal(foo: string, bar: string) { | ||
assert(foo === "foo"); | ||
assert(bar === "bar"); | ||
} | ||
``` | ||
@@ -48,14 +63,13 @@ | ||
foo: "bar", | ||
updated: new MiniSignal<never>(), | ||
updated: new MiniSignal<never, typeof myObject>() // in this case the type variable is never, since we are not passing any parameters | ||
}; | ||
myObject.updated.add(onUpdated, myObject); //add listener with context | ||
myObject.updated.add(() => { | ||
console.log('signal dispatched'); | ||
assert(this === myObject); | ||
assert(this.foo === 'baz'); | ||
}, myObject); // add listener with context | ||
myObject.foo = "baz"; | ||
myObject.updated.dispatch(); //dispatch signal | ||
function onUpdated() { | ||
assert(this === myObject); | ||
assert(this.foo === "baz"); | ||
} | ||
myObject.foo = 'baz'; | ||
myObject.updated.dispatch(); // dispatch signal | ||
``` | ||
@@ -62,0 +76,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
47103
21
83
0
25
895