evnty
Version:
Async-first, reactive event handling library for complex event flows in browser and Node.js
206 lines (204 loc) • 6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: Object.getOwnPropertyDescriptor(all, name).get
});
}
_export(exports, {
get Broadcast () {
return Broadcast;
},
get BroadcastIterator () {
return BroadcastIterator;
},
get ConsumerHandle () {
return ConsumerHandle;
}
});
const _ringbuffercjs = require("./ring-buffer.cjs");
const _signalcjs = require("./signal.cjs");
const _asynccjs = require("./async.cjs");
const _utilscjs = require("./utils.cjs");
class ConsumerHandle {
#broadcast;
constructor(broadcast){
this.#broadcast = broadcast;
}
get cursor() {
return this.#broadcast.getCursor(this);
}
[Symbol.dispose]() {
this.#broadcast.leave(this);
}
}
class BroadcastIterator {
#broadcast;
#signal;
#handle;
constructor(broadcast, signal, handle){
this.#broadcast = broadcast;
this.#signal = signal;
this.#handle = handle;
}
async next() {
try {
while(true){
const result = this.#broadcast.tryConsume(this.#handle);
if (!result.done) {
return {
value: result.value,
done: false
};
}
await this.#signal.receive();
}
} catch {
return {
value: undefined,
done: true
};
}
}
async return() {
this.#broadcast.leave(this.#handle);
return {
value: undefined,
done: true
};
}
}
class Broadcast {
#buffer = new _ringbuffercjs.RingBuffer();
#signal = new _signalcjs.Signal();
#disposer;
#sink;
#nextId = 0;
#cursors = new Map();
#handles = new WeakMap();
#minCursor = 0;
#registry = new FinalizationRegistry((id)=>{
const cursor = this.#cursors.get(id);
this.#cursors.delete(id);
if (cursor === this.#minCursor) {
this.#minCursor = (0, _utilscjs.min)(this.#cursors.values(), this.#buffer.right);
const shift = this.#minCursor - this.#buffer.left;
if (shift > 0) this.#buffer.shiftN(shift);
}
});
[Symbol.toStringTag] = 'Broadcast';
constructor(){
this.#disposer = new _asynccjs.Disposer(this);
}
get sink() {
return this.#sink ??= this.emit.bind(this);
}
handleEvent(event) {
this.emit(event);
}
get size() {
return this.#cursors.size;
}
emit(value) {
if (this.#disposer.disposed) {
return false;
}
this.#buffer.push(value);
this.#signal.emit(value);
return true;
}
receive() {
return this.#signal.receive();
}
then(onfulfilled, onrejected) {
return this.receive().then(onfulfilled, onrejected);
}
catch(onrejected) {
return this.receive().catch(onrejected);
}
finally(onfinally) {
return this.receive().finally(onfinally);
}
join() {
const id = this.#nextId++;
const cursor = this.#buffer.right;
const handle = new ConsumerHandle(this);
this.#handles.set(handle, id);
this.#cursors.set(id, cursor);
if (this.#cursors.size === 1 || cursor < this.#minCursor) {
this.#minCursor = cursor;
}
this.#registry.register(handle, id, handle);
return handle;
}
getCursor(handle) {
const id = this.#handles.get(handle);
if (id === undefined) throw new Error('Invalid handle');
const cursor = this.#cursors.get(id);
if (cursor === undefined) throw new Error('Invalid handle');
return cursor;
}
leave(handle) {
const id = this.#handles.get(handle);
if (id === undefined) return;
const cursor = this.#cursors.get(id);
this.#handles.delete(handle);
this.#cursors.delete(id);
this.#registry.unregister(handle);
if (cursor === this.#minCursor) {
this.#minCursor = (0, _utilscjs.min)(this.#cursors.values(), this.#buffer.right);
const shift = this.#minCursor - this.#buffer.left;
if (shift > 0) this.#buffer.shiftN(shift);
}
}
consume(handle) {
const result = this.tryConsume(handle);
if (result.done) {
throw new Error('No value available');
}
return result.value;
}
tryConsume(handle) {
const id = this.#handles.get(handle);
if (id === undefined) throw new Error('Invalid handle');
const cursor = this.#cursors.get(id);
if (cursor === undefined) throw new Error('Invalid handle');
if (cursor >= this.#buffer.right) {
return {
value: undefined,
done: true
};
}
const value = this.#buffer.peekAt(cursor);
this.#cursors.set(id, cursor + 1);
if (cursor === this.#minCursor) {
this.#minCursor = (0, _utilscjs.min)(this.#cursors.values(), this.#buffer.right);
const shift = this.#minCursor - this.#buffer.left;
if (shift > 0) this.#buffer.shiftN(shift);
}
return {
value,
done: false
};
}
readable(handle) {
return this.getCursor(handle) < this.#buffer.right;
}
[Symbol.asyncIterator]() {
return new BroadcastIterator(this, this.#signal, this.join());
}
dispose() {
this[Symbol.dispose]();
}
[Symbol.dispose]() {
if (this.#disposer[Symbol.dispose]()) {
this.#signal[Symbol.dispose]();
this.#buffer.clear();
this.#cursors.clear();
}
}
}
//# sourceMappingURL=broadcast.cjs.map