@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
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **⚠️ Experimental**: API may change in future versions.
|
|
4
4
|
|
|
5
5
|
[pg-boss](https://github.com/timgit/pg-boss) (Postgres-backed job queue) integration for
|
|
6
|
-
[CNStra](https://cnstra.org). Run each stimulation as a
|
|
6
|
+
[CNStra](https://cnstra.org). Run each stimulation as a resumable job, get Postgres-native
|
|
7
7
|
retry/backoff/scheduling/singleton, and stream **per-hop progress** into any store for a
|
|
8
8
|
live admin/history view.
|
|
9
9
|
|
|
@@ -35,13 +35,10 @@ await createCNSWorker({
|
|
|
35
35
|
cns,
|
|
36
36
|
registry,
|
|
37
37
|
queue: 'cns',
|
|
38
|
-
// Optional:
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
onHop: (h) => db.insertHop(h), // unique(stimulation_id, index) → idempotent
|
|
43
|
-
onCompleted: (s) => db.finishStimulation(s),
|
|
44
|
-
},
|
|
38
|
+
// Optional: record the name-based Stimulation → Attempt → Task history (the SAME
|
|
39
|
+
// model the DevTools use) into any ICNSStimulationRepository. In-memory just to
|
|
40
|
+
// *see* it; Postgres to keep it. One job = one stimulation; each retry = an attempt.
|
|
41
|
+
// observe: new CNSInMemoryStimulationRepository(),
|
|
45
42
|
});
|
|
46
43
|
```
|
|
47
44
|
|
|
@@ -66,18 +63,50 @@ await enqueueStimulation(boss, 'cns', job, {
|
|
|
66
63
|
|
|
67
64
|
## Retries & resume
|
|
68
65
|
|
|
69
|
-
- **
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
pg-boss reuses the **same job id** across a job's retries, so with `resume` the worker loads the
|
|
67
|
+
outstanding frontier and continues via `cns.activate(...)` — only the failed branch re-runs, with
|
|
68
|
+
its context. In pg-boss a run = stimulation = job, so the checkpoint is keyed by **job id**.
|
|
69
|
+
|
|
70
|
+
The checkpoint store is **pluggable** — three ways:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// 1) In-package Postgres table (cns_pgboss_progress) — opt-in subpath, pulls in `pg`
|
|
74
|
+
import { CNSPgBossProgressRepository } from '@cnstra/pg-boss/postgres-progress';
|
|
75
|
+
resume: { repository: new CNSPgBossProgressRepository({ connectionString: DATABASE_URL }) }
|
|
76
|
+
|
|
77
|
+
// 2) Redis, or any other ICNSProgressRepository — the store is independent of the broker
|
|
78
|
+
import { CNSRedisProgressRepository } from '@cnstra/progress-redis';
|
|
79
|
+
resume: { repository: new CNSRedisProgressRepository({ connection }) }
|
|
80
|
+
|
|
81
|
+
// 3) No resume at all → thin mode: a retried job re-runs the WHOLE flow from the entry signal.
|
|
82
|
+
// (omit `resume`) — make neurons idempotent.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
pg-boss's `Job` has no mutable native progress field (unlike BullMQ), so a checkpoint needs a store.
|
|
86
|
+
|
|
87
|
+
**Migrations:** the Postgres table is owned by this package. Auto-created on first use, or manage it
|
|
88
|
+
yourself: `DATABASE_URL=… npx cnstra-pgboss-progress migrate` (or call `ensureSchema(pool)` from
|
|
89
|
+
`@cnstra/pg-boss/postgres-progress`).
|
|
90
|
+
|
|
91
|
+
**Row lifecycle / cleanup:** one upserted row per `job.id`. The worker **deletes it when the job
|
|
92
|
+
succeeds**. A job that fails *permanently* (retries exhausted) keeps its row — pg-boss has no
|
|
93
|
+
terminal-failure hook to key off — so prune those periodically by staleness (a still-retrying job
|
|
94
|
+
keeps `updated_at` fresh, so pick a threshold well above your longest retry window):
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const store = new CNSPgBossProgressRepository({ connectionString: DATABASE_URL });
|
|
98
|
+
// e.g. in a daily cron: drop checkpoints untouched for over a day
|
|
99
|
+
await store.deleteStale(24 * 60 * 60 * 1000);
|
|
100
|
+
```
|
|
73
101
|
|
|
74
102
|
## What this package does / doesn't
|
|
75
103
|
|
|
76
104
|
- ✅ Job execution, retry/backoff, scheduling, singleton, concurrency → **pg-boss**.
|
|
77
|
-
- ✅
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
105
|
+
- ✅ Resumable frontier **resume** across retries → a pluggable `resume.repository` (in-package
|
|
106
|
+
Postgres, `@cnstra/progress-redis`, or your own); omit it for plain re-run-from-entry.
|
|
107
|
+
- ✅ Name-based run/attempt/task **observability** → an optional `observe` store (in-memory to
|
|
108
|
+
just see it, Postgres to keep it — the same model the DevTools render).
|
|
109
|
+
- ❌ No built-in admin UI — see `@cnstra/examples` (`demo:admin`) for the CNStra retry admin.
|
|
81
110
|
|
|
82
111
|
See the full guide: https://cnstra.org/docs/integrations/pg-boss
|
|
83
112
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
4
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
__name,
|
|
8
|
+
__publicField
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=chunk-2JTKI4GS.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__name
|
|
3
|
+
} from "./chunk-2JTKI4GS.mjs";
|
|
4
|
+
|
|
5
|
+
// src/migrate.ts
|
|
6
|
+
var DEFAULT_TABLE = "cns_pgboss_progress";
|
|
7
|
+
function assertSafeTable(table) {
|
|
8
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {
|
|
9
|
+
throw new Error(`[@cnstra/pg-boss] unsafe progress table name: ${JSON.stringify(table)}`);
|
|
10
|
+
}
|
|
11
|
+
return table;
|
|
12
|
+
}
|
|
13
|
+
__name(assertSafeTable, "assertSafeTable");
|
|
14
|
+
function pgBossProgressSchemaSql(table = DEFAULT_TABLE) {
|
|
15
|
+
assertSafeTable(table);
|
|
16
|
+
return `CREATE TABLE IF NOT EXISTS ${table} (
|
|
17
|
+
key text PRIMARY KEY,
|
|
18
|
+
progress jsonb NOT NULL,
|
|
19
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
20
|
+
);
|
|
21
|
+
CREATE INDEX IF NOT EXISTS ${table}_updated_idx ON ${table} (updated_at)`;
|
|
22
|
+
}
|
|
23
|
+
__name(pgBossProgressSchemaSql, "pgBossProgressSchemaSql");
|
|
24
|
+
async function ensureSchema(db, table = DEFAULT_TABLE) {
|
|
25
|
+
await db.query(pgBossProgressSchemaSql(table));
|
|
26
|
+
}
|
|
27
|
+
__name(ensureSchema, "ensureSchema");
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
DEFAULT_TABLE,
|
|
31
|
+
assertSafeTable,
|
|
32
|
+
pgBossProgressSchemaSql,
|
|
33
|
+
ensureSchema
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=chunk-XJFC72LU.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["/**\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":";;;;;AAcO,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;","names":["DEFAULT_TABLE","assertSafeTable","table","test","Error","JSON","stringify","pgBossProgressSchemaSql","ensureSchema","db","query"]}
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
4
|
+
|
|
5
|
+
// src/cli.ts
|
|
6
|
+
var import_pg = require("pg");
|
|
7
|
+
|
|
8
|
+
// src/migrate.ts
|
|
9
|
+
var DEFAULT_TABLE = "cns_pgboss_progress";
|
|
10
|
+
function assertSafeTable(table) {
|
|
11
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {
|
|
12
|
+
throw new Error(`[@cnstra/pg-boss] unsafe progress table name: ${JSON.stringify(table)}`);
|
|
13
|
+
}
|
|
14
|
+
return table;
|
|
15
|
+
}
|
|
16
|
+
__name(assertSafeTable, "assertSafeTable");
|
|
17
|
+
function pgBossProgressSchemaSql(table = DEFAULT_TABLE) {
|
|
18
|
+
assertSafeTable(table);
|
|
19
|
+
return `CREATE TABLE IF NOT EXISTS ${table} (
|
|
20
|
+
key text PRIMARY KEY,
|
|
21
|
+
progress jsonb NOT NULL,
|
|
22
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
23
|
+
);
|
|
24
|
+
CREATE INDEX IF NOT EXISTS ${table}_updated_idx ON ${table} (updated_at)`;
|
|
25
|
+
}
|
|
26
|
+
__name(pgBossProgressSchemaSql, "pgBossProgressSchemaSql");
|
|
27
|
+
async function ensureSchema(db, table = DEFAULT_TABLE) {
|
|
28
|
+
await db.query(pgBossProgressSchemaSql(table));
|
|
29
|
+
}
|
|
30
|
+
__name(ensureSchema, "ensureSchema");
|
|
31
|
+
|
|
32
|
+
// src/cli.ts
|
|
33
|
+
async function main() {
|
|
34
|
+
var _a;
|
|
35
|
+
const cmd = process.argv[2];
|
|
36
|
+
if (cmd !== "migrate") {
|
|
37
|
+
console.error("usage: cnstra-pgboss-progress migrate");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const url = process.env.DATABASE_URL;
|
|
41
|
+
if (!url) {
|
|
42
|
+
console.error("DATABASE_URL is required");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const table = (_a = process.env.CNS_PGBOSS_PROGRESS_TABLE) != null ? _a : DEFAULT_TABLE;
|
|
46
|
+
const pool = new import_pg.Pool({
|
|
47
|
+
connectionString: url
|
|
48
|
+
});
|
|
49
|
+
try {
|
|
50
|
+
await ensureSchema(pool, table);
|
|
51
|
+
console.log(`\u2713 ensured progress table "${table}"`);
|
|
52
|
+
} finally {
|
|
53
|
+
await pool.end();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
__name(main, "main");
|
|
57
|
+
void main().catch((e) => {
|
|
58
|
+
console.error(e);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/migrate.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `cnstra-pgboss-progress migrate` — create the pg-boss progress table.\n *\n * DATABASE_URL=postgres://... cnstra-pgboss-progress migrate\n * CNS_PGBOSS_PROGRESS_TABLE=my_table (optional; default cns_pgboss_progress)\n */\nimport { Pool } from 'pg';\nimport { ensureSchema, DEFAULT_TABLE } from './migrate';\n\nasync function main(): Promise<void> {\n const cmd = process.argv[2];\n if (cmd !== 'migrate') {\n console.error('usage: cnstra-pgboss-progress migrate'); // eslint-disable-line no-console\n process.exit(1);\n }\n const url = process.env.DATABASE_URL;\n if (!url) {\n console.error('DATABASE_URL is required'); // eslint-disable-line no-console\n process.exit(1);\n }\n const table = process.env.CNS_PGBOSS_PROGRESS_TABLE ?? DEFAULT_TABLE;\n const pool = new Pool({ connectionString: url });\n try {\n await ensureSchema(pool, table);\n // eslint-disable-next-line no-console\n console.log(`✓ ensured progress table \"${table}\"`);\n } finally {\n await pool.end();\n }\n}\n\nvoid main().catch(e => {\n console.error(e); // eslint-disable-line no-console\n process.exit(1);\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":";;;;;AAOA,gBAAqB;;;ACOd,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;;;ADhCtB,eAAeG,OAAAA;;AACX,QAAMC,MAAMC,QAAQC,KAAK,CAAA;AACzB,MAAIF,QAAQ,WAAW;AACnBG,YAAQC,MAAM,uCAAA;AACdH,YAAQI,KAAK,CAAA;EACjB;AACA,QAAMC,MAAML,QAAQM,IAAIC;AACxB,MAAI,CAACF,KAAK;AACNH,YAAQC,MAAM,0BAAA;AACdH,YAAQI,KAAK,CAAA;EACjB;AACA,QAAMI,SAAQR,aAAQM,IAAIG,8BAAZT,YAAyCU;AACvD,QAAMC,OAAO,IAAIC,eAAK;IAAEC,kBAAkBR;EAAI,CAAA;AAC9C,MAAI;AACA,UAAMS,aAAaH,MAAMH,KAAAA;AAEzBN,YAAQa,IAAI,kCAA6BP,KAAAA,GAAQ;EACrD,UAAA;AACI,UAAMG,KAAKK,IAAG;EAClB;AACJ;AApBelB;AAsBf,KAAKA,KAAAA,EAAOmB,MAAMC,CAAAA,MAAAA;AACdhB,UAAQC,MAAMe,CAAAA;AACdlB,UAAQI,KAAK,CAAA;AACjB,CAAA;","names":["DEFAULT_TABLE","assertSafeTable","table","test","Error","JSON","stringify","pgBossProgressSchemaSql","ensureSchema","db","query","main","cmd","process","argv","console","error","exit","url","env","DATABASE_URL","table","CNS_PGBOSS_PROGRESS_TABLE","DEFAULT_TABLE","pool","Pool","connectionString","ensureSchema","log","end","catch","e"]}
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_TABLE,
|
|
4
|
+
ensureSchema
|
|
5
|
+
} from "./chunk-XJFC72LU.mjs";
|
|
6
|
+
import {
|
|
7
|
+
__name
|
|
8
|
+
} from "./chunk-2JTKI4GS.mjs";
|
|
9
|
+
|
|
10
|
+
// src/cli.ts
|
|
11
|
+
import { Pool } from "pg";
|
|
12
|
+
async function main() {
|
|
13
|
+
var _a;
|
|
14
|
+
const cmd = process.argv[2];
|
|
15
|
+
if (cmd !== "migrate") {
|
|
16
|
+
console.error("usage: cnstra-pgboss-progress migrate");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const url = process.env.DATABASE_URL;
|
|
20
|
+
if (!url) {
|
|
21
|
+
console.error("DATABASE_URL is required");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const table = (_a = process.env.CNS_PGBOSS_PROGRESS_TABLE) != null ? _a : DEFAULT_TABLE;
|
|
25
|
+
const pool = new Pool({
|
|
26
|
+
connectionString: url
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
await ensureSchema(pool, table);
|
|
30
|
+
console.log(`\u2713 ensured progress table "${table}"`);
|
|
31
|
+
} finally {
|
|
32
|
+
await pool.end();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
__name(main, "main");
|
|
36
|
+
void main().catch((e) => {
|
|
37
|
+
console.error(e);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `cnstra-pgboss-progress migrate` — create the pg-boss progress table.\n *\n * DATABASE_URL=postgres://... cnstra-pgboss-progress migrate\n * CNS_PGBOSS_PROGRESS_TABLE=my_table (optional; default cns_pgboss_progress)\n */\nimport { Pool } from 'pg';\nimport { ensureSchema, DEFAULT_TABLE } from './migrate';\n\nasync function main(): Promise<void> {\n const cmd = process.argv[2];\n if (cmd !== 'migrate') {\n console.error('usage: cnstra-pgboss-progress migrate'); // eslint-disable-line no-console\n process.exit(1);\n }\n const url = process.env.DATABASE_URL;\n if (!url) {\n console.error('DATABASE_URL is required'); // eslint-disable-line no-console\n process.exit(1);\n }\n const table = process.env.CNS_PGBOSS_PROGRESS_TABLE ?? DEFAULT_TABLE;\n const pool = new Pool({ connectionString: url });\n try {\n await ensureSchema(pool, table);\n // eslint-disable-next-line no-console\n console.log(`✓ ensured progress table \"${table}\"`);\n } finally {\n await pool.end();\n }\n}\n\nvoid main().catch(e => {\n console.error(e); // eslint-disable-line no-console\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;AAOA,SAASA,YAAY;AAGrB,eAAeC,OAAAA;;AACX,QAAMC,MAAMC,QAAQC,KAAK,CAAA;AACzB,MAAIF,QAAQ,WAAW;AACnBG,YAAQC,MAAM,uCAAA;AACdH,YAAQI,KAAK,CAAA;EACjB;AACA,QAAMC,MAAML,QAAQM,IAAIC;AACxB,MAAI,CAACF,KAAK;AACNH,YAAQC,MAAM,0BAAA;AACdH,YAAQI,KAAK,CAAA;EACjB;AACA,QAAMI,SAAQR,aAAQM,IAAIG,8BAAZT,YAAyCU;AACvD,QAAMC,OAAO,IAAIC,KAAK;IAAEC,kBAAkBR;EAAI,CAAA;AAC9C,MAAI;AACA,UAAMS,aAAaH,MAAMH,KAAAA;AAEzBN,YAAQa,IAAI,kCAA6BP,KAAAA,GAAQ;EACrD,UAAA;AACI,UAAMG,KAAKK,IAAG;EAClB;AACJ;AApBelB;AAsBf,KAAKA,KAAAA,EAAOmB,MAAMC,CAAAA,MAAAA;AACdhB,UAAQC,MAAMe,CAAAA;AACdlB,UAAQI,KAAK,CAAA;AACjB,CAAA;","names":["Pool","main","cmd","process","argv","console","error","exit","url","env","DATABASE_URL","table","CNS_PGBOSS_PROGRESS_TABLE","DEFAULT_TABLE","pool","Pool","connectionString","ensureSchema","log","end","catch","e"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CNSPersistOptionsRegistry, ICNSStimulationRepository, ICNSProgressRepository, ICNSProgressRecordingStrategy } from '@cnstra/persist';
|
|
2
|
+
import { ICNS } from '@cnstra/types';
|
|
3
|
+
import { CNSCollateral } from '@cnstra/core';
|
|
4
|
+
import { TCNSStimulationJobData } from '@cnstra/broker';
|
|
5
|
+
export { ICNSResponseLike, TCNSStimulationJobData } from '@cnstra/broker';
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* Minimal, structural view of the pg-boss surface this package touches.
|
|
@@ -12,71 +16,14 @@ interface IPgBossJob<TData = unknown> {
|
|
|
12
16
|
id: string;
|
|
13
17
|
name: string;
|
|
14
18
|
data: TData;
|
|
19
|
+
/** 0-based retry count (pg-boss `Job.retryCount`); drives the attempt number. */
|
|
20
|
+
retryCount?: number;
|
|
15
21
|
}
|
|
16
22
|
interface IPgBossLike {
|
|
17
23
|
send(name: string, data: object, options?: Record<string, unknown>): Promise<string | null>;
|
|
18
24
|
work<TData extends object>(name: string, handler: (jobs: IPgBossJob<TData>[]) => Promise<unknown>): Promise<string>;
|
|
19
25
|
work<TData extends object>(name: string, options: Record<string, unknown>, handler: (jobs: IPgBossJob<TData>[]) => Promise<unknown>): Promise<string>;
|
|
20
26
|
}
|
|
21
|
-
/**
|
|
22
|
-
* The payload placed on the queue. It carries the collateral **name** and a
|
|
23
|
-
* JSON-serializable payload — never live neuron/collateral object references,
|
|
24
|
-
* which are not portable across processes.
|
|
25
|
-
*/
|
|
26
|
-
interface TCNSStimulationJobData {
|
|
27
|
-
collateralName: string;
|
|
28
|
-
payload: unknown;
|
|
29
|
-
/** Optional per-run cap on neuron hops. */
|
|
30
|
-
maxNeuronHops?: number;
|
|
31
|
-
}
|
|
32
|
-
/** One per-hop progress record (mirrors the devtools `CNSDTOHop` shape). */
|
|
33
|
-
interface TCNSHopRecord {
|
|
34
|
-
jobId: string;
|
|
35
|
-
index: number;
|
|
36
|
-
neuronName: string | null;
|
|
37
|
-
inputCollateral: string | null;
|
|
38
|
-
outputCollateral: string | null;
|
|
39
|
-
inputPayload: unknown;
|
|
40
|
-
outputPayload: unknown;
|
|
41
|
-
error: string | null;
|
|
42
|
-
at: number;
|
|
43
|
-
}
|
|
44
|
-
/** Lifecycle record for one stimulation (one job). */
|
|
45
|
-
interface TCNSStimulationLifecycle {
|
|
46
|
-
jobId: string;
|
|
47
|
-
collateralName: string;
|
|
48
|
-
payload: unknown;
|
|
49
|
-
status: 'running' | 'completed' | 'failed';
|
|
50
|
-
startedAt: number;
|
|
51
|
-
completedAt?: number;
|
|
52
|
-
hopCount: number;
|
|
53
|
-
error?: string;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Where progress is written (Postgres, OIMDB, logs, …).
|
|
57
|
-
*
|
|
58
|
-
* If `onHop` returns a Promise, CNStra waits for it **before enqueuing the next
|
|
59
|
-
* hop** — i.e. each step is durably committed before the flow advances. This is
|
|
60
|
-
* the per-hop checkpoint barrier that makes an admin/progress view truthful
|
|
61
|
-
* across restarts.
|
|
62
|
-
*/
|
|
63
|
-
interface ICNSProgressSink {
|
|
64
|
-
onStarted?(rec: TCNSStimulationLifecycle): void | Promise<void>;
|
|
65
|
-
onHop?(rec: TCNSHopRecord): void | Promise<void>;
|
|
66
|
-
onCompleted?(rec: TCNSStimulationLifecycle): void | Promise<void>;
|
|
67
|
-
}
|
|
68
|
-
/** Structural view of the response passed to `onResponse` (not exported by core). */
|
|
69
|
-
interface ICNSResponseLike {
|
|
70
|
-
inputSignal?: {
|
|
71
|
-
collateral?: unknown;
|
|
72
|
-
payload?: unknown;
|
|
73
|
-
};
|
|
74
|
-
outputSignal?: {
|
|
75
|
-
collateral?: unknown;
|
|
76
|
-
payload?: unknown;
|
|
77
|
-
};
|
|
78
|
-
error?: unknown;
|
|
79
|
-
}
|
|
80
27
|
|
|
81
28
|
interface TCNSWorkerOptions {
|
|
82
29
|
/** Your pg-boss instance (already started). */
|
|
@@ -87,8 +34,33 @@ interface TCNSWorkerOptions {
|
|
|
87
34
|
registry: CNSPersistOptionsRegistry;
|
|
88
35
|
/** Queue name to consume. */
|
|
89
36
|
queue: string;
|
|
90
|
-
/**
|
|
91
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Optional **observability** store: the canonical name-based
|
|
39
|
+
* Stimulation → Attempt → Task history (the SAME model the devtools use),
|
|
40
|
+
* written per hop via a {@link CNSStimulationPersistor}. Pass a
|
|
41
|
+
* `CNSInMemoryStimulationRepository` to just *see* progress (ephemeral), or a
|
|
42
|
+
* Postgres store to keep it. It records history; it does not drive resume —
|
|
43
|
+
* that is `resume` below. One job = one stimulation (`stimulationId` = job id),
|
|
44
|
+
* each pg-boss retry = a new attempt.
|
|
45
|
+
*/
|
|
46
|
+
observe?: ICNSStimulationRepository;
|
|
47
|
+
/**
|
|
48
|
+
* Optional resumable **resume**. When set, the worker records the run's
|
|
49
|
+
* outstanding frontier (by name) into `repository` as it advances, and a
|
|
50
|
+
* retried job (same job id) resumes from that frontier via `cns.activate(...)`
|
|
51
|
+
* instead of re-running from the entry signal. The record's lifecycle matches
|
|
52
|
+
* the pg-boss job row: dropped on success, kept on failure for the retry.
|
|
53
|
+
*
|
|
54
|
+
* Requires every neuron/collateral that can appear in the frontier to be in
|
|
55
|
+
* the `registry` (not just the entry collaterals). Neurons should still be
|
|
56
|
+
* idempotent — resume narrows re-execution to the unfinished branch, it does
|
|
57
|
+
* not make individual hops exactly-once.
|
|
58
|
+
*/
|
|
59
|
+
resume?: {
|
|
60
|
+
repository: ICNSProgressRepository;
|
|
61
|
+
/** When to persist during the run. Defaults to debounce + max-staleness. */
|
|
62
|
+
strategy?: ICNSProgressRecordingStrategy;
|
|
63
|
+
};
|
|
92
64
|
/** pg-boss `work` options (batchSize, pollingIntervalSeconds, …). */
|
|
93
65
|
workOptions?: Record<string, unknown>;
|
|
94
66
|
}
|
|
@@ -101,7 +73,23 @@ interface TCNSWorkerOptions {
|
|
|
101
73
|
* `waitUntilComplete()` rejects and the error propagates so pg-boss applies its
|
|
102
74
|
* own retry/backoff policy.
|
|
103
75
|
*/
|
|
104
|
-
|
|
76
|
+
/** A registered worker + a graceful `shutdown` (see {@link createCNSWorker}). */
|
|
77
|
+
interface TCNSPgBossWorkerHandle {
|
|
78
|
+
/** The pg-boss `work()` registration id. */
|
|
79
|
+
workId: string;
|
|
80
|
+
/**
|
|
81
|
+
* Graceful shutdown of THIS worker's processing: stop taking new jobs, let
|
|
82
|
+
* in-flight stimulations finish for up to `drainMs` (default 10s), then **abort**
|
|
83
|
+
* whatever is still running — an aborted stimulation's remaining tasks become its
|
|
84
|
+
* frontier, which the resume checkpoint records, so the job's retry resumes there.
|
|
85
|
+
* It does NOT stop or close your `boss` (you own it) or your stores — call
|
|
86
|
+
* `boss.stop(...)` and close your stores after this resolves.
|
|
87
|
+
*/
|
|
88
|
+
shutdown(opts?: {
|
|
89
|
+
drainMs?: number;
|
|
90
|
+
}): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
declare function createCNSWorker(options: TCNSWorkerOptions): Promise<TCNSPgBossWorkerHandle>;
|
|
105
93
|
|
|
106
94
|
/** Commonly used pg-boss send options (a superset is passed through). */
|
|
107
95
|
interface TCNSSendOptions {
|
|
@@ -123,4 +111,4 @@ declare function stimulationJob(registry: CNSPersistOptionsRegistry, collateral:
|
|
|
123
111
|
/** Send a stimulation job to a pg-boss queue. Returns the job id. */
|
|
124
112
|
declare function enqueueStimulation(boss: IPgBossLike, queue: string, job: TCNSStimulationJobData, options?: TCNSSendOptions): Promise<string | null>;
|
|
125
113
|
|
|
126
|
-
export { type
|
|
114
|
+
export { type IPgBossJob, type IPgBossLike, type TCNSPgBossWorkerHandle, type TCNSSendOptions, type TCNSWorkerOptions, createCNSWorker, enqueueStimulation, stimulationJob };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CNSPersistOptionsRegistry, ICNSStimulationRepository, ICNSProgressRepository, ICNSProgressRecordingStrategy } from '@cnstra/persist';
|
|
2
|
+
import { ICNS } from '@cnstra/types';
|
|
3
|
+
import { CNSCollateral } from '@cnstra/core';
|
|
4
|
+
import { TCNSStimulationJobData } from '@cnstra/broker';
|
|
5
|
+
export { ICNSResponseLike, TCNSStimulationJobData } from '@cnstra/broker';
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* Minimal, structural view of the pg-boss surface this package touches.
|
|
@@ -12,71 +16,14 @@ interface IPgBossJob<TData = unknown> {
|
|
|
12
16
|
id: string;
|
|
13
17
|
name: string;
|
|
14
18
|
data: TData;
|
|
19
|
+
/** 0-based retry count (pg-boss `Job.retryCount`); drives the attempt number. */
|
|
20
|
+
retryCount?: number;
|
|
15
21
|
}
|
|
16
22
|
interface IPgBossLike {
|
|
17
23
|
send(name: string, data: object, options?: Record<string, unknown>): Promise<string | null>;
|
|
18
24
|
work<TData extends object>(name: string, handler: (jobs: IPgBossJob<TData>[]) => Promise<unknown>): Promise<string>;
|
|
19
25
|
work<TData extends object>(name: string, options: Record<string, unknown>, handler: (jobs: IPgBossJob<TData>[]) => Promise<unknown>): Promise<string>;
|
|
20
26
|
}
|
|
21
|
-
/**
|
|
22
|
-
* The payload placed on the queue. It carries the collateral **name** and a
|
|
23
|
-
* JSON-serializable payload — never live neuron/collateral object references,
|
|
24
|
-
* which are not portable across processes.
|
|
25
|
-
*/
|
|
26
|
-
interface TCNSStimulationJobData {
|
|
27
|
-
collateralName: string;
|
|
28
|
-
payload: unknown;
|
|
29
|
-
/** Optional per-run cap on neuron hops. */
|
|
30
|
-
maxNeuronHops?: number;
|
|
31
|
-
}
|
|
32
|
-
/** One per-hop progress record (mirrors the devtools `CNSDTOHop` shape). */
|
|
33
|
-
interface TCNSHopRecord {
|
|
34
|
-
jobId: string;
|
|
35
|
-
index: number;
|
|
36
|
-
neuronName: string | null;
|
|
37
|
-
inputCollateral: string | null;
|
|
38
|
-
outputCollateral: string | null;
|
|
39
|
-
inputPayload: unknown;
|
|
40
|
-
outputPayload: unknown;
|
|
41
|
-
error: string | null;
|
|
42
|
-
at: number;
|
|
43
|
-
}
|
|
44
|
-
/** Lifecycle record for one stimulation (one job). */
|
|
45
|
-
interface TCNSStimulationLifecycle {
|
|
46
|
-
jobId: string;
|
|
47
|
-
collateralName: string;
|
|
48
|
-
payload: unknown;
|
|
49
|
-
status: 'running' | 'completed' | 'failed';
|
|
50
|
-
startedAt: number;
|
|
51
|
-
completedAt?: number;
|
|
52
|
-
hopCount: number;
|
|
53
|
-
error?: string;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Where progress is written (Postgres, OIMDB, logs, …).
|
|
57
|
-
*
|
|
58
|
-
* If `onHop` returns a Promise, CNStra waits for it **before enqueuing the next
|
|
59
|
-
* hop** — i.e. each step is durably committed before the flow advances. This is
|
|
60
|
-
* the per-hop checkpoint barrier that makes an admin/progress view truthful
|
|
61
|
-
* across restarts.
|
|
62
|
-
*/
|
|
63
|
-
interface ICNSProgressSink {
|
|
64
|
-
onStarted?(rec: TCNSStimulationLifecycle): void | Promise<void>;
|
|
65
|
-
onHop?(rec: TCNSHopRecord): void | Promise<void>;
|
|
66
|
-
onCompleted?(rec: TCNSStimulationLifecycle): void | Promise<void>;
|
|
67
|
-
}
|
|
68
|
-
/** Structural view of the response passed to `onResponse` (not exported by core). */
|
|
69
|
-
interface ICNSResponseLike {
|
|
70
|
-
inputSignal?: {
|
|
71
|
-
collateral?: unknown;
|
|
72
|
-
payload?: unknown;
|
|
73
|
-
};
|
|
74
|
-
outputSignal?: {
|
|
75
|
-
collateral?: unknown;
|
|
76
|
-
payload?: unknown;
|
|
77
|
-
};
|
|
78
|
-
error?: unknown;
|
|
79
|
-
}
|
|
80
27
|
|
|
81
28
|
interface TCNSWorkerOptions {
|
|
82
29
|
/** Your pg-boss instance (already started). */
|
|
@@ -87,8 +34,33 @@ interface TCNSWorkerOptions {
|
|
|
87
34
|
registry: CNSPersistOptionsRegistry;
|
|
88
35
|
/** Queue name to consume. */
|
|
89
36
|
queue: string;
|
|
90
|
-
/**
|
|
91
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Optional **observability** store: the canonical name-based
|
|
39
|
+
* Stimulation → Attempt → Task history (the SAME model the devtools use),
|
|
40
|
+
* written per hop via a {@link CNSStimulationPersistor}. Pass a
|
|
41
|
+
* `CNSInMemoryStimulationRepository` to just *see* progress (ephemeral), or a
|
|
42
|
+
* Postgres store to keep it. It records history; it does not drive resume —
|
|
43
|
+
* that is `resume` below. One job = one stimulation (`stimulationId` = job id),
|
|
44
|
+
* each pg-boss retry = a new attempt.
|
|
45
|
+
*/
|
|
46
|
+
observe?: ICNSStimulationRepository;
|
|
47
|
+
/**
|
|
48
|
+
* Optional resumable **resume**. When set, the worker records the run's
|
|
49
|
+
* outstanding frontier (by name) into `repository` as it advances, and a
|
|
50
|
+
* retried job (same job id) resumes from that frontier via `cns.activate(...)`
|
|
51
|
+
* instead of re-running from the entry signal. The record's lifecycle matches
|
|
52
|
+
* the pg-boss job row: dropped on success, kept on failure for the retry.
|
|
53
|
+
*
|
|
54
|
+
* Requires every neuron/collateral that can appear in the frontier to be in
|
|
55
|
+
* the `registry` (not just the entry collaterals). Neurons should still be
|
|
56
|
+
* idempotent — resume narrows re-execution to the unfinished branch, it does
|
|
57
|
+
* not make individual hops exactly-once.
|
|
58
|
+
*/
|
|
59
|
+
resume?: {
|
|
60
|
+
repository: ICNSProgressRepository;
|
|
61
|
+
/** When to persist during the run. Defaults to debounce + max-staleness. */
|
|
62
|
+
strategy?: ICNSProgressRecordingStrategy;
|
|
63
|
+
};
|
|
92
64
|
/** pg-boss `work` options (batchSize, pollingIntervalSeconds, …). */
|
|
93
65
|
workOptions?: Record<string, unknown>;
|
|
94
66
|
}
|
|
@@ -101,7 +73,23 @@ interface TCNSWorkerOptions {
|
|
|
101
73
|
* `waitUntilComplete()` rejects and the error propagates so pg-boss applies its
|
|
102
74
|
* own retry/backoff policy.
|
|
103
75
|
*/
|
|
104
|
-
|
|
76
|
+
/** A registered worker + a graceful `shutdown` (see {@link createCNSWorker}). */
|
|
77
|
+
interface TCNSPgBossWorkerHandle {
|
|
78
|
+
/** The pg-boss `work()` registration id. */
|
|
79
|
+
workId: string;
|
|
80
|
+
/**
|
|
81
|
+
* Graceful shutdown of THIS worker's processing: stop taking new jobs, let
|
|
82
|
+
* in-flight stimulations finish for up to `drainMs` (default 10s), then **abort**
|
|
83
|
+
* whatever is still running — an aborted stimulation's remaining tasks become its
|
|
84
|
+
* frontier, which the resume checkpoint records, so the job's retry resumes there.
|
|
85
|
+
* It does NOT stop or close your `boss` (you own it) or your stores — call
|
|
86
|
+
* `boss.stop(...)` and close your stores after this resolves.
|
|
87
|
+
*/
|
|
88
|
+
shutdown(opts?: {
|
|
89
|
+
drainMs?: number;
|
|
90
|
+
}): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
declare function createCNSWorker(options: TCNSWorkerOptions): Promise<TCNSPgBossWorkerHandle>;
|
|
105
93
|
|
|
106
94
|
/** Commonly used pg-boss send options (a superset is passed through). */
|
|
107
95
|
interface TCNSSendOptions {
|
|
@@ -123,4 +111,4 @@ declare function stimulationJob(registry: CNSPersistOptionsRegistry, collateral:
|
|
|
123
111
|
/** Send a stimulation job to a pg-boss queue. Returns the job id. */
|
|
124
112
|
declare function enqueueStimulation(boss: IPgBossLike, queue: string, job: TCNSStimulationJobData, options?: TCNSSendOptions): Promise<string | null>;
|
|
125
113
|
|
|
126
|
-
export { type
|
|
114
|
+
export { type IPgBossJob, type IPgBossLike, type TCNSPgBossWorkerHandle, type TCNSSendOptions, type TCNSWorkerOptions, createCNSWorker, enqueueStimulation, stimulationJob };
|