"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Op = exports.state = void 0; const baseerr_1 = __importDefault(require("baseerr")); const p_defer_1 = __importDefault(require("p-defer")); var state; (function (state) { state["STARTED"] = "STARTED"; state["STOPPED"] = "STOPPED"; state["STARTING"] = "STARTING"; state["STOPPING"] = "STOPPING"; })(state || (exports.state = state = {})); var Op; (function (Op) { Op["START"] = "START"; Op["STOP"] = "STOP"; })(Op || (exports.Op = Op = {})); class AbstractStartable { constructor() { this.currentOp = null; this.pendingOp = null; this.started = false; } get state() { if (this.pendingOp?.op === Op.START) { return state.STARTING; } else if (this.pendingOp?.op === Op.STOP) { return state.STOPPING; } else if (this.currentOp?.op === Op.START) { return state.STARTING; } else if (this.currentOp?.op === Op.STOP) { return state.STOPPING; } else if (this.started) { return state.STARTED; } else { return state.STOPPED; } } async start(opts) { if (this.state === state.STARTED) return; if (this.state === state.STARTING) return this.currentOp.deferred.promise; if (this.state === state.STOPPING) { if (opts?.force) { const startPromise = this.scheduleOp({ op: Op.START, opts }); this.forceStop().catch(() => { /* noop */ }); return startPromise; } return Promise.reject(new Error('cannot start while stopping')); } // this.state === state.STOPPED await this.runOp({ op: Op.START, opts }); // @ts-ignore this.state could have changed if (this.state === state.STOPPING) { throw new Error('started successfully, but stopping now'); } } async stop(opts) { if (this.state === state.STOPPED) return; if (this.state === state.STOPPING) { if (opts?.force) { return this.forceStop(opts); } return this.currentOp.deferred.promise; } if (this.state === state.STARTING) { return this.scheduleOp({ op: Op.STOP, opts }); } // this.state === state.STARTED return this.runOp({ op: Op.STOP, opts }); } async scheduleOp(pendingOp) { console.log('scheduleOp', { nextPendingOp: pendingOp, pendingOp: this.pendingOp, }); if (this.pendingOp) { if (this.pendingOp.op === pendingOp.op) { // based on scheduleOp invocations, this should only happen with forceStop? if (Boolean(pendingOp.opts?.force) && !Boolean(this.pendingOp.opts?.force)) { // override old pending with new pending this.pendingOp = { ...pendingOp, deferred: this.pendingOp.deferred, }; return this.pendingOp.deferred.promise; } else { // use existing pending // pendingOp.deferred == null return this.pendingOp.deferred.promise; } } else { this.pendingOp.deferred.reject(new Error('aborted')); this.pendingOp = null; // fall through to this.pendingOp = null } } if (this.pendingOp == null) { this.pendingOp = { ...pendingOp, deferred: (0, p_defer_1.default)(), }; } return this.pendingOp.deferred.promise; } async runPendingOp(successfulFinishedOp) { const pendingOp = this.pendingOp; if (pendingOp == null) { return; } this.pendingOp = null; if (pendingOp.op === successfulFinishedOp) { pendingOp.deferred.resolve(); return; } // try { this.runOp(pendingOp).catch((err) => { //noop }); } async runOp(_currentOp) { const deferred = _currentOp.deferred ?? (0, p_defer_1.default)(); const currentOp = { ..._currentOp, deferred }; this.currentOp = currentOp; // run op in bg const self = this; (async () => { try { if (currentOp.op === Op.START) { await self._start(currentOp.opts); } else { // op === op.STOP await self._stop(currentOp.opts); } // stop was sucessful if (self.currentOp == currentOp) { self.started = currentOp.op === Op.START; self.currentOp = null; self.runPendingOp(currentOp.op); // kick off next op first.. currentOp.deferred.resolve(); } } catch (err) { if (self.currentOp == currentOp) { // self.started ? depends on implementation.. self.currentOp = null; self.runPendingOp(); // kick off next op first.. currentOp.deferred.reject(err); } } })(); return currentOp.deferred.promise; } async forceStop(opts) { const currentOp = this.currentOp; const pendingOp = this.pendingOp; baseerr_1.default.assert(currentOp?.op === Op.STOP || pendingOp?.op === Op.STOP, 'cannot override start with stop', { currentOp: currentOp?.op, pendingOp: pendingOp?.op, }); if (currentOp?.op === Op.STOP) { if (currentOp.opts?.force) { // already forcing, no need to override return currentOp.deferred.promise; } // run force stop, and only "listen" to the force stop this.currentOp = null; return this.runOp({ op: Op.STOP, opts, deferred: currentOp.deferred, }); } else { // pendingOp?.op === Op.STOP return this.scheduleOp({ op: Op.STOP, opts, }); } } } exports.default = AbstractStartable; //# sourceMappingURL=index.js.map