mini-signals
Advanced tools
Comparing version 2.0.0-1 to 2.0.0-2
type CallBack<T extends any[]> = (...x: T) => void; | ||
declare const MiniSignalSymbol: unique symbol; | ||
declare const MINI_SIGNAL_KEY: unique symbol; | ||
type MiniSignalNodeRef<T, S> = { | ||
[MINI_SIGNAL_KEY]: Symbol; | ||
} & { | ||
__brand: S; | ||
} & { | ||
__type: T; | ||
}; | ||
type MiniSignalNode<T extends any[]> = { | ||
@@ -7,12 +14,13 @@ fn: CallBack<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; | ||
}> { | ||
export declare class MiniSignal<T extends any[] = any[], S extends any = Symbol | string> { | ||
/** | ||
* A Symbol that is used to guarantee the uniqueness of the MiniSignal | ||
* instance. | ||
*/ | ||
private readonly _symbol; | ||
private _refMap; | ||
private _head?; | ||
private _tail?; | ||
private readonly symbol; | ||
private dispatching; | ||
private _dispatching; | ||
hasListeners(): boolean; | ||
@@ -26,7 +34,7 @@ /** | ||
*/ | ||
add(fn: CallBack<T>): MiniSignalRef<T, S>; | ||
add(fn: CallBack<T>): MiniSignalNodeRef<T, S>; | ||
/** | ||
* Remove binding object. | ||
*/ | ||
detach(ref: MiniSignalRef<T, S>): this; | ||
detach(sym: MiniSignalNodeRef<T, S>): this; | ||
/** | ||
@@ -39,3 +47,5 @@ * Detach all listeners. | ||
private _addNode; | ||
private _createRef; | ||
protected _getRef(sym: MiniSignalNodeRef<T, S>): MiniSignalNode<T> | undefined; | ||
} | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.MiniSignal = void 0; | ||
const MiniSignalSymbol = Symbol('MiniSignalSymbol'); | ||
const MINI_SIGNAL_KEY = Symbol('SIGNAL'); | ||
function isMiniSignalNodeRef(obj) { | ||
return typeof obj === 'object' && MINI_SIGNAL_KEY in obj; | ||
} | ||
class MiniSignal { | ||
constructor() { | ||
/** | ||
* A Symbol that is used to guarantee the uniqueness of the MiniSignal | ||
* instance. | ||
*/ | ||
this._symbol = Symbol('MiniSignal'); | ||
this._refMap = new WeakMap(); | ||
this._head = undefined; | ||
this._tail = undefined; | ||
this.symbol = Symbol('MiniSignal'); | ||
this.dispatching = false; | ||
this._dispatching = false; | ||
} | ||
hasListeners() { | ||
return !(this._head == null); | ||
return this._head != null; | ||
} | ||
@@ -19,3 +27,3 @@ /** | ||
dispatch(...args) { | ||
if (this.dispatching) { | ||
if (this._dispatching) { | ||
throw new Error('MiniSignal#dispatch(): Signal already dispatching.'); | ||
@@ -26,3 +34,3 @@ } | ||
return false; | ||
this.dispatching = true; | ||
this._dispatching = true; | ||
while (node != null) { | ||
@@ -32,3 +40,3 @@ node.fn(...args); | ||
} | ||
this.dispatching = false; | ||
this._dispatching = false; | ||
return true; | ||
@@ -43,6 +51,3 @@ } | ||
} | ||
return this._addNode({ | ||
fn, | ||
[MiniSignalSymbol]: this.symbol | ||
}); | ||
return this._createRef(this._addNode({ fn })); | ||
} | ||
@@ -52,11 +57,13 @@ /** | ||
*/ | ||
detach(ref) { | ||
if (!(ref instanceof WeakRef)) { | ||
throw new Error('MiniSignal#detach(): First arg must be a MiniSignalNode object.'); | ||
detach(sym) { | ||
if (!isMiniSignalNodeRef(sym)) { | ||
throw new Error('MiniSignal#detach(): First arg must be a MiniSignal listener reference.'); | ||
} | ||
const node = ref.deref(); | ||
if (!node || !node[MiniSignalSymbol]) | ||
return this; | ||
if (node[MiniSignalSymbol] !== this.symbol) | ||
return this; // Error? | ||
if (sym[MINI_SIGNAL_KEY] !== this._symbol) { | ||
throw new Error('MiniSignal#detach(): MiniSignal listener does not belong to this MiniSignal.'); | ||
} | ||
const node = this._refMap.get(sym); | ||
if (!node) | ||
return this; // already detached | ||
this._refMap.delete(sym); | ||
this._disconnectNode(node); | ||
@@ -74,2 +81,3 @@ this._destroyNode(node); | ||
this._head = this._tail = undefined; | ||
this._refMap = new WeakMap(); | ||
while (n != null) { | ||
@@ -84,3 +92,2 @@ this._destroyNode(n); | ||
node.prev = undefined; | ||
node[MiniSignalSymbol] = undefined; | ||
} | ||
@@ -108,3 +115,2 @@ _disconnectNode(node) { | ||
} | ||
node[MiniSignalSymbol] = undefined; | ||
} | ||
@@ -122,5 +128,13 @@ _addNode(node) { | ||
} | ||
return new WeakRef(node); | ||
return node; | ||
} | ||
_createRef(node) { | ||
const sym = { [MINI_SIGNAL_KEY]: this._symbol }; | ||
this._refMap.set(sym, node); | ||
return sym; | ||
} | ||
_getRef(sym) { | ||
return this._refMap.get(sym); | ||
} | ||
} | ||
exports.MiniSignal = MiniSignal; |
@@ -279,11 +279,11 @@ "use strict"; | ||
e.detach(); | ||
}).throws('MiniSignal#detach(): First arg must be a MiniSignalNode object.'); | ||
}).throws('MiniSignal#detach(): First arg must be a MiniSignal listener reference.'); | ||
(0, chai_1.expect)(() => { | ||
// @ts-expect-error testing error | ||
e.detach(1); | ||
}).throws('MiniSignal#detach(): First arg must be a MiniSignalNode object.'); | ||
}).throws('MiniSignal#detach(): First arg must be a MiniSignal listener reference.'); | ||
(0, chai_1.expect)(() => { | ||
// @ts-expect-error testing error | ||
e.detach(bar); | ||
}).throws('MiniSignal#detach(): First arg must be a MiniSignalNode object.'); | ||
}).throws('MiniSignal#detach(): First arg must be a MiniSignal listener reference.'); | ||
}); | ||
@@ -385,3 +385,5 @@ it('should only remove the event with the specified node', () => { | ||
const binding = e.add(foo); | ||
e2.detach(binding); | ||
(0, chai_1.expect)(() => { | ||
e2.detach(binding); | ||
}).throws('MiniSignal#detach(): MiniSignal listener does not belong to this MiniSignal.'); | ||
(0, chai_1.expect)(e.hasListeners()); | ||
@@ -427,30 +429,115 @@ }); | ||
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(() => { | ||
/* */ | ||
it('should clean up when signal is destroyed', () => __awaiter(void 0, void 0, void 0, function* () { | ||
let e = new mini_signals_1.MiniSignal(); | ||
const eR = new WeakRef(e); | ||
let fn = () => { | ||
noop(e, w); | ||
}; | ||
const fR = new WeakRef(fn); | ||
const w = e.add(fn); | ||
e.add(fn); | ||
e.add(noop); | ||
e.add(() => { | ||
fn(); | ||
noop(); | ||
}); | ||
(0, chai_1.expect)(w.deref()).to.exist; | ||
e.add(() => { | ||
fn(); | ||
noop(); | ||
e.detach(w); | ||
}); | ||
(0, chai_1.expect)(fR.deref()).to.exist; | ||
e.dispatch(); | ||
(0, chai_1.expect)(w.deref()).to.exist; | ||
// Removing references in this scope should mark nodes GC | ||
fn = null; | ||
e = null; | ||
yield new Promise(resolve => setTimeout(resolve, 0)); | ||
global.gc(); | ||
(0, chai_1.expect)(fR.deref()).to.be.undefined; | ||
(0, chai_1.expect)(eR.deref()).to.be.undefined; | ||
// Only the node reference should be left | ||
(0, chai_1.expect)(w).to.exist; | ||
})); | ||
it('should not leak memory after detach', () => __awaiter(void 0, void 0, void 0, function* () { | ||
let e = new mini_signals_1.MiniSignal(); | ||
const eR = new WeakRef(e); | ||
let fn = () => { | ||
noop(e, fn); | ||
}; | ||
const fR = new WeakRef(fn); | ||
const w = e.add(fn); | ||
fn = null; | ||
(0, chai_1.expect)(fR.deref()).to.exist; | ||
e.dispatch(); | ||
(0, chai_1.expect)(fR.deref()).to.exist; | ||
e.detach(w); | ||
yield new Promise(resolve => setTimeout(resolve, 0)); | ||
global.gc(); | ||
(0, chai_1.expect)(w.deref()).to.be.undefined; | ||
(0, chai_1.expect)(fR.deref()).to.be.undefined; | ||
// should not throw an error when detaching gc ref | ||
e.detach(w); | ||
(0, chai_1.expect)(eR.deref()).to.exist; | ||
// Also cleans up the signal | ||
e = null; | ||
yield new Promise(resolve => setTimeout(resolve, 0)); | ||
global.gc(); | ||
(0, chai_1.expect)(eR.deref()).to.be.undefined; | ||
// Only the node reference should be left | ||
(0, chai_1.expect)(w).to.exist; | ||
})); | ||
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(() => { | ||
it('should not leak memory after detach all', () => __awaiter(void 0, void 0, void 0, function* () { | ||
let e = new mini_signals_1.MiniSignal(); | ||
const eR = new WeakRef(e); | ||
let fn = () => { | ||
noop(e, fn); | ||
}; | ||
const fR = new WeakRef(fn); | ||
const w = e.add(fn); | ||
fn = null; | ||
(0, chai_1.expect)(fR.deref()).to.exist; | ||
e.dispatch(); | ||
(0, chai_1.expect)(fR.deref()).to.exist; | ||
e.detach(w); | ||
yield new Promise(resolve => setTimeout(resolve, 0)); | ||
global.gc(); | ||
(0, chai_1.expect)(fR.deref()).to.be.undefined; | ||
// should not throw an error when detaching gc ref | ||
e.detachAll(); | ||
(0, chai_1.expect)(eR.deref()).to.exist; | ||
// Also cleans up the signal | ||
e = null; | ||
yield new Promise(resolve => setTimeout(resolve, 0)); | ||
global.gc(); | ||
(0, chai_1.expect)(eR.deref()).to.be.undefined; | ||
// Only the node reference should be left | ||
(0, chai_1.expect)(w).to.exist; | ||
})); | ||
it('should clean up after itself when using add', () => __awaiter(void 0, void 0, void 0, function* () { | ||
let e = new mini_signals_1.MiniSignal(); | ||
const eR = new WeakRef(e); | ||
let w; | ||
let fn = () => { | ||
noop(e, w); | ||
e.detach(w); | ||
}); | ||
(0, chai_1.expect)(w.deref()).to.exist; | ||
}; | ||
const fR = new WeakRef(fn); | ||
w = e.add(fn); | ||
fn = null; | ||
(0, chai_1.expect)(fR.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; | ||
(0, chai_1.expect)(fR.deref()).to.be.undefined; | ||
// Also cleans up the signal | ||
e = null; | ||
yield new Promise(resolve => setTimeout(resolve, 0)); | ||
global.gc(); | ||
(0, chai_1.expect)(eR.deref()).to.be.undefined; | ||
// Only the node reference should be left | ||
(0, chai_1.expect)(w).to.exist; | ||
})); | ||
}); | ||
}); | ||
function noop(...args) { | ||
// empty | ||
} |
@@ -93,3 +93,3 @@ [mini-signals](../README.md) / [Exports](../modules.md) / MiniSignal | ||
▸ `Private` **_addNode**(`node`): `MiniSignalRef`<`T`, `S`\> | ||
▸ `Private` **_addNode**(`node`): `MiniSignalNodeRef`<`T`, `S`\> | ||
@@ -104,3 +104,3 @@ #### Parameters | ||
`MiniSignalRef`<`T`, `S`\> | ||
`MiniSignalNodeRef`<`T`, `S`\> | ||
@@ -155,3 +155,3 @@ #### Defined in | ||
▸ **add**(`fn`): `MiniSignalRef`<`T`, `S`\> | ||
▸ **add**(`fn`): `MiniSignalNodeRef`<`T`, `S`\> | ||
@@ -168,3 +168,3 @@ Register a new listener. | ||
`MiniSignalRef`<`T`, `S`\> | ||
`MiniSignalNodeRef`<`T`, `S`\> | ||
@@ -187,3 +187,3 @@ #### Defined in | ||
| :------ | :------ | | ||
| `ref` | `MiniSignalRef`<`T`, `S`\> | | ||
| `ref` | `MiniSignalNodeRef`<`T`, `S`\> | | ||
@@ -190,0 +190,0 @@ #### Returns |
{ | ||
"name": "mini-signals", | ||
"version": "2.0.0-1", | ||
"version": "2.0.0-2", | ||
"description": "signals, in TypeScript, fast", | ||
@@ -49,15 +49,15 @@ "main": "dist/index.js", | ||
"@types/mocha": "^10.0.1", | ||
"@typescript-eslint/eslint-plugin": "^5.56.0", | ||
"@typescript-eslint/eslint-plugin": "^5.57.0", | ||
"benchmark": "^1.0.0", | ||
"chai": "^4.3.7", | ||
"chg": "^0.4.0", | ||
"eslint": "^8.36.0", | ||
"eslint": "^8.37.0", | ||
"eslint-config-prettier": "^8.8.0", | ||
"eslint-config-standard-with-typescript": "^34.0.1", | ||
"eslint-plugin-import": "^2.27.5", | ||
"eslint-plugin-n": "^15.6.1", | ||
"eslint-plugin-n": "^15.7.0", | ||
"eslint-plugin-promise": "^6.1.1", | ||
"eventemitter3": "^5.0.0", | ||
"mocha": "^10.2.0", | ||
"np": "^7.6.4", | ||
"np": "^7.7.0", | ||
"npm-check": "^6.0.1", | ||
@@ -69,3 +69,3 @@ "npm-run-all": "^4.1.5", | ||
"ts-node": "^10.9.1", | ||
"tsd": "^0.28.0", | ||
"tsd": "^0.28.1", | ||
"typedoc": "^0.23.28", | ||
@@ -72,0 +72,0 @@ "typedoc-plugin-markdown": "^3.14.0", |
@@ -13,3 +13,3 @@ # mini-signals | ||
> Note: Signals here are the type defined by [js-signals](https://github.com/millermedeiros/js-signals) inspired by AS3-Signals. They should not to be confused with [SolidJS](https://www.solidjs.com/tutorial/introduction_signals) or Angular signals. | ||
> Note: Signals here are the type defined by Miller Medeiros in [js-signals](https://github.com/millermedeiros/js-signals) inspired by AS3-Signals. They should not to be confused with [SolidJS](https://www.solidjs.com/tutorial/introduction_signals) or [Angular signals](https://github.com/angular/angular/discussions/49090). | ||
@@ -23,9 +23,9 @@ ## mini-signals 2.0.0 | ||
- `.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. | ||
- `.add` is now type safe. The type of the listener is checked against the type variable in the constructor as well as an optional "flavor". | ||
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. | ||
- `.add` now returns a node reference instead of a object. The returned node cannot be removed directly; it must be from the signal using `MiniSignal#detach`. | ||
- `.once` has been removed. Use `.add` instead with a call to `.detach` in the callback. | ||
- The `thisArg` parameter has been removed from `.add`. Use `.add` with a call to `.bind` or (preferred) use an arrow function with a closure. | ||
- `.dispatch` now throws an error if the signal is already dispatching. | ||
@@ -63,3 +63,3 @@ | ||
foo: "bar", | ||
updated: new MiniSignal<never, typeof myObject>() // in this case the type variable is never, since we are not passing any parameters | ||
updated: new MiniSignal<never>() // in this case the type variable is never, since we are not passing any parameters | ||
}; | ||
@@ -69,5 +69,4 @@ | ||
console.log('signal dispatched'); | ||
assert(this === myObject); | ||
assert(this.foo === 'baz'); | ||
}, myObject); // add listener with context | ||
assert(myObject.foo === 'baz'); | ||
}); | ||
@@ -74,0 +73,0 @@ myObject.foo = 'baz'; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
51271
1006
82