@cnstra/pg-boss 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -16
- package/dist/chunk-2JTKI4GS.mjs +10 -0
- package/dist/chunk-2JTKI4GS.mjs.map +1 -0
- package/dist/chunk-XJFC72LU.mjs +35 -0
- package/dist/chunk-XJFC72LU.mjs.map +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +40 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +52 -64
- package/dist/index.d.ts +52 -64
- package/dist/index.js +41 -85
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +44 -85
- package/dist/index.mjs.map +1 -1
- package/dist/postgres-progress.d.mts +75 -0
- package/dist/postgres-progress.d.ts +75 -0
- package/dist/postgres-progress.js +142 -0
- package/dist/postgres-progress.js.map +1 -0
- package/dist/postgres-progress.mjs +94 -0
- package/dist/postgres-progress.mjs.map +1 -0
- package/package.json +41 -7
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
|
+
|
|
22
|
+
// src/postgres-progress.ts
|
|
23
|
+
var postgres_progress_exports = {};
|
|
24
|
+
__export(postgres_progress_exports, {
|
|
25
|
+
CNSPgBossProgressRepository: () => CNSPgBossProgressRepository,
|
|
26
|
+
DEFAULT_TABLE: () => DEFAULT_TABLE,
|
|
27
|
+
assertSafeTable: () => assertSafeTable,
|
|
28
|
+
ensureSchema: () => ensureSchema,
|
|
29
|
+
pgBossProgressSchemaSql: () => pgBossProgressSchemaSql
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(postgres_progress_exports);
|
|
32
|
+
|
|
33
|
+
// src/CNSPgBossProgressRepository.ts
|
|
34
|
+
var import_pg = require("pg");
|
|
35
|
+
|
|
36
|
+
// src/migrate.ts
|
|
37
|
+
var DEFAULT_TABLE = "cns_pgboss_progress";
|
|
38
|
+
function assertSafeTable(table) {
|
|
39
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {
|
|
40
|
+
throw new Error(`[@cnstra/pg-boss] unsafe progress table name: ${JSON.stringify(table)}`);
|
|
41
|
+
}
|
|
42
|
+
return table;
|
|
43
|
+
}
|
|
44
|
+
__name(assertSafeTable, "assertSafeTable");
|
|
45
|
+
function pgBossProgressSchemaSql(table = DEFAULT_TABLE) {
|
|
46
|
+
assertSafeTable(table);
|
|
47
|
+
return `CREATE TABLE IF NOT EXISTS ${table} (
|
|
48
|
+
key text PRIMARY KEY,
|
|
49
|
+
progress jsonb NOT NULL,
|
|
50
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
51
|
+
);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS ${table}_updated_idx ON ${table} (updated_at)`;
|
|
53
|
+
}
|
|
54
|
+
__name(pgBossProgressSchemaSql, "pgBossProgressSchemaSql");
|
|
55
|
+
async function ensureSchema(db, table = DEFAULT_TABLE) {
|
|
56
|
+
await db.query(pgBossProgressSchemaSql(table));
|
|
57
|
+
}
|
|
58
|
+
__name(ensureSchema, "ensureSchema");
|
|
59
|
+
|
|
60
|
+
// src/CNSPgBossProgressRepository.ts
|
|
61
|
+
var _CNSPgBossProgressRepository = class _CNSPgBossProgressRepository {
|
|
62
|
+
constructor(opts) {
|
|
63
|
+
__publicField(this, "pool");
|
|
64
|
+
__publicField(this, "ownsPool");
|
|
65
|
+
__publicField(this, "table");
|
|
66
|
+
__publicField(this, "autoMigrate");
|
|
67
|
+
__publicField(this, "ready");
|
|
68
|
+
var _a, _b;
|
|
69
|
+
if (opts.pool) {
|
|
70
|
+
this.pool = opts.pool;
|
|
71
|
+
this.ownsPool = false;
|
|
72
|
+
} else if (opts.connectionString) {
|
|
73
|
+
this.pool = new import_pg.Pool({
|
|
74
|
+
connectionString: opts.connectionString
|
|
75
|
+
});
|
|
76
|
+
this.ownsPool = true;
|
|
77
|
+
} else {
|
|
78
|
+
throw new Error("[@cnstra/pg-boss] CNSPgBossProgressRepository needs `connectionString` or `pool`");
|
|
79
|
+
}
|
|
80
|
+
this.table = assertSafeTable((_a = opts.table) != null ? _a : DEFAULT_TABLE);
|
|
81
|
+
this.autoMigrate = (_b = opts.autoMigrate) != null ? _b : true;
|
|
82
|
+
}
|
|
83
|
+
ensure() {
|
|
84
|
+
var _a;
|
|
85
|
+
if (!this.autoMigrate) return Promise.resolve();
|
|
86
|
+
return (_a = this.ready) != null ? _a : this.ready = ensureSchema(this.pool, this.table);
|
|
87
|
+
}
|
|
88
|
+
async save(key, progress) {
|
|
89
|
+
await this.ensure();
|
|
90
|
+
await this.pool.query(`INSERT INTO ${this.table} (key, progress, updated_at)
|
|
91
|
+
VALUES ($1, $2::jsonb, now())
|
|
92
|
+
ON CONFLICT (key) DO UPDATE SET progress = EXCLUDED.progress, updated_at = now()`, [
|
|
93
|
+
key,
|
|
94
|
+
JSON.stringify(progress)
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
async load(key) {
|
|
98
|
+
var _a;
|
|
99
|
+
await this.ensure();
|
|
100
|
+
const res = await this.pool.query(`SELECT progress FROM ${this.table} WHERE key = $1`, [
|
|
101
|
+
key
|
|
102
|
+
]);
|
|
103
|
+
return (_a = res.rows[0]) == null ? void 0 : _a.progress;
|
|
104
|
+
}
|
|
105
|
+
async delete(key) {
|
|
106
|
+
await this.ensure();
|
|
107
|
+
await this.pool.query(`DELETE FROM ${this.table} WHERE key = $1`, [
|
|
108
|
+
key
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Prune rows not touched for `olderThanMs` — the safety net for jobs that failed
|
|
113
|
+
* PERMANENTLY (retries exhausted): their frontier row is never `delete`d (pg-boss
|
|
114
|
+
* has no terminal-failure hook), so it would leak. A still-retrying job keeps its
|
|
115
|
+
* `updated_at` fresh, so choose a threshold comfortably larger than your longest
|
|
116
|
+
* retry backoff window and run this on a schedule. Returns the number of rows removed.
|
|
117
|
+
*/
|
|
118
|
+
async deleteStale(olderThanMs) {
|
|
119
|
+
var _a;
|
|
120
|
+
await this.ensure();
|
|
121
|
+
const res = await this.pool.query(`DELETE FROM ${this.table}
|
|
122
|
+
WHERE updated_at < now() - ($1::bigint * interval '1 millisecond')`, [
|
|
123
|
+
Math.max(0, Math.floor(olderThanMs))
|
|
124
|
+
]);
|
|
125
|
+
return (_a = res.rowCount) != null ? _a : 0;
|
|
126
|
+
}
|
|
127
|
+
/** Close the owned pool (no-op when a pool was injected). */
|
|
128
|
+
async close() {
|
|
129
|
+
if (this.ownsPool) await this.pool.end();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
__name(_CNSPgBossProgressRepository, "CNSPgBossProgressRepository");
|
|
133
|
+
var CNSPgBossProgressRepository = _CNSPgBossProgressRepository;
|
|
134
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
135
|
+
0 && (module.exports = {
|
|
136
|
+
CNSPgBossProgressRepository,
|
|
137
|
+
DEFAULT_TABLE,
|
|
138
|
+
assertSafeTable,
|
|
139
|
+
ensureSchema,
|
|
140
|
+
pgBossProgressSchemaSql
|
|
141
|
+
});
|
|
142
|
+
//# sourceMappingURL=postgres-progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/postgres-progress.ts","../src/CNSPgBossProgressRepository.ts","../src/migrate.ts"],"sourcesContent":["/**\n * Opt-in Postgres progress store for pg-boss — import from\n * `@cnstra/pg-boss/postgres-progress` (this subpath pulls in `pg`; the main\n * `@cnstra/pg-boss` entry does not).\n */\nexport {\n CNSPgBossProgressRepository,\n type TCNSPgBossProgressRepositoryOptions,\n} from './CNSPgBossProgressRepository';\nexport {\n ensureSchema,\n pgBossProgressSchemaSql,\n assertSafeTable,\n DEFAULT_TABLE,\n type IPgQueryable,\n} from './migrate';\n","/**\n * Postgres-backed resume-progress store for pg-boss, keyed by the pg-boss **job id**.\n *\n * In pg-boss a run = stimulation = job (one entity), so the checkpoint is keyed by\n * `job.id`; there is no separate run store. pg-boss can't hold arbitrary per-hop\n * progress on its job row (its `Job` has data/output/signal but no mutable progress\n * field), so the frontier lives in its own table (`cns_pgboss_progress`, default) on\n * the same Postgres. One upserted row per key; dropped when the job completes.\n *\n * OPTIONAL: this is an opt-in export (`@cnstra/pg-boss/postgres-progress`) — importing\n * `@cnstra/pg-boss` (worker/enqueue) does NOT pull in `pg`. It's just one\n * `ICNSProgressRepository`; the progress store is independent of the broker, so you can\n * run pg-boss with `@cnstra/progress-redis` (or any impl) instead.\n */\nimport { Pool } from 'pg';\nimport type { ICNSProgressRepository } from '@cnstra/persist';\nimport type { TCNSProgress } from '@cnstra/persist-dto';\nimport { DEFAULT_TABLE, assertSafeTable, ensureSchema } from './migrate';\n\nexport interface TCNSPgBossProgressRepositoryOptions {\n /** Connection string (a Pool is created and owned by the repo). */\n connectionString?: string;\n /** Or inject an existing pg Pool (not closed by `close()`). */\n pool?: Pool;\n /** Table name (default `cns_pgboss_progress`). */\n table?: string;\n /** Lazily `ensureSchema` on first use (default true). Set false to manage migrations yourself. */\n autoMigrate?: boolean;\n}\n\nexport class CNSPgBossProgressRepository implements ICNSProgressRepository {\n private readonly pool: Pool;\n private readonly ownsPool: boolean;\n private readonly table: string;\n private readonly autoMigrate: boolean;\n private ready: Promise<void> | undefined;\n\n constructor(opts: TCNSPgBossProgressRepositoryOptions) {\n if (opts.pool) {\n this.pool = opts.pool;\n this.ownsPool = false;\n } else if (opts.connectionString) {\n this.pool = new Pool({ connectionString: opts.connectionString });\n this.ownsPool = true;\n } else {\n throw new Error(\n '[@cnstra/pg-boss] CNSPgBossProgressRepository needs `connectionString` or `pool`'\n );\n }\n this.table = assertSafeTable(opts.table ?? DEFAULT_TABLE);\n this.autoMigrate = opts.autoMigrate ?? true;\n }\n\n private ensure(): Promise<void> {\n if (!this.autoMigrate) return Promise.resolve();\n return (this.ready ??= ensureSchema(this.pool, this.table));\n }\n\n async save(key: string, progress: TCNSProgress): Promise<void> {\n await this.ensure();\n await this.pool.query(\n `INSERT INTO ${this.table} (key, progress, updated_at)\n VALUES ($1, $2::jsonb, now())\n ON CONFLICT (key) DO UPDATE SET progress = EXCLUDED.progress, updated_at = now()`,\n [key, JSON.stringify(progress)]\n );\n }\n\n async load(key: string): Promise<TCNSProgress | undefined> {\n await this.ensure();\n const res = await this.pool.query<{ progress: TCNSProgress }>(\n `SELECT progress FROM ${this.table} WHERE key = $1`,\n [key]\n );\n return res.rows[0]?.progress;\n }\n\n async delete(key: string): Promise<void> {\n await this.ensure();\n await this.pool.query(`DELETE FROM ${this.table} WHERE key = $1`, [key]);\n }\n\n /**\n * Prune rows not touched for `olderThanMs` — the safety net for jobs that failed\n * PERMANENTLY (retries exhausted): their frontier row is never `delete`d (pg-boss\n * has no terminal-failure hook), so it would leak. A still-retrying job keeps its\n * `updated_at` fresh, so choose a threshold comfortably larger than your longest\n * retry backoff window and run this on a schedule. Returns the number of rows removed.\n */\n async deleteStale(olderThanMs: number): Promise<number> {\n await this.ensure();\n const res = await this.pool.query(\n `DELETE FROM ${this.table}\n WHERE updated_at < now() - ($1::bigint * interval '1 millisecond')`,\n [Math.max(0, Math.floor(olderThanMs))]\n );\n return res.rowCount ?? 0;\n }\n\n /** Close the owned pool (no-op when a pool was injected). */\n async close(): Promise<void> {\n if (this.ownsPool) await this.pool.end();\n }\n}\n","/**\n * Schema / migrations for the pg-boss progress table (`cns_pgboss_progress`).\n *\n * The package OWNS this schema so you can manage it explicitly (a migration step in\n * your own tooling) instead of relying on the repository's lazy auto-create. Run it\n * via the `cnstra-pgboss-progress migrate` CLI, or call `ensureSchema(db)` yourself\n * with any pg client/pool.\n */\n\n/** Minimal pg client surface (a `pg` Pool or Client satisfies it). */\nexport interface IPgQueryable {\n query(text: string, params?: unknown[]): Promise<{ rows: unknown[] }>;\n}\n\nexport const DEFAULT_TABLE = 'cns_pgboss_progress';\n\n/** Guard the table identifier (it is interpolated into DDL/DML, not parameterised). */\nexport function assertSafeTable(table: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {\n throw new Error(\n `[@cnstra/pg-boss] unsafe progress table name: ${JSON.stringify(table)}`\n );\n }\n return table;\n}\n\n/** The CREATE TABLE for the progress store. One upserted row per key (job id). */\nexport function pgBossProgressSchemaSql(table: string = DEFAULT_TABLE): string {\n assertSafeTable(table);\n // `updated_at` + its index back `deleteStale()`: a row is deleted when its job\n // succeeds, but a job that fails PERMANENTLY (retries exhausted) leaves its row —\n // pg-boss has no terminal-failure hook to key off. Pruning by staleness collects\n // those, and the index keeps the sweep cheap.\n return `CREATE TABLE IF NOT EXISTS ${table} (\n key text PRIMARY KEY,\n progress jsonb NOT NULL,\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nCREATE INDEX IF NOT EXISTS ${table}_updated_idx ON ${table} (updated_at)`;\n}\n\n/** Idempotently create the progress table. Safe to run repeatedly. */\nexport async function ensureSchema(\n db: IPgQueryable,\n table: string = DEFAULT_TABLE\n): Promise<void> {\n await db.query(pgBossProgressSchemaSql(table));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;ACcA,gBAAqB;;;ACAd,IAAMA,gBAAgB;AAGtB,SAASC,gBAAgBC,OAAa;AACzC,MAAI,CAAC,2BAA2BC,KAAKD,KAAAA,GAAQ;AACzC,UAAM,IAAIE,MACN,iDAAiDC,KAAKC,UAAUJ,KAAAA,CAAAA,EAAQ;EAEhF;AACA,SAAOA;AACX;AAPgBD;AAUT,SAASM,wBAAwBL,QAAgBF,eAAa;AACjEC,kBAAgBC,KAAAA;AAKhB,SAAO,8BAA8BA,KAAAA;;;;;6BAKZA,KAAAA,mBAAwBA,KAAAA;AACrD;AAZgBK;AAehB,eAAsBC,aAClBC,IACAP,QAAgBF,eAAa;AAE7B,QAAMS,GAAGC,MAAMH,wBAAwBL,KAAAA,CAAAA;AAC3C;AALsBM;;;ADZf,IAAMG,+BAAN,MAAMA,6BAAAA;EAOT,YAAYC,MAA2C;AANtCC;AACAC;AACAC;AACAC;AACTC;AAnCZ;AAsCQ,QAAIL,KAAKC,MAAM;AACX,WAAKA,OAAOD,KAAKC;AACjB,WAAKC,WAAW;IACpB,WAAWF,KAAKM,kBAAkB;AAC9B,WAAKL,OAAO,IAAIM,eAAK;QAAED,kBAAkBN,KAAKM;MAAiB,CAAA;AAC/D,WAAKJ,WAAW;IACpB,OAAO;AACH,YAAM,IAAIM,MACN,kFAAA;IAER;AACA,SAAKL,QAAQM,iBAAgBT,UAAKG,UAALH,YAAcU,aAAAA;AAC3C,SAAKN,eAAcJ,UAAKI,gBAALJ,YAAoB;EAC3C;EAEQW,SAAwB;AArDpC;AAsDQ,QAAI,CAAC,KAAKP,YAAa,QAAOQ,QAAQC,QAAO;AAC7C,YAAQ,UAAKR,UAAL,iBAAKA,QAAUS,aAAa,KAAKb,MAAM,KAAKE,KAAK;EAC7D;EAEA,MAAMY,KAAKC,KAAaC,UAAuC;AAC3D,UAAM,KAAKN,OAAM;AACjB,UAAM,KAAKV,KAAKiB,MACZ,eAAe,KAAKf,KAAK;;gGAGzB;MAACa;MAAKG,KAAKC,UAAUH,QAAAA;KAAU;EAEvC;EAEA,MAAMI,KAAKL,KAAgD;AApE/D;AAqEQ,UAAM,KAAKL,OAAM;AACjB,UAAMW,MAAM,MAAM,KAAKrB,KAAKiB,MACxB,wBAAwB,KAAKf,KAAK,mBAClC;MAACa;KAAI;AAET,YAAOM,SAAIC,KAAK,CAAA,MAATD,mBAAaL;EACxB;EAEA,MAAMO,OAAOR,KAA4B;AACrC,UAAM,KAAKL,OAAM;AACjB,UAAM,KAAKV,KAAKiB,MAAM,eAAe,KAAKf,KAAK,mBAAmB;MAACa;KAAI;EAC3E;;;;;;;;EASA,MAAMS,YAAYC,aAAsC;AAzF5D;AA0FQ,UAAM,KAAKf,OAAM;AACjB,UAAMW,MAAM,MAAM,KAAKrB,KAAKiB,MACxB,eAAe,KAAKf,KAAK;kFAEzB;MAACwB,KAAKC,IAAI,GAAGD,KAAKE,MAAMH,WAAAA,CAAAA;KAAc;AAE1C,YAAOJ,SAAIQ,aAAJR,YAAgB;EAC3B;;EAGA,MAAMS,QAAuB;AACzB,QAAI,KAAK7B,SAAU,OAAM,KAAKD,KAAK+B,IAAG;EAC1C;AACJ;AAzEajC;AAAN,IAAMA,8BAAN;","names":["DEFAULT_TABLE","assertSafeTable","table","test","Error","JSON","stringify","pgBossProgressSchemaSql","ensureSchema","db","query","CNSPgBossProgressRepository","opts","pool","ownsPool","table","autoMigrate","ready","connectionString","Pool","Error","assertSafeTable","DEFAULT_TABLE","ensure","Promise","resolve","ensureSchema","save","key","progress","query","JSON","stringify","load","res","rows","delete","deleteStale","olderThanMs","Math","max","floor","rowCount","close","end"]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_TABLE,
|
|
3
|
+
assertSafeTable,
|
|
4
|
+
ensureSchema,
|
|
5
|
+
pgBossProgressSchemaSql
|
|
6
|
+
} from "./chunk-XJFC72LU.mjs";
|
|
7
|
+
import {
|
|
8
|
+
__name,
|
|
9
|
+
__publicField
|
|
10
|
+
} from "./chunk-2JTKI4GS.mjs";
|
|
11
|
+
|
|
12
|
+
// src/CNSPgBossProgressRepository.ts
|
|
13
|
+
import { Pool } from "pg";
|
|
14
|
+
var _CNSPgBossProgressRepository = class _CNSPgBossProgressRepository {
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
__publicField(this, "pool");
|
|
17
|
+
__publicField(this, "ownsPool");
|
|
18
|
+
__publicField(this, "table");
|
|
19
|
+
__publicField(this, "autoMigrate");
|
|
20
|
+
__publicField(this, "ready");
|
|
21
|
+
var _a, _b;
|
|
22
|
+
if (opts.pool) {
|
|
23
|
+
this.pool = opts.pool;
|
|
24
|
+
this.ownsPool = false;
|
|
25
|
+
} else if (opts.connectionString) {
|
|
26
|
+
this.pool = new Pool({
|
|
27
|
+
connectionString: opts.connectionString
|
|
28
|
+
});
|
|
29
|
+
this.ownsPool = true;
|
|
30
|
+
} else {
|
|
31
|
+
throw new Error("[@cnstra/pg-boss] CNSPgBossProgressRepository needs `connectionString` or `pool`");
|
|
32
|
+
}
|
|
33
|
+
this.table = assertSafeTable((_a = opts.table) != null ? _a : DEFAULT_TABLE);
|
|
34
|
+
this.autoMigrate = (_b = opts.autoMigrate) != null ? _b : true;
|
|
35
|
+
}
|
|
36
|
+
ensure() {
|
|
37
|
+
var _a;
|
|
38
|
+
if (!this.autoMigrate) return Promise.resolve();
|
|
39
|
+
return (_a = this.ready) != null ? _a : this.ready = ensureSchema(this.pool, this.table);
|
|
40
|
+
}
|
|
41
|
+
async save(key, progress) {
|
|
42
|
+
await this.ensure();
|
|
43
|
+
await this.pool.query(`INSERT INTO ${this.table} (key, progress, updated_at)
|
|
44
|
+
VALUES ($1, $2::jsonb, now())
|
|
45
|
+
ON CONFLICT (key) DO UPDATE SET progress = EXCLUDED.progress, updated_at = now()`, [
|
|
46
|
+
key,
|
|
47
|
+
JSON.stringify(progress)
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
async load(key) {
|
|
51
|
+
var _a;
|
|
52
|
+
await this.ensure();
|
|
53
|
+
const res = await this.pool.query(`SELECT progress FROM ${this.table} WHERE key = $1`, [
|
|
54
|
+
key
|
|
55
|
+
]);
|
|
56
|
+
return (_a = res.rows[0]) == null ? void 0 : _a.progress;
|
|
57
|
+
}
|
|
58
|
+
async delete(key) {
|
|
59
|
+
await this.ensure();
|
|
60
|
+
await this.pool.query(`DELETE FROM ${this.table} WHERE key = $1`, [
|
|
61
|
+
key
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Prune rows not touched for `olderThanMs` — the safety net for jobs that failed
|
|
66
|
+
* PERMANENTLY (retries exhausted): their frontier row is never `delete`d (pg-boss
|
|
67
|
+
* has no terminal-failure hook), so it would leak. A still-retrying job keeps its
|
|
68
|
+
* `updated_at` fresh, so choose a threshold comfortably larger than your longest
|
|
69
|
+
* retry backoff window and run this on a schedule. Returns the number of rows removed.
|
|
70
|
+
*/
|
|
71
|
+
async deleteStale(olderThanMs) {
|
|
72
|
+
var _a;
|
|
73
|
+
await this.ensure();
|
|
74
|
+
const res = await this.pool.query(`DELETE FROM ${this.table}
|
|
75
|
+
WHERE updated_at < now() - ($1::bigint * interval '1 millisecond')`, [
|
|
76
|
+
Math.max(0, Math.floor(olderThanMs))
|
|
77
|
+
]);
|
|
78
|
+
return (_a = res.rowCount) != null ? _a : 0;
|
|
79
|
+
}
|
|
80
|
+
/** Close the owned pool (no-op when a pool was injected). */
|
|
81
|
+
async close() {
|
|
82
|
+
if (this.ownsPool) await this.pool.end();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
__name(_CNSPgBossProgressRepository, "CNSPgBossProgressRepository");
|
|
86
|
+
var CNSPgBossProgressRepository = _CNSPgBossProgressRepository;
|
|
87
|
+
export {
|
|
88
|
+
CNSPgBossProgressRepository,
|
|
89
|
+
DEFAULT_TABLE,
|
|
90
|
+
assertSafeTable,
|
|
91
|
+
ensureSchema,
|
|
92
|
+
pgBossProgressSchemaSql
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=postgres-progress.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/CNSPgBossProgressRepository.ts"],"sourcesContent":["/**\n * Postgres-backed resume-progress store for pg-boss, keyed by the pg-boss **job id**.\n *\n * In pg-boss a run = stimulation = job (one entity), so the checkpoint is keyed by\n * `job.id`; there is no separate run store. pg-boss can't hold arbitrary per-hop\n * progress on its job row (its `Job` has data/output/signal but no mutable progress\n * field), so the frontier lives in its own table (`cns_pgboss_progress`, default) on\n * the same Postgres. One upserted row per key; dropped when the job completes.\n *\n * OPTIONAL: this is an opt-in export (`@cnstra/pg-boss/postgres-progress`) — importing\n * `@cnstra/pg-boss` (worker/enqueue) does NOT pull in `pg`. It's just one\n * `ICNSProgressRepository`; the progress store is independent of the broker, so you can\n * run pg-boss with `@cnstra/progress-redis` (or any impl) instead.\n */\nimport { Pool } from 'pg';\nimport type { ICNSProgressRepository } from '@cnstra/persist';\nimport type { TCNSProgress } from '@cnstra/persist-dto';\nimport { DEFAULT_TABLE, assertSafeTable, ensureSchema } from './migrate';\n\nexport interface TCNSPgBossProgressRepositoryOptions {\n /** Connection string (a Pool is created and owned by the repo). */\n connectionString?: string;\n /** Or inject an existing pg Pool (not closed by `close()`). */\n pool?: Pool;\n /** Table name (default `cns_pgboss_progress`). */\n table?: string;\n /** Lazily `ensureSchema` on first use (default true). Set false to manage migrations yourself. */\n autoMigrate?: boolean;\n}\n\nexport class CNSPgBossProgressRepository implements ICNSProgressRepository {\n private readonly pool: Pool;\n private readonly ownsPool: boolean;\n private readonly table: string;\n private readonly autoMigrate: boolean;\n private ready: Promise<void> | undefined;\n\n constructor(opts: TCNSPgBossProgressRepositoryOptions) {\n if (opts.pool) {\n this.pool = opts.pool;\n this.ownsPool = false;\n } else if (opts.connectionString) {\n this.pool = new Pool({ connectionString: opts.connectionString });\n this.ownsPool = true;\n } else {\n throw new Error(\n '[@cnstra/pg-boss] CNSPgBossProgressRepository needs `connectionString` or `pool`'\n );\n }\n this.table = assertSafeTable(opts.table ?? DEFAULT_TABLE);\n this.autoMigrate = opts.autoMigrate ?? true;\n }\n\n private ensure(): Promise<void> {\n if (!this.autoMigrate) return Promise.resolve();\n return (this.ready ??= ensureSchema(this.pool, this.table));\n }\n\n async save(key: string, progress: TCNSProgress): Promise<void> {\n await this.ensure();\n await this.pool.query(\n `INSERT INTO ${this.table} (key, progress, updated_at)\n VALUES ($1, $2::jsonb, now())\n ON CONFLICT (key) DO UPDATE SET progress = EXCLUDED.progress, updated_at = now()`,\n [key, JSON.stringify(progress)]\n );\n }\n\n async load(key: string): Promise<TCNSProgress | undefined> {\n await this.ensure();\n const res = await this.pool.query<{ progress: TCNSProgress }>(\n `SELECT progress FROM ${this.table} WHERE key = $1`,\n [key]\n );\n return res.rows[0]?.progress;\n }\n\n async delete(key: string): Promise<void> {\n await this.ensure();\n await this.pool.query(`DELETE FROM ${this.table} WHERE key = $1`, [key]);\n }\n\n /**\n * Prune rows not touched for `olderThanMs` — the safety net for jobs that failed\n * PERMANENTLY (retries exhausted): their frontier row is never `delete`d (pg-boss\n * has no terminal-failure hook), so it would leak. A still-retrying job keeps its\n * `updated_at` fresh, so choose a threshold comfortably larger than your longest\n * retry backoff window and run this on a schedule. Returns the number of rows removed.\n */\n async deleteStale(olderThanMs: number): Promise<number> {\n await this.ensure();\n const res = await this.pool.query(\n `DELETE FROM ${this.table}\n WHERE updated_at < now() - ($1::bigint * interval '1 millisecond')`,\n [Math.max(0, Math.floor(olderThanMs))]\n );\n return res.rowCount ?? 0;\n }\n\n /** Close the owned pool (no-op when a pool was injected). */\n async close(): Promise<void> {\n if (this.ownsPool) await this.pool.end();\n }\n}\n"],"mappings":";;;;;;;;;;;;AAcA,SAASA,YAAY;AAgBd,IAAMC,+BAAN,MAAMA,6BAAAA;EAOT,YAAYC,MAA2C;AANtCC;AACAC;AACAC;AACAC;AACTC;AAnCZ;AAsCQ,QAAIL,KAAKC,MAAM;AACX,WAAKA,OAAOD,KAAKC;AACjB,WAAKC,WAAW;IACpB,WAAWF,KAAKM,kBAAkB;AAC9B,WAAKL,OAAO,IAAIM,KAAK;QAAED,kBAAkBN,KAAKM;MAAiB,CAAA;AAC/D,WAAKJ,WAAW;IACpB,OAAO;AACH,YAAM,IAAIM,MACN,kFAAA;IAER;AACA,SAAKL,QAAQM,iBAAgBT,UAAKG,UAALH,YAAcU,aAAAA;AAC3C,SAAKN,eAAcJ,UAAKI,gBAALJ,YAAoB;EAC3C;EAEQW,SAAwB;AArDpC;AAsDQ,QAAI,CAAC,KAAKP,YAAa,QAAOQ,QAAQC,QAAO;AAC7C,YAAQ,UAAKR,UAAL,iBAAKA,QAAUS,aAAa,KAAKb,MAAM,KAAKE,KAAK;EAC7D;EAEA,MAAMY,KAAKC,KAAaC,UAAuC;AAC3D,UAAM,KAAKN,OAAM;AACjB,UAAM,KAAKV,KAAKiB,MACZ,eAAe,KAAKf,KAAK;;gGAGzB;MAACa;MAAKG,KAAKC,UAAUH,QAAAA;KAAU;EAEvC;EAEA,MAAMI,KAAKL,KAAgD;AApE/D;AAqEQ,UAAM,KAAKL,OAAM;AACjB,UAAMW,MAAM,MAAM,KAAKrB,KAAKiB,MACxB,wBAAwB,KAAKf,KAAK,mBAClC;MAACa;KAAI;AAET,YAAOM,SAAIC,KAAK,CAAA,MAATD,mBAAaL;EACxB;EAEA,MAAMO,OAAOR,KAA4B;AACrC,UAAM,KAAKL,OAAM;AACjB,UAAM,KAAKV,KAAKiB,MAAM,eAAe,KAAKf,KAAK,mBAAmB;MAACa;KAAI;EAC3E;;;;;;;;EASA,MAAMS,YAAYC,aAAsC;AAzF5D;AA0FQ,UAAM,KAAKf,OAAM;AACjB,UAAMW,MAAM,MAAM,KAAKrB,KAAKiB,MACxB,eAAe,KAAKf,KAAK;kFAEzB;MAACwB,KAAKC,IAAI,GAAGD,KAAKE,MAAMH,WAAAA,CAAAA;KAAc;AAE1C,YAAOJ,SAAIQ,aAAJR,YAAgB;EAC3B;;EAGA,MAAMS,QAAuB;AACzB,QAAI,KAAK7B,SAAU,OAAM,KAAKD,KAAK+B,IAAG;EAC1C;AACJ;AAzEajC;AAAN,IAAMA,8BAAN;","names":["Pool","CNSPgBossProgressRepository","opts","pool","ownsPool","table","autoMigrate","ready","connectionString","Pool","Error","assertSafeTable","DEFAULT_TABLE","ensure","Promise","resolve","ensureSchema","save","key","progress","query","JSON","stringify","load","res","rows","delete","deleteStale","olderThanMs","Math","max","floor","rowCount","close","end"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cnstra/pg-boss",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "pg-boss (Postgres) integration for CNStra: run stimulations as jobs and stream per-hop progress",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cnstra",
|
|
@@ -18,34 +18,68 @@
|
|
|
18
18
|
"types": "./dist/index.d.ts",
|
|
19
19
|
"import": "./dist/index.mjs",
|
|
20
20
|
"require": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./postgres-progress": {
|
|
23
|
+
"types": "./dist/postgres-progress.d.ts",
|
|
24
|
+
"import": "./dist/postgres-progress.mjs",
|
|
25
|
+
"require": "./dist/postgres-progress.js"
|
|
21
26
|
}
|
|
22
27
|
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"cnstra-pgboss-progress": "./dist/cli.js"
|
|
30
|
+
},
|
|
23
31
|
"sideEffects": false,
|
|
24
32
|
"files": [
|
|
25
33
|
"dist"
|
|
26
34
|
],
|
|
27
35
|
"scripts": {
|
|
28
36
|
"build": "tsup",
|
|
37
|
+
"test": "jest",
|
|
29
38
|
"test:types": "tsc --noEmit",
|
|
30
39
|
"clean": "rm -rf dist"
|
|
31
40
|
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@cnstra/types": "^0.1.0",
|
|
43
|
+
"@cnstra/broker": "^0.1.0"
|
|
44
|
+
},
|
|
32
45
|
"peerDependencies": {
|
|
33
46
|
"@cnstra/core": "^2.4.0",
|
|
34
|
-
"
|
|
47
|
+
"@cnstra/persist": "^0.1.0",
|
|
48
|
+
"@cnstra/persist-dto": "^0.1.0",
|
|
49
|
+
"pg-boss": ">=9",
|
|
50
|
+
"pg": ">=8"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@cnstra/core": "^2.4.0",
|
|
54
|
+
"@cnstra/persist": "^0.1.0",
|
|
55
|
+
"@cnstra/persist-dto": "^0.1.0",
|
|
56
|
+
"@types/jest": "29.5.12",
|
|
57
|
+
"@types/pg": "^8.11.0",
|
|
58
|
+
"jest": "29.7.0",
|
|
59
|
+
"pg": "^8.11.0",
|
|
60
|
+
"ts-jest": "29.1.2",
|
|
61
|
+
"tsup": "8.5.1",
|
|
62
|
+
"typescript": "5.7.2",
|
|
63
|
+
"@cnstra/broker": "^0.1.0"
|
|
35
64
|
},
|
|
36
65
|
"peerDependenciesMeta": {
|
|
37
66
|
"pg-boss": {
|
|
38
67
|
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"pg": {
|
|
70
|
+
"optional": true
|
|
39
71
|
}
|
|
40
72
|
},
|
|
41
|
-
"devDependencies": {
|
|
42
|
-
"@cnstra/core": "^2.4.0",
|
|
43
|
-
"tsup": "8.5.1",
|
|
44
|
-
"typescript": "5.7.2"
|
|
45
|
-
},
|
|
46
73
|
"author": "Andrei Baikov <abaikov23@gmail.com>",
|
|
47
74
|
"license": "MIT",
|
|
48
75
|
"publishConfig": {
|
|
49
76
|
"access": "public"
|
|
77
|
+
},
|
|
78
|
+
"typesVersions": {
|
|
79
|
+
"*": {
|
|
80
|
+
"postgres-progress": [
|
|
81
|
+
"./dist/postgres-progress.d.ts"
|
|
82
|
+
]
|
|
83
|
+
}
|
|
50
84
|
}
|
|
51
85
|
}
|