@cnstra/pg-boss 0.1.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 +86 -0
- package/dist/index.d.mts +126 -0
- package/dist/index.d.ts +126 -0
- package/dist/index.js +134 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +106 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# @cnstra/pg-boss
|
|
2
|
+
|
|
3
|
+
> **⚠️ Experimental**: API may change in future versions.
|
|
4
|
+
|
|
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
|
|
7
|
+
retry/backoff/scheduling/singleton, and stream **per-hop progress** into any store for a
|
|
8
|
+
live admin/history view.
|
|
9
|
+
|
|
10
|
+
It follows the same pattern as the [Message Brokers guide](https://cnstra.org/docs/integrations/message-brokers):
|
|
11
|
+
**one job = one stimulation**, reconstructed across processes by **name** via a
|
|
12
|
+
`CNSPersistOptionsRegistry` (neuron/collateral object references are not portable).
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @cnstra/pg-boss @cnstra/core pg-boss
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Worker
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import PgBoss from 'pg-boss';
|
|
24
|
+
import { createCNS } from '@cnstra/core';
|
|
25
|
+
import { createCNSWorker } from '@cnstra/pg-boss';
|
|
26
|
+
|
|
27
|
+
const { cns, registry } = createCNS({ importUserNeuron, /* … */ });
|
|
28
|
+
|
|
29
|
+
const boss = new PgBoss(process.env.DATABASE_URL!);
|
|
30
|
+
await boss.start();
|
|
31
|
+
await boss.createQueue('cns');
|
|
32
|
+
|
|
33
|
+
await createCNSWorker({
|
|
34
|
+
boss,
|
|
35
|
+
cns,
|
|
36
|
+
registry,
|
|
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
|
+
},
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The worker reconstructs the entry signal from the job's `collateralName`, stimulates, and
|
|
49
|
+
awaits completion. If the stimulation fails or aborts, `waitUntilComplete()` rejects and the
|
|
50
|
+
error propagates so **pg-boss applies its retry/backoff policy**.
|
|
51
|
+
|
|
52
|
+
## Producer
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { enqueueStimulation, stimulationJob } from '@cnstra/pg-boss';
|
|
56
|
+
|
|
57
|
+
// Type-safe: throws if the collateral isn't registered
|
|
58
|
+
const job = stimulationJob(registry, importUser, { userId: '42' });
|
|
59
|
+
|
|
60
|
+
await enqueueStimulation(boss, 'cns', job, {
|
|
61
|
+
retryLimit: 5,
|
|
62
|
+
retryBackoff: true,
|
|
63
|
+
singletonKey: 'user:42', // one in-flight stimulation per entity
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Retries & resume
|
|
68
|
+
|
|
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.
|
|
73
|
+
|
|
74
|
+
## What this package does / doesn't
|
|
75
|
+
|
|
76
|
+
- ✅ 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.
|
|
81
|
+
|
|
82
|
+
See the full guide: https://cnstra.org/docs/integrations/pg-boss
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ICNS, CNSPersistOptionsRegistry, CNSCollateral } from '@cnstra/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal, structural view of the pg-boss surface this package touches.
|
|
5
|
+
*
|
|
6
|
+
* We type against these instead of importing `pg-boss` directly so the package
|
|
7
|
+
* builds without pg-boss installed. It is compatible with pg-boss v9/v10+ where
|
|
8
|
+
* `work` handlers receive an array of jobs (batch). Pass your real `PgBoss`
|
|
9
|
+
* instance — it satisfies this shape structurally.
|
|
10
|
+
*/
|
|
11
|
+
interface IPgBossJob<TData = unknown> {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
data: TData;
|
|
15
|
+
}
|
|
16
|
+
interface IPgBossLike {
|
|
17
|
+
send(name: string, data: object, options?: Record<string, unknown>): Promise<string | null>;
|
|
18
|
+
work<TData extends object>(name: string, handler: (jobs: IPgBossJob<TData>[]) => Promise<unknown>): Promise<string>;
|
|
19
|
+
work<TData extends object>(name: string, options: Record<string, unknown>, handler: (jobs: IPgBossJob<TData>[]) => Promise<unknown>): Promise<string>;
|
|
20
|
+
}
|
|
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
|
+
|
|
81
|
+
interface TCNSWorkerOptions {
|
|
82
|
+
/** Your pg-boss instance (already started). */
|
|
83
|
+
boss: IPgBossLike;
|
|
84
|
+
/** The CNS the jobs run against. */
|
|
85
|
+
cns: ICNS<any, any>;
|
|
86
|
+
/** Name ↔ object registry used to reconstruct signals from job data. */
|
|
87
|
+
registry: CNSPersistOptionsRegistry;
|
|
88
|
+
/** Queue name to consume. */
|
|
89
|
+
queue: string;
|
|
90
|
+
/** Optional durable progress sink (started / hop / completed). */
|
|
91
|
+
progress?: ICNSProgressSink;
|
|
92
|
+
/** pg-boss `work` options (batchSize, pollingIntervalSeconds, …). */
|
|
93
|
+
workOptions?: Record<string, unknown>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Register a pg-boss worker that turns each job into a CNStra stimulation.
|
|
97
|
+
*
|
|
98
|
+
* One job = one stimulation. The job carries `{ collateralName, payload }`; the
|
|
99
|
+
* worker reconstructs the entry signal by name, stimulates, optionally streams
|
|
100
|
+
* per-hop progress, and awaits completion. If the stimulation fails or aborts,
|
|
101
|
+
* `waitUntilComplete()` rejects and the error propagates so pg-boss applies its
|
|
102
|
+
* own retry/backoff policy.
|
|
103
|
+
*/
|
|
104
|
+
declare function createCNSWorker(options: TCNSWorkerOptions): Promise<string>;
|
|
105
|
+
|
|
106
|
+
/** Commonly used pg-boss send options (a superset is passed through). */
|
|
107
|
+
interface TCNSSendOptions {
|
|
108
|
+
retryLimit?: number;
|
|
109
|
+
retryBackoff?: boolean;
|
|
110
|
+
retryDelay?: number;
|
|
111
|
+
/** One in-flight job per key — e.g. one stimulation per entity id. */
|
|
112
|
+
singletonKey?: string;
|
|
113
|
+
startAfter?: number | string | Date;
|
|
114
|
+
expireInSeconds?: number;
|
|
115
|
+
priority?: number;
|
|
116
|
+
[key: string]: unknown;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Build a queue job from a **registered** collateral. Throws if the collateral
|
|
120
|
+
* is not in the registry, so a typo can't reach the queue.
|
|
121
|
+
*/
|
|
122
|
+
declare function stimulationJob(registry: CNSPersistOptionsRegistry, collateral: CNSCollateral<unknown>, payload: unknown, extra?: Pick<TCNSStimulationJobData, 'maxNeuronHops'>): TCNSStimulationJobData;
|
|
123
|
+
/** Send a stimulation job to a pg-boss queue. Returns the job id. */
|
|
124
|
+
declare function enqueueStimulation(boss: IPgBossLike, queue: string, job: TCNSStimulationJobData, options?: TCNSSendOptions): Promise<string | null>;
|
|
125
|
+
|
|
126
|
+
export { type ICNSProgressSink, type ICNSResponseLike, type IPgBossJob, type IPgBossLike, type TCNSHopRecord, type TCNSSendOptions, type TCNSStimulationJobData, type TCNSStimulationLifecycle, type TCNSWorkerOptions, createCNSWorker, enqueueStimulation, stimulationJob };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ICNS, CNSPersistOptionsRegistry, CNSCollateral } from '@cnstra/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal, structural view of the pg-boss surface this package touches.
|
|
5
|
+
*
|
|
6
|
+
* We type against these instead of importing `pg-boss` directly so the package
|
|
7
|
+
* builds without pg-boss installed. It is compatible with pg-boss v9/v10+ where
|
|
8
|
+
* `work` handlers receive an array of jobs (batch). Pass your real `PgBoss`
|
|
9
|
+
* instance — it satisfies this shape structurally.
|
|
10
|
+
*/
|
|
11
|
+
interface IPgBossJob<TData = unknown> {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
data: TData;
|
|
15
|
+
}
|
|
16
|
+
interface IPgBossLike {
|
|
17
|
+
send(name: string, data: object, options?: Record<string, unknown>): Promise<string | null>;
|
|
18
|
+
work<TData extends object>(name: string, handler: (jobs: IPgBossJob<TData>[]) => Promise<unknown>): Promise<string>;
|
|
19
|
+
work<TData extends object>(name: string, options: Record<string, unknown>, handler: (jobs: IPgBossJob<TData>[]) => Promise<unknown>): Promise<string>;
|
|
20
|
+
}
|
|
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
|
+
|
|
81
|
+
interface TCNSWorkerOptions {
|
|
82
|
+
/** Your pg-boss instance (already started). */
|
|
83
|
+
boss: IPgBossLike;
|
|
84
|
+
/** The CNS the jobs run against. */
|
|
85
|
+
cns: ICNS<any, any>;
|
|
86
|
+
/** Name ↔ object registry used to reconstruct signals from job data. */
|
|
87
|
+
registry: CNSPersistOptionsRegistry;
|
|
88
|
+
/** Queue name to consume. */
|
|
89
|
+
queue: string;
|
|
90
|
+
/** Optional durable progress sink (started / hop / completed). */
|
|
91
|
+
progress?: ICNSProgressSink;
|
|
92
|
+
/** pg-boss `work` options (batchSize, pollingIntervalSeconds, …). */
|
|
93
|
+
workOptions?: Record<string, unknown>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Register a pg-boss worker that turns each job into a CNStra stimulation.
|
|
97
|
+
*
|
|
98
|
+
* One job = one stimulation. The job carries `{ collateralName, payload }`; the
|
|
99
|
+
* worker reconstructs the entry signal by name, stimulates, optionally streams
|
|
100
|
+
* per-hop progress, and awaits completion. If the stimulation fails or aborts,
|
|
101
|
+
* `waitUntilComplete()` rejects and the error propagates so pg-boss applies its
|
|
102
|
+
* own retry/backoff policy.
|
|
103
|
+
*/
|
|
104
|
+
declare function createCNSWorker(options: TCNSWorkerOptions): Promise<string>;
|
|
105
|
+
|
|
106
|
+
/** Commonly used pg-boss send options (a superset is passed through). */
|
|
107
|
+
interface TCNSSendOptions {
|
|
108
|
+
retryLimit?: number;
|
|
109
|
+
retryBackoff?: boolean;
|
|
110
|
+
retryDelay?: number;
|
|
111
|
+
/** One in-flight job per key — e.g. one stimulation per entity id. */
|
|
112
|
+
singletonKey?: string;
|
|
113
|
+
startAfter?: number | string | Date;
|
|
114
|
+
expireInSeconds?: number;
|
|
115
|
+
priority?: number;
|
|
116
|
+
[key: string]: unknown;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Build a queue job from a **registered** collateral. Throws if the collateral
|
|
120
|
+
* is not in the registry, so a typo can't reach the queue.
|
|
121
|
+
*/
|
|
122
|
+
declare function stimulationJob(registry: CNSPersistOptionsRegistry, collateral: CNSCollateral<unknown>, payload: unknown, extra?: Pick<TCNSStimulationJobData, 'maxNeuronHops'>): TCNSStimulationJobData;
|
|
123
|
+
/** Send a stimulation job to a pg-boss queue. Returns the job id. */
|
|
124
|
+
declare function enqueueStimulation(boss: IPgBossLike, queue: string, job: TCNSStimulationJobData, options?: TCNSSendOptions): Promise<string | null>;
|
|
125
|
+
|
|
126
|
+
export { type ICNSProgressSink, type ICNSResponseLike, type IPgBossJob, type IPgBossLike, type TCNSHopRecord, type TCNSSendOptions, type TCNSStimulationJobData, type TCNSStimulationLifecycle, type TCNSWorkerOptions, createCNSWorker, enqueueStimulation, stimulationJob };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/index.ts
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
createCNSWorker: () => createCNSWorker,
|
|
23
|
+
enqueueStimulation: () => enqueueStimulation,
|
|
24
|
+
stimulationJob: () => stimulationJob
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/worker.ts
|
|
29
|
+
function toHopRecord(jobId, index, resp, cns, registry) {
|
|
30
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
31
|
+
const outputCol = (_a = resp.outputSignal) == null ? void 0 : _a.collateral;
|
|
32
|
+
const inputCol = (_b = resp.inputSignal) == null ? void 0 : _b.collateral;
|
|
33
|
+
const ownerNeuron = outputCol ? cns.network.getParentNeuronByCollateral(outputCol) : null;
|
|
34
|
+
return {
|
|
35
|
+
jobId,
|
|
36
|
+
index,
|
|
37
|
+
neuronName: ownerNeuron ? (_c = registry.getNeuronName(ownerNeuron)) != null ? _c : null : null,
|
|
38
|
+
inputCollateral: inputCol ? (_d = registry.getCollateralName(inputCol)) != null ? _d : null : null,
|
|
39
|
+
outputCollateral: outputCol ? (_e = registry.getCollateralName(outputCol)) != null ? _e : null : null,
|
|
40
|
+
inputPayload: (_i = (_h = (_f = resp.inputSignal) == null ? void 0 : _f.payload) != null ? _h : (_g = resp.outputSignal) == null ? void 0 : _g.payload) != null ? _i : null,
|
|
41
|
+
outputPayload: outputCol ? (_k = (_j = resp.outputSignal) == null ? void 0 : _j.payload) != null ? _k : null : null,
|
|
42
|
+
error: resp.error != null ? String(resp.error) : null,
|
|
43
|
+
at: Date.now()
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function createCNSWorker(options) {
|
|
47
|
+
const { boss, cns, registry, queue, progress, workOptions } = options;
|
|
48
|
+
const runJob = async (job) => {
|
|
49
|
+
var _a, _b, _c;
|
|
50
|
+
const { collateralName, payload, maxNeuronHops } = job.data;
|
|
51
|
+
const collateral = registry.getCollateral(collateralName);
|
|
52
|
+
if (!collateral) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`[@cnstra/pg-boss] Unknown collateral "${collateralName}". Register it in the CNSPersistOptionsRegistry.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const startedAt = Date.now();
|
|
58
|
+
let hopCount = 0;
|
|
59
|
+
await ((_a = progress == null ? void 0 : progress.onStarted) == null ? void 0 : _a.call(progress, {
|
|
60
|
+
jobId: job.id,
|
|
61
|
+
collateralName,
|
|
62
|
+
payload,
|
|
63
|
+
status: "running",
|
|
64
|
+
startedAt,
|
|
65
|
+
hopCount: 0
|
|
66
|
+
}));
|
|
67
|
+
const stimulation = cns.stimulate(collateral.createSignal(payload), {
|
|
68
|
+
maxNeuronHops,
|
|
69
|
+
onResponse: async (resp) => {
|
|
70
|
+
var _a2;
|
|
71
|
+
const rec = toHopRecord(
|
|
72
|
+
job.id,
|
|
73
|
+
hopCount,
|
|
74
|
+
resp,
|
|
75
|
+
cns,
|
|
76
|
+
registry
|
|
77
|
+
);
|
|
78
|
+
hopCount += 1;
|
|
79
|
+
await ((_a2 = progress == null ? void 0 : progress.onHop) == null ? void 0 : _a2.call(progress, rec));
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
await stimulation.waitUntilComplete();
|
|
84
|
+
await ((_b = progress == null ? void 0 : progress.onCompleted) == null ? void 0 : _b.call(progress, {
|
|
85
|
+
jobId: job.id,
|
|
86
|
+
collateralName,
|
|
87
|
+
payload,
|
|
88
|
+
status: "completed",
|
|
89
|
+
startedAt,
|
|
90
|
+
completedAt: Date.now(),
|
|
91
|
+
hopCount
|
|
92
|
+
}));
|
|
93
|
+
} catch (err) {
|
|
94
|
+
await ((_c = progress == null ? void 0 : progress.onCompleted) == null ? void 0 : _c.call(progress, {
|
|
95
|
+
jobId: job.id,
|
|
96
|
+
collateralName,
|
|
97
|
+
payload,
|
|
98
|
+
status: "failed",
|
|
99
|
+
startedAt,
|
|
100
|
+
completedAt: Date.now(),
|
|
101
|
+
hopCount,
|
|
102
|
+
error: err instanceof Error ? err.message : String(err)
|
|
103
|
+
}));
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const handler = async (jobs) => {
|
|
108
|
+
for (const job of jobs) {
|
|
109
|
+
await runJob(job);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
return workOptions ? boss.work(queue, workOptions, handler) : boss.work(queue, handler);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/enqueue.ts
|
|
116
|
+
function stimulationJob(registry, collateral, payload, extra) {
|
|
117
|
+
const collateralName = registry.getCollateralName(collateral);
|
|
118
|
+
if (!collateralName) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
"[@cnstra/pg-boss] Collateral is not registered in the CNSPersistOptionsRegistry; cannot enqueue it by name."
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return { collateralName, payload, ...extra };
|
|
124
|
+
}
|
|
125
|
+
function enqueueStimulation(boss, queue, job, options) {
|
|
126
|
+
return boss.send(queue, job, options);
|
|
127
|
+
}
|
|
128
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
129
|
+
0 && (module.exports = {
|
|
130
|
+
createCNSWorker,
|
|
131
|
+
enqueueStimulation,
|
|
132
|
+
stimulationJob
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/worker.ts","../src/enqueue.ts"],"sourcesContent":["export { createCNSWorker } from './worker';\nexport type { TCNSWorkerOptions } from './worker';\n\nexport { enqueueStimulation, stimulationJob } from './enqueue';\nexport type { TCNSSendOptions } from './enqueue';\n\nexport type {\n IPgBossLike,\n IPgBossJob,\n TCNSStimulationJobData,\n TCNSHopRecord,\n TCNSStimulationLifecycle,\n ICNSProgressSink,\n ICNSResponseLike,\n} from './types';\n","import type { CNSPersistOptionsRegistry, ICNS } from '@cnstra/core';\nimport type {\n IPgBossLike,\n IPgBossJob,\n ICNSProgressSink,\n ICNSResponseLike,\n TCNSHopRecord,\n TCNSStimulationJobData,\n} from './types';\n\nexport interface TCNSWorkerOptions {\n /** Your pg-boss instance (already started). */\n boss: IPgBossLike;\n /** The CNS the jobs run against. */\n cns: ICNS<any, any>;\n /** Name ↔ object registry used to reconstruct signals from job data. */\n registry: CNSPersistOptionsRegistry;\n /** Queue name to consume. */\n queue: string;\n /** Optional durable progress sink (started / hop / completed). */\n progress?: ICNSProgressSink;\n /** pg-boss `work` options (batchSize, pollingIntervalSeconds, …). */\n workOptions?: Record<string, unknown>;\n}\n\n/**\n * Resolve a single response into a durable hop record, turning live object\n * references into stable names via the registry (same derivation devtools uses).\n */\nfunction toHopRecord(\n jobId: string,\n index: number,\n resp: ICNSResponseLike,\n cns: ICNS<any, any>,\n registry: CNSPersistOptionsRegistry\n): TCNSHopRecord {\n const outputCol = resp.outputSignal?.collateral;\n const inputCol = resp.inputSignal?.collateral;\n\n const ownerNeuron = outputCol\n ? cns.network.getParentNeuronByCollateral(outputCol as any)\n : null;\n\n return {\n jobId,\n index,\n neuronName: ownerNeuron\n ? registry.getNeuronName(ownerNeuron as any) ?? null\n : null,\n inputCollateral: inputCol\n ? registry.getCollateralName(inputCol as any) ?? null\n : null,\n outputCollateral: outputCol\n ? registry.getCollateralName(outputCol as any) ?? null\n : null,\n inputPayload:\n resp.inputSignal?.payload ?? resp.outputSignal?.payload ?? null,\n outputPayload: outputCol ? resp.outputSignal?.payload ?? null : null,\n error: resp.error != null ? String(resp.error) : null,\n at: Date.now(),\n };\n}\n\n/**\n * Register a pg-boss worker that turns each job into a CNStra stimulation.\n *\n * One job = one stimulation. The job carries `{ collateralName, payload }`; the\n * worker reconstructs the entry signal by name, stimulates, optionally streams\n * per-hop progress, and awaits completion. If the stimulation fails or aborts,\n * `waitUntilComplete()` rejects and the error propagates so pg-boss applies its\n * own retry/backoff policy.\n */\nexport function createCNSWorker(options: TCNSWorkerOptions): Promise<string> {\n const { boss, cns, registry, queue, progress, workOptions } = options;\n\n const runJob = async (\n job: IPgBossJob<TCNSStimulationJobData>\n ): Promise<void> => {\n const { collateralName, payload, maxNeuronHops } = job.data;\n const collateral = registry.getCollateral(collateralName);\n if (!collateral) {\n throw new Error(\n `[@cnstra/pg-boss] Unknown collateral \"${collateralName}\". ` +\n `Register it in the CNSPersistOptionsRegistry.`\n );\n }\n\n const startedAt = Date.now();\n let hopCount = 0;\n\n await progress?.onStarted?.({\n jobId: job.id,\n collateralName,\n payload,\n status: 'running',\n startedAt,\n hopCount: 0,\n });\n\n const stimulation = cns.stimulate(collateral.createSignal(payload), {\n maxNeuronHops,\n onResponse: async (resp: ICNSResponseLike) => {\n const rec = toHopRecord(\n job.id,\n hopCount,\n resp,\n cns,\n registry\n );\n hopCount += 1;\n // Returning a Promise makes CNStra wait for the durable write\n // before enqueuing the next hop (per-hop checkpoint barrier).\n await progress?.onHop?.(rec);\n },\n });\n\n try {\n await stimulation.waitUntilComplete();\n await progress?.onCompleted?.({\n jobId: job.id,\n collateralName,\n payload,\n status: 'completed',\n startedAt,\n completedAt: Date.now(),\n hopCount,\n });\n } catch (err) {\n await progress?.onCompleted?.({\n jobId: job.id,\n collateralName,\n payload,\n status: 'failed',\n startedAt,\n completedAt: Date.now(),\n hopCount,\n error: err instanceof Error ? err.message : String(err),\n });\n // Let pg-boss decide about retries/backoff.\n throw err;\n }\n };\n\n const handler = async (\n jobs: IPgBossJob<TCNSStimulationJobData>[]\n ): Promise<void> => {\n for (const job of jobs) {\n await runJob(job);\n }\n };\n\n return workOptions\n ? boss.work<TCNSStimulationJobData>(queue, workOptions, handler)\n : boss.work<TCNSStimulationJobData>(queue, handler);\n}\n","import type { CNSCollateral, CNSPersistOptionsRegistry } from '@cnstra/core';\nimport type { IPgBossLike, TCNSStimulationJobData } from './types';\n\n/** Commonly used pg-boss send options (a superset is passed through). */\nexport interface TCNSSendOptions {\n retryLimit?: number;\n retryBackoff?: boolean;\n retryDelay?: number;\n /** One in-flight job per key — e.g. one stimulation per entity id. */\n singletonKey?: string;\n startAfter?: number | string | Date;\n expireInSeconds?: number;\n priority?: number;\n [key: string]: unknown;\n}\n\n/**\n * Build a queue job from a **registered** collateral. Throws if the collateral\n * is not in the registry, so a typo can't reach the queue.\n */\nexport function stimulationJob(\n registry: CNSPersistOptionsRegistry,\n collateral: CNSCollateral<unknown>,\n payload: unknown,\n extra?: Pick<TCNSStimulationJobData, 'maxNeuronHops'>\n): TCNSStimulationJobData {\n const collateralName = registry.getCollateralName(collateral);\n if (!collateralName) {\n throw new Error(\n '[@cnstra/pg-boss] Collateral is not registered in the ' +\n 'CNSPersistOptionsRegistry; cannot enqueue it by name.'\n );\n }\n return { collateralName, payload, ...extra };\n}\n\n/** Send a stimulation job to a pg-boss queue. Returns the job id. */\nexport function enqueueStimulation(\n boss: IPgBossLike,\n queue: string,\n job: TCNSStimulationJobData,\n options?: TCNSSendOptions\n): Promise<string | null> {\n return boss.send(queue, job as unknown as object, options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC6BA,SAAS,YACL,OACA,OACA,MACA,KACA,UACa;AAnCjB;AAoCI,QAAM,aAAY,UAAK,iBAAL,mBAAmB;AACrC,QAAM,YAAW,UAAK,gBAAL,mBAAkB;AAEnC,QAAM,cAAc,YACd,IAAI,QAAQ,4BAA4B,SAAgB,IACxD;AAEN,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA,YAAY,eACN,cAAS,cAAc,WAAkB,MAAzC,YAA8C,OAC9C;AAAA,IACN,iBAAiB,YACX,cAAS,kBAAkB,QAAe,MAA1C,YAA+C,OAC/C;AAAA,IACN,kBAAkB,aACZ,cAAS,kBAAkB,SAAgB,MAA3C,YAAgD,OAChD;AAAA,IACN,eACI,sBAAK,gBAAL,mBAAkB,YAAlB,aAA6B,UAAK,iBAAL,mBAAmB,YAAhD,YAA2D;AAAA,IAC/D,eAAe,aAAY,gBAAK,iBAAL,mBAAmB,YAAnB,YAA8B,OAAO;AAAA,IAChE,OAAO,KAAK,SAAS,OAAO,OAAO,KAAK,KAAK,IAAI;AAAA,IACjD,IAAI,KAAK,IAAI;AAAA,EACjB;AACJ;AAWO,SAAS,gBAAgB,SAA6C;AACzE,QAAM,EAAE,MAAM,KAAK,UAAU,OAAO,UAAU,YAAY,IAAI;AAE9D,QAAM,SAAS,OACX,QACgB;AA7ExB;AA8EQ,UAAM,EAAE,gBAAgB,SAAS,cAAc,IAAI,IAAI;AACvD,UAAM,aAAa,SAAS,cAAc,cAAc;AACxD,QAAI,CAAC,YAAY;AACb,YAAM,IAAI;AAAA,QACN,yCAAyC,cAAc;AAAA,MAE3D;AAAA,IACJ;AAEA,UAAM,YAAY,KAAK,IAAI;AAC3B,QAAI,WAAW;AAEf,YAAM,0CAAU,cAAV,kCAAsB;AAAA,MACxB,OAAO,IAAI;AAAA,MACX;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA,UAAU;AAAA,IACd;AAEA,UAAM,cAAc,IAAI,UAAU,WAAW,aAAa,OAAO,GAAG;AAAA,MAChE;AAAA,MACA,YAAY,OAAO,SAA2B;AArG1D,YAAAA;AAsGgB,cAAM,MAAM;AAAA,UACR,IAAI;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACJ;AACA,oBAAY;AAGZ,gBAAMA,MAAA,qCAAU,UAAV,gBAAAA,IAAA,eAAkB;AAAA,MAC5B;AAAA,IACJ,CAAC;AAED,QAAI;AACA,YAAM,YAAY,kBAAkB;AACpC,cAAM,0CAAU,gBAAV,kCAAwB;AAAA,QAC1B,OAAO,IAAI;AAAA,QACX;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,QACA,aAAa,KAAK,IAAI;AAAA,QACtB;AAAA,MACJ;AAAA,IACJ,SAAS,KAAK;AACV,cAAM,0CAAU,gBAAV,kCAAwB;AAAA,QAC1B,OAAO,IAAI;AAAA,QACX;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,QACA,aAAa,KAAK,IAAI;AAAA,QACtB;AAAA,QACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MAC1D;AAEA,YAAM;AAAA,IACV;AAAA,EACJ;AAEA,QAAM,UAAU,OACZ,SACgB;AAChB,eAAW,OAAO,MAAM;AACpB,YAAM,OAAO,GAAG;AAAA,IACpB;AAAA,EACJ;AAEA,SAAO,cACD,KAAK,KAA6B,OAAO,aAAa,OAAO,IAC7D,KAAK,KAA6B,OAAO,OAAO;AAC1D;;;ACtIO,SAAS,eACZ,UACA,YACA,SACA,OACsB;AACtB,QAAM,iBAAiB,SAAS,kBAAkB,UAAU;AAC5D,MAAI,CAAC,gBAAgB;AACjB,UAAM,IAAI;AAAA,MACN;AAAA,IAEJ;AAAA,EACJ;AACA,SAAO,EAAE,gBAAgB,SAAS,GAAG,MAAM;AAC/C;AAGO,SAAS,mBACZ,MACA,OACA,KACA,SACsB;AACtB,SAAO,KAAK,KAAK,OAAO,KAA0B,OAAO;AAC7D;","names":["_a"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// src/worker.ts
|
|
2
|
+
function toHopRecord(jobId, index, resp, cns, registry) {
|
|
3
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
4
|
+
const outputCol = (_a = resp.outputSignal) == null ? void 0 : _a.collateral;
|
|
5
|
+
const inputCol = (_b = resp.inputSignal) == null ? void 0 : _b.collateral;
|
|
6
|
+
const ownerNeuron = outputCol ? cns.network.getParentNeuronByCollateral(outputCol) : null;
|
|
7
|
+
return {
|
|
8
|
+
jobId,
|
|
9
|
+
index,
|
|
10
|
+
neuronName: ownerNeuron ? (_c = registry.getNeuronName(ownerNeuron)) != null ? _c : null : null,
|
|
11
|
+
inputCollateral: inputCol ? (_d = registry.getCollateralName(inputCol)) != null ? _d : null : null,
|
|
12
|
+
outputCollateral: outputCol ? (_e = registry.getCollateralName(outputCol)) != null ? _e : null : null,
|
|
13
|
+
inputPayload: (_i = (_h = (_f = resp.inputSignal) == null ? void 0 : _f.payload) != null ? _h : (_g = resp.outputSignal) == null ? void 0 : _g.payload) != null ? _i : null,
|
|
14
|
+
outputPayload: outputCol ? (_k = (_j = resp.outputSignal) == null ? void 0 : _j.payload) != null ? _k : null : null,
|
|
15
|
+
error: resp.error != null ? String(resp.error) : null,
|
|
16
|
+
at: Date.now()
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function createCNSWorker(options) {
|
|
20
|
+
const { boss, cns, registry, queue, progress, workOptions } = options;
|
|
21
|
+
const runJob = async (job) => {
|
|
22
|
+
var _a, _b, _c;
|
|
23
|
+
const { collateralName, payload, maxNeuronHops } = job.data;
|
|
24
|
+
const collateral = registry.getCollateral(collateralName);
|
|
25
|
+
if (!collateral) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`[@cnstra/pg-boss] Unknown collateral "${collateralName}". Register it in the CNSPersistOptionsRegistry.`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const startedAt = Date.now();
|
|
31
|
+
let hopCount = 0;
|
|
32
|
+
await ((_a = progress == null ? void 0 : progress.onStarted) == null ? void 0 : _a.call(progress, {
|
|
33
|
+
jobId: job.id,
|
|
34
|
+
collateralName,
|
|
35
|
+
payload,
|
|
36
|
+
status: "running",
|
|
37
|
+
startedAt,
|
|
38
|
+
hopCount: 0
|
|
39
|
+
}));
|
|
40
|
+
const stimulation = cns.stimulate(collateral.createSignal(payload), {
|
|
41
|
+
maxNeuronHops,
|
|
42
|
+
onResponse: async (resp) => {
|
|
43
|
+
var _a2;
|
|
44
|
+
const rec = toHopRecord(
|
|
45
|
+
job.id,
|
|
46
|
+
hopCount,
|
|
47
|
+
resp,
|
|
48
|
+
cns,
|
|
49
|
+
registry
|
|
50
|
+
);
|
|
51
|
+
hopCount += 1;
|
|
52
|
+
await ((_a2 = progress == null ? void 0 : progress.onHop) == null ? void 0 : _a2.call(progress, rec));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
try {
|
|
56
|
+
await stimulation.waitUntilComplete();
|
|
57
|
+
await ((_b = progress == null ? void 0 : progress.onCompleted) == null ? void 0 : _b.call(progress, {
|
|
58
|
+
jobId: job.id,
|
|
59
|
+
collateralName,
|
|
60
|
+
payload,
|
|
61
|
+
status: "completed",
|
|
62
|
+
startedAt,
|
|
63
|
+
completedAt: Date.now(),
|
|
64
|
+
hopCount
|
|
65
|
+
}));
|
|
66
|
+
} catch (err) {
|
|
67
|
+
await ((_c = progress == null ? void 0 : progress.onCompleted) == null ? void 0 : _c.call(progress, {
|
|
68
|
+
jobId: job.id,
|
|
69
|
+
collateralName,
|
|
70
|
+
payload,
|
|
71
|
+
status: "failed",
|
|
72
|
+
startedAt,
|
|
73
|
+
completedAt: Date.now(),
|
|
74
|
+
hopCount,
|
|
75
|
+
error: err instanceof Error ? err.message : String(err)
|
|
76
|
+
}));
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const handler = async (jobs) => {
|
|
81
|
+
for (const job of jobs) {
|
|
82
|
+
await runJob(job);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
return workOptions ? boss.work(queue, workOptions, handler) : boss.work(queue, handler);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/enqueue.ts
|
|
89
|
+
function stimulationJob(registry, collateral, payload, extra) {
|
|
90
|
+
const collateralName = registry.getCollateralName(collateral);
|
|
91
|
+
if (!collateralName) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"[@cnstra/pg-boss] Collateral is not registered in the CNSPersistOptionsRegistry; cannot enqueue it by name."
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return { collateralName, payload, ...extra };
|
|
97
|
+
}
|
|
98
|
+
function enqueueStimulation(boss, queue, job, options) {
|
|
99
|
+
return boss.send(queue, job, options);
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
createCNSWorker,
|
|
103
|
+
enqueueStimulation,
|
|
104
|
+
stimulationJob
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/worker.ts","../src/enqueue.ts"],"sourcesContent":["import type { CNSPersistOptionsRegistry, ICNS } from '@cnstra/core';\nimport type {\n IPgBossLike,\n IPgBossJob,\n ICNSProgressSink,\n ICNSResponseLike,\n TCNSHopRecord,\n TCNSStimulationJobData,\n} from './types';\n\nexport interface TCNSWorkerOptions {\n /** Your pg-boss instance (already started). */\n boss: IPgBossLike;\n /** The CNS the jobs run against. */\n cns: ICNS<any, any>;\n /** Name ↔ object registry used to reconstruct signals from job data. */\n registry: CNSPersistOptionsRegistry;\n /** Queue name to consume. */\n queue: string;\n /** Optional durable progress sink (started / hop / completed). */\n progress?: ICNSProgressSink;\n /** pg-boss `work` options (batchSize, pollingIntervalSeconds, …). */\n workOptions?: Record<string, unknown>;\n}\n\n/**\n * Resolve a single response into a durable hop record, turning live object\n * references into stable names via the registry (same derivation devtools uses).\n */\nfunction toHopRecord(\n jobId: string,\n index: number,\n resp: ICNSResponseLike,\n cns: ICNS<any, any>,\n registry: CNSPersistOptionsRegistry\n): TCNSHopRecord {\n const outputCol = resp.outputSignal?.collateral;\n const inputCol = resp.inputSignal?.collateral;\n\n const ownerNeuron = outputCol\n ? cns.network.getParentNeuronByCollateral(outputCol as any)\n : null;\n\n return {\n jobId,\n index,\n neuronName: ownerNeuron\n ? registry.getNeuronName(ownerNeuron as any) ?? null\n : null,\n inputCollateral: inputCol\n ? registry.getCollateralName(inputCol as any) ?? null\n : null,\n outputCollateral: outputCol\n ? registry.getCollateralName(outputCol as any) ?? null\n : null,\n inputPayload:\n resp.inputSignal?.payload ?? resp.outputSignal?.payload ?? null,\n outputPayload: outputCol ? resp.outputSignal?.payload ?? null : null,\n error: resp.error != null ? String(resp.error) : null,\n at: Date.now(),\n };\n}\n\n/**\n * Register a pg-boss worker that turns each job into a CNStra stimulation.\n *\n * One job = one stimulation. The job carries `{ collateralName, payload }`; the\n * worker reconstructs the entry signal by name, stimulates, optionally streams\n * per-hop progress, and awaits completion. If the stimulation fails or aborts,\n * `waitUntilComplete()` rejects and the error propagates so pg-boss applies its\n * own retry/backoff policy.\n */\nexport function createCNSWorker(options: TCNSWorkerOptions): Promise<string> {\n const { boss, cns, registry, queue, progress, workOptions } = options;\n\n const runJob = async (\n job: IPgBossJob<TCNSStimulationJobData>\n ): Promise<void> => {\n const { collateralName, payload, maxNeuronHops } = job.data;\n const collateral = registry.getCollateral(collateralName);\n if (!collateral) {\n throw new Error(\n `[@cnstra/pg-boss] Unknown collateral \"${collateralName}\". ` +\n `Register it in the CNSPersistOptionsRegistry.`\n );\n }\n\n const startedAt = Date.now();\n let hopCount = 0;\n\n await progress?.onStarted?.({\n jobId: job.id,\n collateralName,\n payload,\n status: 'running',\n startedAt,\n hopCount: 0,\n });\n\n const stimulation = cns.stimulate(collateral.createSignal(payload), {\n maxNeuronHops,\n onResponse: async (resp: ICNSResponseLike) => {\n const rec = toHopRecord(\n job.id,\n hopCount,\n resp,\n cns,\n registry\n );\n hopCount += 1;\n // Returning a Promise makes CNStra wait for the durable write\n // before enqueuing the next hop (per-hop checkpoint barrier).\n await progress?.onHop?.(rec);\n },\n });\n\n try {\n await stimulation.waitUntilComplete();\n await progress?.onCompleted?.({\n jobId: job.id,\n collateralName,\n payload,\n status: 'completed',\n startedAt,\n completedAt: Date.now(),\n hopCount,\n });\n } catch (err) {\n await progress?.onCompleted?.({\n jobId: job.id,\n collateralName,\n payload,\n status: 'failed',\n startedAt,\n completedAt: Date.now(),\n hopCount,\n error: err instanceof Error ? err.message : String(err),\n });\n // Let pg-boss decide about retries/backoff.\n throw err;\n }\n };\n\n const handler = async (\n jobs: IPgBossJob<TCNSStimulationJobData>[]\n ): Promise<void> => {\n for (const job of jobs) {\n await runJob(job);\n }\n };\n\n return workOptions\n ? boss.work<TCNSStimulationJobData>(queue, workOptions, handler)\n : boss.work<TCNSStimulationJobData>(queue, handler);\n}\n","import type { CNSCollateral, CNSPersistOptionsRegistry } from '@cnstra/core';\nimport type { IPgBossLike, TCNSStimulationJobData } from './types';\n\n/** Commonly used pg-boss send options (a superset is passed through). */\nexport interface TCNSSendOptions {\n retryLimit?: number;\n retryBackoff?: boolean;\n retryDelay?: number;\n /** One in-flight job per key — e.g. one stimulation per entity id. */\n singletonKey?: string;\n startAfter?: number | string | Date;\n expireInSeconds?: number;\n priority?: number;\n [key: string]: unknown;\n}\n\n/**\n * Build a queue job from a **registered** collateral. Throws if the collateral\n * is not in the registry, so a typo can't reach the queue.\n */\nexport function stimulationJob(\n registry: CNSPersistOptionsRegistry,\n collateral: CNSCollateral<unknown>,\n payload: unknown,\n extra?: Pick<TCNSStimulationJobData, 'maxNeuronHops'>\n): TCNSStimulationJobData {\n const collateralName = registry.getCollateralName(collateral);\n if (!collateralName) {\n throw new Error(\n '[@cnstra/pg-boss] Collateral is not registered in the ' +\n 'CNSPersistOptionsRegistry; cannot enqueue it by name.'\n );\n }\n return { collateralName, payload, ...extra };\n}\n\n/** Send a stimulation job to a pg-boss queue. Returns the job id. */\nexport function enqueueStimulation(\n boss: IPgBossLike,\n queue: string,\n job: TCNSStimulationJobData,\n options?: TCNSSendOptions\n): Promise<string | null> {\n return boss.send(queue, job as unknown as object, options);\n}\n"],"mappings":"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","names":["_a"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cnstra/pg-boss",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "pg-boss (Postgres) integration for CNStra: run stimulations as jobs and stream per-hop progress",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cnstra",
|
|
7
|
+
"pg-boss",
|
|
8
|
+
"postgres",
|
|
9
|
+
"queue",
|
|
10
|
+
"workflow",
|
|
11
|
+
"orchestration"
|
|
12
|
+
],
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"module": "dist/index.mjs",
|
|
15
|
+
"types": "dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.mjs",
|
|
20
|
+
"require": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"test:types": "tsc --noEmit",
|
|
30
|
+
"clean": "rm -rf dist"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@cnstra/core": "^2.4.0",
|
|
34
|
+
"pg-boss": ">=9"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"pg-boss": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@cnstra/core": "^2.4.0",
|
|
43
|
+
"tsup": "8.5.1",
|
|
44
|
+
"typescript": "5.7.2"
|
|
45
|
+
},
|
|
46
|
+
"author": "Andrei Baikov <abaikov23@gmail.com>",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|