@m-ld/gateway
Version:
m-ld gateway for services
136 lines • 10.9 kB
JavaScript
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3ViZG9tYWluQ2xvbmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmVyL1N1YmRvbWFpbkNsb25lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUdBLE9BQU8sRUFBRSxZQUFZLEVBQUUsSUFBSSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQzVDLE9BQU8sRUFBRSxTQUFTLEVBQWlCLE1BQU0sc0JBQXNCLENBQUM7QUFhaEU7Ozs7R0FJRztBQUNILE1BQU0sWUFBWSxHQUFHLENBQUMsQ0FBQztBQUN2QixNQUFNLGNBQWMsR0FBRyxFQUFFLENBQUM7QUFDMUIsTUFBTSxZQUFZLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQztBQUM5QyxNQUFNLFVBQVUsT0FBTyxDQUFDLElBQVk7SUFDbEMsT0FBTyxRQUFRLFlBQVksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUM7QUFDM0YsQ0FBQztBQU1EOzs7OztHQUtHO0FBQ0gsTUFBTSxPQUFPLGNBQWUsU0FBUSxTQUFTO0lBTTNDLFlBQ0UsSUFBbUIsRUFDRixNQUFpQixFQUNsQyxPQUFxQjtRQUVyQixLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7UUFISyxXQUFNLEdBQU4sTUFBTSxDQUFXO1FBTDVCLFdBQU0sR0FBRyxJQUFJLFlBQVksQ0FBQztRQUMxQixhQUFRLEdBQUcsS0FBSyxDQUFDO1FBUXZCLElBQUksQ0FBQyxVQUFVLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNLEVBQUUsRUFBRSxhQUFhLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUN0RSxzQ0FBc0M7UUFDdEMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxFQUFFO1lBQ3BDLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFO2dCQUN6QyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7YUFDdEQ7aUJBQU0sSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEVBQUU7Z0JBQ2xELDBDQUEwQztnQkFDMUMsTUFBTSxJQUFJLENBQUMsZUFBZSxDQUFDLEVBQUUsS0FBSyxFQUFFLEVBQUUsS0FBSyxJQUFJLEVBQUU7b0JBQy9DLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztnQkFDekQsQ0FBQyxDQUFDLENBQUM7YUFDSjtpQkFBTTtnQkFDTCwyQ0FBMkM7Z0JBQzNDLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUM7YUFDbkM7UUFDSCxDQUFDLENBQUMsQ0FBQztRQUNILElBQUksQ0FBQyxNQUFNLEdBQUcsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLENBQUM7SUFDbEMsQ0FBQztJQUtELEVBQUUsQ0FBQyxTQUFpQixFQUFFLFFBQWtDO1FBQ3RELElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLFNBQVMsRUFBRSxRQUFRLENBQUMsQ0FBQztRQUNwQyxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRCxtRUFBbUU7SUFDbkUsSUFBSSxDQUFDLFNBQWlCLEVBQUUsUUFBa0M7UUFDeEQsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQ3RDLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELHlEQUF5RDtJQUN6RCxJQUFJLEtBQUs7UUFDUCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUM7SUFDckIsQ0FBQztJQUVELElBQUksS0FBSztRQUNQLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7SUFDM0IsQ0FBQztJQUVPLEtBQUssQ0FBQyxPQUFPLENBQUMsTUFBd0MsRUFBRSxRQUFRLEdBQUcsSUFBSTtRQUM3RSxNQUFNLFNBQVMsR0FBRyxZQUFZLElBQUksTUFBTSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNwRSxNQUFNLGVBQWUsR0FBb0I7WUFDdkMsR0FBRyxNQUFNLEVBQUUsWUFBWSxFQUFFLFFBQVEsQ0FBQyxDQUFDLENBQUMsU0FBUyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUztTQUM5RCxDQUFDO1FBQ0YsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDLEVBQUUsZUFBZSxDQUFDLENBQUM7UUFDdEUsT0FBTyxlQUFlLENBQUM7SUFDekIsQ0FBQztJQUVPLEtBQUssQ0FBQyxlQUFlLENBQUMsS0FBaUIsRUFBRSxJQUFzQztRQUNyRixJQUFJLENBQUMsTUFBTSxHQUFHLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxHQUFHLEtBQUssRUFBRSxDQUFDO1FBQ3pDLDZDQUE2QztRQUM3QyxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxhQUFhLENBQUMsQ0FBQyxDQUFDLGVBQWU7UUFDdEUsTUFBTSxJQUFJLEVBQUUsQ0FBQztRQUNiLE1BQU0sWUFBWSxDQUFDO1FBQ25CLElBQUksQ0FBQyxNQUFNLEdBQUcsRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO0lBQ3ZDLENBQUM7SUFFRCxJQUFJLElBQUk7UUFDTixPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUM7SUFDeEMsQ0FBQztJQUVEOzs7T0FHRztJQUNILEtBQUssQ0FBQyxDQUFDLElBQUk7UUFDVCwyREFBMkQ7UUFDM0QsTUFBTSxJQUFJLE9BQU8sQ0FBTyxPQUFPLENBQUMsRUFBRSxDQUNoQyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUN2QixJQUFJLENBQUMsZUFBZSxDQUFDLEVBQUUsS0FBSyxFQUFFLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQy9DLElBQUksS0FBSyxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLElBQUksSUFBSSxDQUFDLFVBQVUsQ0FBQyxRQUFRLEVBQUU7WUFDekQsdURBQXVEO1lBQ3ZELE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUMvQixDQUFDO0lBRUQ7O09BRUc7SUFDSCxLQUFLLENBQUMsT0FBZTtRQUNuQixPQUFPLElBQUksT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1lBQ3JDLE1BQU0sT0FBTyxHQUFHLEtBQUssRUFBRSxLQUFnQixFQUFFLEVBQUU7Z0JBQ3pDLElBQUksT0FBTyxJQUFJLElBQUksRUFBRTtvQkFDbkIsc0RBQXNEO29CQUN0RCxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDO29CQUM3QixJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUM7b0JBQ2hDLElBQUk7d0JBQ0YsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEdBQUcsTUFBTSxLQUFLLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO3dCQUMvQyxJQUFJLFVBQVUsS0FBSyxJQUFJLENBQUMsSUFBSTs0QkFDMUIsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO3FCQUNqQjs0QkFBUzt3QkFDUixpRUFBaUU7d0JBQ2pFLElBQUksVUFBVSxLQUFLLElBQUksQ0FBQyxJQUFJOzRCQUMxQixJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUM7cUJBQ3BDO2lCQUNGO3FCQUFNO29CQUNMLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztpQkFDZjtZQUNILENBQUMsQ0FBQztZQUNGLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEtBQUssT0FBTztnQkFDOUIsT0FBTyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDOztnQkFFekMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FDeEIsSUFBSSxDQUFDLGVBQWUsQ0FBQyxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLEVBQzNDLEdBQUcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUN4QixDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNwQixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRCxJQUFJLE1BQU07UUFDUixPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxJQUFJLElBQUksQ0FBQztJQUNsQyxDQUFDO0lBRUQsS0FBSyxDQUFDLE1BQU07UUFDVixNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDOUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDbEMsQ0FBQztJQUVELEtBQUssQ0FBQyxLQUFLO1FBQ1QsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUU7WUFDbEIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7WUFDckIsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxDQUFDO1NBQzNCO0lBQ0gsQ0FBQztDQUNGIn0=