UNPKG

@cran/pg.q

Version:

Cranberry Postgres Queue

116 lines (115 loc) 3.51 kB
import { Pool } from "pg"; import debug from "debug"; import { createQueries, createQueryNames } from "./queries"; const logger = debug("cran").extend("pgq"); function deferred() { // eslint-disable-next-line @typescript-eslint/init-declarations let resolve; const promise = new Promise(function handler(res) { resolve = res; }); return Object.assign(promise, { resolve, }); } export class PgQ { handler; pool; testing; shutdown; finished; release; q; constructor({ pool, handler, target = "default", names = {}, testing = false, }) { this.pool = pool instanceof Pool ? pool : new Pool(pool); this.handler = handler; this.q = createQueries(createQueryNames(names), target); this.testing = testing; } async start() { const log = logger.extend("start"); log("enter"); if (this.finished) { log("already running"); return this.finished; } await this.pool.query(this.q.setup); this.shutdown = false; this.finished = deferred(); delete this.release; await this.active(); delete this.finished; log("leave"); } async stop() { const log = logger.extend("stop"); log("enter"); if (!this.finished) { log("already stopped"); return Promise.resolve(); } this.shutdown = true; this.release?.resolve(); log("leave"); return this.finished; } async active() { const log = logger.extend("active"); log("enter"); while (true) { if (this.shutdown) { break; } if (!await this.run()) { await this.standby(); delete this.release; } } this.finished.resolve(); log("leave"); } async standby() { const log = logger.extend("standby"); log("enter"); if (this.shutdown) { return; } const client = await this.pool.connect(); this.release = deferred(); const resolver = deferred(); client.on("notification", resolver.resolve); await client.query(this.q.listen); await Promise.race([this.release, resolver,]); client.off("notification", resolver.resolve); await client.query(this.q.unlisten); client.release(); log("leave"); } async run() { const log = logger.extend("run"); log("enter"); const client = await this.pool.connect(); let result = false; await client.query("begin"); const r_next = await client.query(this.q.next); if (1 === r_next.rows.length) { await client.query("savepoint rc"); const [{ id, },] = r_next.rows; try { const r_dq = await client.query(this.q.deq, [id,]); if (1 === r_dq.rows.length) { const [{ payload, },] = r_dq.rows; await this.handler(payload); result = true; } } catch (context) { log("error"); log.extend("error")(context); await client.query("rollback to rc"); } } await client.query(this.testing ? "rollback" : "commit"); client.release(); log("leave"); return result; } }