UNPKG

@m-ld/gateway

Version:

m-ld gateway for services

136 lines 10.9 kB
import { EventEmitter, once } from 'events'; import { Subdomain } from '../data/Subdomain.js'; /** * Queue entries are indexed by end-tick as * `_gw:tick:${zeroPad(tick.toString(36), 8)}`. This gives a maximum tick of * 36^8, about 3 trillion, about 90 years in milliseconds. */ const TICK_KEY_LEN = 8; const TICK_KEY_RADIX = 36; const TICK_KEY_PAD = '0'.repeat(TICK_KEY_LEN); export function tickKey(tick) { return `tick:${TICK_KEY_PAD.concat(tick.toString(TICK_KEY_RADIX)).slice(-TICK_KEY_LEN)}`; } /** * Mediates access to the given clone. * * Updates may be emitted by the `update` event, {@link #poll} and {@link #write}, * which emit disjoint sets of updates under normal operation. */ export class SubdomainClone extends Subdomain { constructor(spec, _clone, backend) { super(spec); this._clone = _clone; this.events = new EventEmitter; this._closing = false; this.queueStore = backend.sublevel('_gw:', { valueEncoding: 'json' }); // Follow the clone to enqueue updates _clone.follow(async (update, state) => { if (this.events.listenerCount('echo') > 0) { this.events.emit('echo', await this.enqueue(update)); } else if (this.events.listenerCount('update') > 0) { // LOCKS the state if anyone is subscribed await this.doAndStayLocked({ state }, async () => { this.events.emit('update', await this.enqueue(update)); }); } else { // Nobody is listening, will hopefully poll await this.enqueue(update, false); } }); this._state = { state: _clone }; } on(eventName, listener) { this.events.on(eventName, listener); return this; } // noinspection JSUnusedGlobalSymbols - implements _NodeEventTarget once(eventName, listener) { this.events.once(eventName, listener); return this; } /** Getter omits state, which is managed by this class */ get clone() { return this._clone; } get state() { return this._state.state; } async enqueue(update, emitting = true) { const emitCount = '@emitCount' in update ? update['@emitCount'] : 0; const subdomainUpdate = { ...update, '@emitCount': emitting ? emitCount + 1 : emitCount }; await this.queueStore.put(tickKey(update['@ticks']), subdomainUpdate); return subdomainUpdate; } async doAndStayLocked(state, proc) { this._state = { lock: 'read', ...state }; // Start waiting for the lock before the proc const lockReleased = once(this.events, 'lockRelease'); // TODO timeout await proc(); await lockReleased; this._state = { state: this._clone }; } get tick() { return this._clone.status.value.ticks; } /** * Report the update queue, incrementing the dequeue count. * LOCKS the state until a `lockRelease` event. */ async *poll() { // Grab a read lock on the clone that waits for lockRelease await new Promise(resolve => this._clone.read(state => this.doAndStayLocked({ state }, resolve))); for await (let [_key, update] of this.queueStore.iterator()) // Re-enqueue the update with an incremented emit count yield this.enqueue(update); } /** * LOCKS the state until a `lockRelease` event. */ write(request) { return new Promise((resolve, reject) => { const doWrite = async (state) => { if (request != null) { // Every write should produce one echo update or none. const beforeTick = this.tick; this.events.on('echo', resolve); try { this._state.state = await state.write(request); if (beforeTick === this.tick) resolve(null); } finally { // If clone tick not changed after write, cancel the expectation. if (beforeTick === this.tick) this.events.off('echo', resolve); } } else { resolve(null); } }; if (this._state.lock === 'write') doWrite(this._state.state).catch(reject); else this._clone.write(state => this.doAndStayLocked({ state, lock: 'write' }, () => doWrite(state))).catch(reject); }); } get locked() { return this._state.lock != null; } async unlock() { await this.queueStore.clear(); this.events.emit('lockRelease'); } async close() { if (!this._closing) { this._closing = true; await this._clone.close(); } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3ViZG9tYWluQ2xvbmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmVyL1N1YmRvbWFpbkNsb25lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUdBLE9BQU8sRUFBRSxZQUFZLEVBQUUsSUFBSSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQzVDLE9BQU8sRUFBRSxTQUFTLEVBQWlCLE1BQU0sc0JBQXNCLENBQUM7QUFhaEU7Ozs7R0FJRztBQUNILE1BQU0sWUFBWSxHQUFHLENBQUMsQ0FBQztBQUN2QixNQUFNLGNBQWMsR0FBRyxFQUFFLENBQUM7QUFDMUIsTUFBTSxZQUFZLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQztBQUM5QyxNQUFNLFVBQVUsT0FBTyxDQUFDLElBQVk7SUFDbEMsT0FBTyxRQUFRLFlBQVksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUM7QUFDM0YsQ0FBQztBQU1EOzs7OztHQUtHO0FBQ0gsTUFBTSxPQUFPLGNBQWUsU0FBUSxTQUFTO0lBTTNDLFlBQ0UsSUFBbUIsRUFDRixNQUFpQixFQUNsQyxPQUFxQjtRQUVyQixLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7UUFISyxXQUFNLEdBQU4sTUFBTSxDQUFXO1FBTDVCLFdBQU0sR0FBRyxJQUFJLFlBQVksQ0FBQztRQUMxQixhQUFRLEdBQUcsS0FBSyxDQUFDO1FBUXZCLElBQUksQ0FBQyxVQUFVLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNLEVBQUUsRUFBRSxhQUFhLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUN0RSxzQ0FBc0M7UUFDdEMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxFQUFFO1lBQ3BDLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQyxh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