@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 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 durable job, get Postgres-native
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: durably record every step. If onHop returns a Promise, CNStra
39
- // waits for it before running the next hop (per-hop checkpoint barrier).
40
- progress: {
41
- onStarted: (s) => db.insertStimulation(s),
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
- - **Cross-process (recommended):** a retried job re-runs the whole flow from the entry signal.
70
- Make neurons **idempotent**, or deduplicate hops with `unique(stimulation_id, index)`.
71
- - **In-process partial resume:** `cns.activate(getFailedTasks())` works only within one process
72
- (it holds live object references) — it is not portable through the queue.
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
- - ✅ Per-step (hop) progress records → the `progress` sink (your tables / OIMDB / logs).
78
- - ❌ No built-in Postgres schema or admin UI — you own the store. pg-boss has **no per-step
79
- progress** of its own, so step history is always your tables. See the docs for a schema and
80
- an admin-panel design.
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
@@ -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
@@ -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 { ICNS, CNSPersistOptionsRegistry, CNSCollateral } from '@cnstra/core';
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
- /** Optional durable progress sink (started / hop / completed). */
91
- progress?: ICNSProgressSink;
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
- declare function createCNSWorker(options: TCNSWorkerOptions): Promise<string>;
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 ICNSProgressSink, type ICNSResponseLike, type IPgBossJob, type IPgBossLike, type TCNSHopRecord, type TCNSSendOptions, type TCNSStimulationJobData, type TCNSStimulationLifecycle, type TCNSWorkerOptions, createCNSWorker, enqueueStimulation, stimulationJob };
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 { ICNS, CNSPersistOptionsRegistry, CNSCollateral } from '@cnstra/core';
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
- /** Optional durable progress sink (started / hop / completed). */
91
- progress?: ICNSProgressSink;
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
- declare function createCNSWorker(options: TCNSWorkerOptions): Promise<string>;
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 ICNSProgressSink, type ICNSResponseLike, type IPgBossJob, type IPgBossLike, type TCNSHopRecord, type TCNSSendOptions, type TCNSStimulationJobData, type TCNSStimulationLifecycle, type TCNSWorkerOptions, createCNSWorker, enqueueStimulation, stimulationJob };
114
+ export { type IPgBossJob, type IPgBossLike, type TCNSPgBossWorkerHandle, type TCNSSendOptions, type TCNSWorkerOptions, createCNSWorker, enqueueStimulation, stimulationJob };