@ultimat3/jobs 1.0.0 → 1.2.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.
Files changed (74) hide show
  1. package/package.json +5 -5
  2. package/src/worker.ts +33 -1
  3. package/src/clock.d.ts +0 -7
  4. package/src/clock.d.ts.map +0 -1
  5. package/src/clock.js +0 -21
  6. package/src/clock.js.map +0 -1
  7. package/src/driver-memory.d.ts +0 -9
  8. package/src/driver-memory.d.ts.map +0 -1
  9. package/src/driver-memory.js +0 -189
  10. package/src/driver-memory.js.map +0 -1
  11. package/src/driver-nats.d.ts +0 -7
  12. package/src/driver-nats.d.ts.map +0 -1
  13. package/src/driver-nats.js +0 -51
  14. package/src/driver-nats.js.map +0 -1
  15. package/src/driver-pg-sql.d.ts +0 -19
  16. package/src/driver-pg-sql.d.ts.map +0 -1
  17. package/src/driver-pg-sql.js +0 -160
  18. package/src/driver-pg-sql.js.map +0 -1
  19. package/src/driver-pg.d.ts +0 -17
  20. package/src/driver-pg.d.ts.map +0 -1
  21. package/src/driver-pg.js +0 -246
  22. package/src/driver-pg.js.map +0 -1
  23. package/src/driver-redis.d.ts +0 -7
  24. package/src/driver-redis.d.ts.map +0 -1
  25. package/src/driver-redis.js +0 -54
  26. package/src/driver-redis.js.map +0 -1
  27. package/src/driver.d.ts +0 -114
  28. package/src/driver.d.ts.map +0 -1
  29. package/src/driver.js +0 -14
  30. package/src/driver.js.map +0 -1
  31. package/src/errors.d.ts +0 -63
  32. package/src/errors.d.ts.map +0 -1
  33. package/src/errors.js +0 -105
  34. package/src/errors.js.map +0 -1
  35. package/src/events.d.ts +0 -35
  36. package/src/events.d.ts.map +0 -1
  37. package/src/events.js +0 -92
  38. package/src/events.js.map +0 -1
  39. package/src/index.d.ts +0 -32
  40. package/src/index.d.ts.map +0 -1
  41. package/src/index.js +0 -18
  42. package/src/index.js.map +0 -1
  43. package/src/inspect.d.ts +0 -82
  44. package/src/inspect.d.ts.map +0 -1
  45. package/src/inspect.js +0 -113
  46. package/src/inspect.js.map +0 -1
  47. package/src/job.d.ts +0 -71
  48. package/src/job.d.ts.map +0 -1
  49. package/src/job.js +0 -99
  50. package/src/job.js.map +0 -1
  51. package/src/limits.d.ts +0 -47
  52. package/src/limits.d.ts.map +0 -1
  53. package/src/limits.js +0 -0
  54. package/src/limits.js.map +0 -1
  55. package/src/outbox.d.ts +0 -81
  56. package/src/outbox.d.ts.map +0 -1
  57. package/src/outbox.js +0 -202
  58. package/src/outbox.js.map +0 -1
  59. package/src/retry.d.ts +0 -40
  60. package/src/retry.d.ts.map +0 -1
  61. package/src/retry.js +0 -59
  62. package/src/retry.js.map +0 -1
  63. package/src/scheduler.d.ts +0 -79
  64. package/src/scheduler.d.ts.map +0 -1
  65. package/src/scheduler.js +0 -183
  66. package/src/scheduler.js.map +0 -1
  67. package/src/steps.d.ts +0 -86
  68. package/src/steps.d.ts.map +0 -1
  69. package/src/steps.js +0 -227
  70. package/src/steps.js.map +0 -1
  71. package/src/worker.d.ts +0 -68
  72. package/src/worker.d.ts.map +0 -1
  73. package/src/worker.js +0 -273
  74. package/src/worker.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/jobs",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Durable background work: steps, transactional outbox, cron tasks, one driver interface",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,9 +30,9 @@
30
30
  "test": "bun test"
31
31
  },
32
32
  "dependencies": {
33
- "@ultimat3/core": "1.0.0",
34
- "@ultimat3/entity": "1.0.0",
35
- "@ultimat3/schema": "1.0.0",
36
- "@ultimat3/time": "1.0.0"
33
+ "@ultimat3/core": "1.2.0",
34
+ "@ultimat3/entity": "1.2.0",
35
+ "@ultimat3/schema": "1.2.0",
36
+ "@ultimat3/time": "1.2.0"
37
37
  }
38
38
  }
package/src/worker.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  // deploy turns "at least once" into "always twice", so draining is on by default.
5
5
 
6
6
  import type { Clock, Ctx } from '@ultimat3/core';
7
- import { logger, onShutdown, uuid, withSpan } from '@ultimat3/core';
7
+ import { logger, onShutdown, recordQueueDepth, uuid, withSpan } from '@ultimat3/core';
8
8
  import { nowMs } from './clock';
9
9
  import type { ClaimedJob, JobDriver, QueueStats } from './driver';
10
10
  import { DEFAULT_QUEUE, DEFAULT_VISIBILITY_TIMEOUT_MS } from './driver';
@@ -18,6 +18,14 @@ import { nextRetry } from './retry';
18
18
  import type { EventLookup, StepRecord } from './steps';
19
19
  import { createStepRunner, isStepSuspension } from './steps';
20
20
 
21
+ /**
22
+ * How often the claim loop republishes `queue_depth`. Its own interval, not `pollIntervalMs`:
23
+ * `driver.stats()` is an aggregate over the whole jobs table and a scrape reads the gauge every
24
+ * ~15s, so publishing at the poll rate would multiply the queue's read load by sixty to write the
25
+ * same number sixty times.
26
+ */
27
+ const QUEUE_DEPTH_INTERVAL_MS = 15_000;
28
+
21
29
  export type JobOutcome = 'completed' | 'suspended' | 'retried' | 'dead-lettered';
22
30
 
23
31
  export interface JobExecution {
@@ -205,6 +213,29 @@ export function createWorker(options: WorkerOptions): Worker {
205
213
  let failed = 0;
206
214
  let suspended = 0;
207
215
  let deadLettered = 0;
216
+ let depthPublishedAt = Number.NEGATIVE_INFINITY;
217
+
218
+ /**
219
+ * This package's ONE metrics call site: the `queue_depth` series `docker/helm`'s worker HPA
220
+ * scales on. `ready` and not `ready + delayed` — the gauge means "waiting to be picked up", and
221
+ * a job parked until Tuesday is not backlog no matter how many workers are added. Every queue
222
+ * the driver reports, not only the ones this process serves, because depth is the queue's fact
223
+ * and a queue no pod published is a queue no autoscaler can see.
224
+ */
225
+ const publishQueueDepth = async (): Promise<void> => {
226
+ const now = nowMs(options.clock);
227
+ if (now - depthPublishedAt < QUEUE_DEPTH_INTERVAL_MS) return;
228
+ depthPublishedAt = now;
229
+ try {
230
+ for (const stat of await options.driver.stats()) recordQueueDepth(stat.queue, stat.ready);
231
+ } catch (error) {
232
+ // Instrumentation never costs a tick: a queue that cannot be measured must still be worked.
233
+ logger.warn('jobs.worker.depth-failed', {
234
+ workerId,
235
+ error: error instanceof Error ? error.message : String(error),
236
+ });
237
+ }
238
+ };
208
239
 
209
240
  const runClaimed = async (claimed: ClaimedJob): Promise<JobExecution> => {
210
241
  const handle = getJob(claimed.name);
@@ -249,6 +280,7 @@ export function createWorker(options: WorkerOptions): Worker {
249
280
 
250
281
  const tick = async (): Promise<readonly JobExecution[]> => {
251
282
  if (state === 'draining' || state === 'stopped') return [];
283
+ await publishQueueDepth();
252
284
  const results: JobExecution[] = [];
253
285
 
254
286
  for (const queue of queues) {
package/src/clock.d.ts DELETED
@@ -1,7 +0,0 @@
1
- import type { Clock } from '@ultimat3/core';
2
- export type DurationInput = string | number;
3
- /** A Clock may hand back a `Date` or epoch ms; scheduling needs one comparable number. */
4
- export declare function nowMs(clock?: Clock): number;
5
- /** `'3d'` | `'30s'` | `1500` -> ms. Numbers pass through so callers may stay explicit. */
6
- export declare function toMs(duration: DurationInput): number;
7
- //# sourceMappingURL=clock.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["clock.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAI5C,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,0FAA0F;AAC1F,wBAAgB,KAAK,CAAC,KAAK,GAAE,KAAmB,GAAG,MAAM,CAIxD;AAED,0FAA0F;AAC1F,wBAAgB,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAKpD"}
package/src/clock.js DELETED
@@ -1,21 +0,0 @@
1
- // Internal: every scheduling decision in this package is made in epoch ms against an
2
- // injected Clock, so tests never sleep and a frozen clock cannot be bypassed.
3
- import { systemClock } from '@ultimat3/core';
4
- import { parseDuration } from '@ultimat3/time';
5
- /** A Clock may hand back a `Date` or epoch ms; scheduling needs one comparable number. */
6
- export function nowMs(clock = systemClock) {
7
- const reading = clock.now();
8
- if (reading instanceof Date)
9
- return reading.getTime();
10
- return Number(reading);
11
- }
12
- /** `'3d'` | `'30s'` | `1500` -> ms. Numbers pass through so callers may stay explicit. */
13
- export function toMs(duration) {
14
- if (typeof duration === 'number')
15
- return duration;
16
- const parsed = parseDuration(duration);
17
- if (parsed instanceof Date)
18
- return parsed.getTime();
19
- return Number(parsed);
20
- }
21
- //# sourceMappingURL=clock.js.map
package/src/clock.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"clock.js","sourceRoot":"","sources":["clock.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,8EAA8E;AAG9E,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAI/C,0FAA0F;AAC1F,MAAM,UAAU,KAAK,CAAC,KAAK,GAAU,WAAW;IAC9C,MAAM,OAAO,GAAY,KAAK,CAAC,GAAG,EAAE,CAAC;IACrC,IAAI,OAAO,YAAY,IAAI;QAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IACtD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,IAAI,CAAC,QAAuB;IAC1C,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAClD,MAAM,MAAM,GAAY,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,MAAM,YAAY,IAAI;QAAE,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC;IACpD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC"}
@@ -1,9 +0,0 @@
1
- import type { Clock } from '@ultimat3/core';
2
- import type { JobDriver } from './driver';
3
- import type { StepStore } from './steps';
4
- export interface MemoryDriverOptions {
5
- readonly clock?: Clock;
6
- readonly steps?: StepStore;
7
- }
8
- export declare function createMemoryDriver(options?: MemoryDriverOptions): JobDriver;
9
- //# sourceMappingURL=driver-memory.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"driver-memory.d.ts","sourceRoot":"","sources":["driver-memory.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAG5C,OAAO,KAAK,EAKV,SAAS,EAMV,MAAM,UAAU,CAAC;AAGlB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGzC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;CAC5B;AAID,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,SAAS,CAwL/E"}
@@ -1,189 +0,0 @@
1
- // In-process driver for `x dev` and tests: same semantics as pg (visibility timeout,
2
- // idempotency dedupe, dead-letter) with zero infrastructure, so a test suite exercises the
3
- // real claim/ack/nack paths rather than a mock that always succeeds.
4
- import { assert, systemClock, uuid } from '@ultimat3/core';
5
- import { nowMs } from './clock';
6
- import { DEFAULT_QUEUE } from './driver';
7
- import { JobDuplicateError } from './errors';
8
- import { createMemoryStepStore } from './steps';
9
- const LIVE_STATES = new Set(['ready', 'delayed', 'running', 'suspended']);
10
- export function createMemoryDriver(options = {}) {
11
- const clock = options.clock ?? systemClock;
12
- const steps = options.steps ?? createMemoryStepStore();
13
- const jobs = new Map();
14
- const liveByKey = (key) => {
15
- for (const record of jobs.values()) {
16
- if (record.idempotencyKey === key && LIVE_STATES.has(record.state))
17
- return record;
18
- }
19
- return undefined;
20
- };
21
- const update = (id, patch) => {
22
- const existing = jobs.get(id);
23
- if (existing === undefined)
24
- return;
25
- jobs.set(id, { ...existing, ...patch, updatedAt: nowMs(clock) });
26
- };
27
- const introspect = {
28
- job(jobId) {
29
- return Promise.resolve(jobs.get(jobId));
30
- },
31
- list(filter = {}) {
32
- const rows = [...jobs.values()]
33
- .filter((record) => filter.queue === undefined || record.queue === filter.queue)
34
- .filter((record) => filter.name === undefined || record.name === filter.name)
35
- .filter((record) => filter.state === undefined || record.state === filter.state)
36
- .sort((a, b) => a.createdAt - b.createdAt)
37
- .slice(0, filter.limit ?? 100);
38
- return Promise.resolve(rows);
39
- },
40
- deadLetters(limit = 100) {
41
- const rows = [...jobs.values()]
42
- .filter((record) => record.state === 'dead')
43
- .sort((a, b) => b.updatedAt - a.updatedAt)
44
- .slice(0, limit);
45
- return Promise.resolve(rows);
46
- },
47
- async requeue(jobId, requeueOptions) {
48
- const existing = jobs.get(jobId);
49
- assert(existing !== undefined, `no job ${jobId} in the memory driver`, 'requeue a job id returned by enqueue() or stats() — the memory driver holds no state across processes, so an id from another run will not resolve');
50
- const record = existing;
51
- if (requeueOptions?.fromStep !== undefined) {
52
- // Drop the target step so it re-executes; earlier steps stay memoized.
53
- await steps.del(record.runId, requeueOptions.fromStep);
54
- }
55
- update(jobId, { state: 'ready', attempt: 0, runAt: nowMs(clock) });
56
- const next = jobs.get(jobId);
57
- return next ?? record;
58
- },
59
- };
60
- return {
61
- name: 'memory',
62
- steps,
63
- introspect,
64
- enqueue(request) {
65
- const existing = liveByKey(request.idempotencyKey);
66
- if (existing !== undefined) {
67
- if (request.onConflict === 'error') {
68
- throw new JobDuplicateError({
69
- job: request.name,
70
- idempotencyKey: request.idempotencyKey,
71
- existingId: existing.id,
72
- });
73
- }
74
- return Promise.resolve({ id: existing.id, runId: existing.runId, deduped: true });
75
- }
76
- const at = nowMs(clock);
77
- const runAt = request.runAt ?? at;
78
- const record = {
79
- id: uuid(),
80
- name: request.name,
81
- queue: request.queue || DEFAULT_QUEUE,
82
- input: request.input,
83
- idempotencyKey: request.idempotencyKey,
84
- runId: request.runId ?? uuid(),
85
- attempt: 0,
86
- maxAttempts: request.maxAttempts,
87
- state: runAt > at ? 'delayed' : 'ready',
88
- runAt,
89
- createdAt: at,
90
- updatedAt: at,
91
- ...(request.tenantId === undefined ? {} : { tenantId: request.tenantId }),
92
- };
93
- jobs.set(record.id, record);
94
- return Promise.resolve({ id: record.id, runId: record.runId, deduped: false });
95
- },
96
- claim(claimOptions) {
97
- const at = nowMs(clock);
98
- const wanted = new Set(claimOptions.queues);
99
- const claimable = [...jobs.values()]
100
- .filter((record) => wanted.size === 0 || wanted.has(record.queue))
101
- .filter((record) => {
102
- if (record.runAt > at)
103
- return false;
104
- if (record.state === 'ready' || record.state === 'delayed')
105
- return true;
106
- if (record.state === 'suspended')
107
- return true;
108
- // Lease expiry: a worker that died without ack releases its job here.
109
- return record.state === 'running' && (record.visibleAt ?? 0) <= at;
110
- })
111
- .sort((a, b) => a.runAt - b.runAt)
112
- .slice(0, claimOptions.limit);
113
- const out = [];
114
- for (const record of claimable) {
115
- const claimed = {
116
- ...record,
117
- state: 'running',
118
- attempt: record.attempt + 1,
119
- claimedBy: claimOptions.workerId,
120
- claimedAt: at,
121
- visibleAt: at + claimOptions.visibilityTimeoutMs,
122
- updatedAt: at,
123
- };
124
- jobs.set(record.id, claimed);
125
- out.push(claimed);
126
- }
127
- return Promise.resolve(out);
128
- },
129
- ack(jobId) {
130
- update(jobId, { state: 'done' });
131
- return Promise.resolve();
132
- },
133
- nack(jobId, nackOptions) {
134
- const record = jobs.get(jobId);
135
- if (record === undefined)
136
- return Promise.resolve();
137
- const at = nowMs(clock);
138
- const counts = nackOptions.countsAsAttempt !== false;
139
- const patch = {
140
- state: nackOptions.deadLetter === true ? 'dead' : counts ? 'ready' : 'suspended',
141
- runAt: at + nackOptions.delayMs,
142
- // A suspension must not burn an attempt, or a 3-day sleep dead-letters the run.
143
- attempt: counts ? record.attempt : record.attempt - 1,
144
- ...(nackOptions.error === undefined ? {} : { lastError: nackOptions.error }),
145
- };
146
- update(jobId, patch);
147
- return Promise.resolve();
148
- },
149
- heartbeat(jobId, heartbeatOptions) {
150
- update(jobId, { visibleAt: nowMs(clock) + heartbeatOptions.visibilityTimeoutMs });
151
- return Promise.resolve();
152
- },
153
- stats() {
154
- const at = nowMs(clock);
155
- const byQueue = new Map();
156
- for (const record of jobs.values()) {
157
- const current = byQueue.get(record.queue) ?? {
158
- queue: record.queue,
159
- ready: 0,
160
- delayed: 0,
161
- running: 0,
162
- suspended: 0,
163
- dead: 0,
164
- oldestReadyMs: 0,
165
- };
166
- const next = { ...current };
167
- if (record.state === 'ready' && record.runAt <= at) {
168
- next.ready += 1;
169
- next.oldestReadyMs = Math.max(next.oldestReadyMs, at - record.runAt);
170
- }
171
- else if (record.state === 'ready' || record.state === 'delayed')
172
- next.delayed += 1;
173
- else if (record.state === 'running')
174
- next.running += 1;
175
- else if (record.state === 'suspended')
176
- next.suspended += 1;
177
- else if (record.state === 'dead')
178
- next.dead += 1;
179
- byQueue.set(record.queue, next);
180
- }
181
- return Promise.resolve([...byQueue.values()].sort((a, b) => a.queue.localeCompare(b.queue)));
182
- },
183
- close() {
184
- jobs.clear();
185
- return Promise.resolve();
186
- },
187
- };
188
- }
189
- //# sourceMappingURL=driver-memory.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"driver-memory.js","sourceRoot":"","sources":["driver-memory.ts"],"names":[],"mappings":"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"}
@@ -1,7 +0,0 @@
1
- import type { JobDriver } from './driver';
2
- export interface NatsDriverOptions {
3
- readonly servers?: readonly string[];
4
- readonly streamPrefix?: string;
5
- }
6
- export declare function createNatsDriver(_options?: NatsDriverOptions): JobDriver;
7
- //# sourceMappingURL=driver-nats.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"driver-nats.d.ts","sourceRoot":"","sources":["driver-nats.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAKV,SAAS,EAGV,MAAM,UAAU,CAAC;AA4BlB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,GAAE,iBAAsB,GAAG,SAAS,CAuB5E"}
@@ -1,51 +0,0 @@
1
- // NATS driver — interface-complete, not implemented. The intended mapping, so the eventual
2
- // implementation has no design decisions left: a JetStream work-queue stream per job queue,
3
- // a durable pull consumer per worker pool (`fetch` == claim, `ack`/`nak` map 1:1),
4
- // `ack_wait` as the visibility timeout, and a KV bucket for step records.
5
- import { JobsNotImplementedError } from './errors';
6
- const FIX = 'use driver: "pg" (default) or "memory" — see docs/jobs/drivers.md#nats';
7
- const unavailable = (method) => {
8
- throw new JobsNotImplementedError({ feature: `nats jobs driver (${method})`, fix: FIX });
9
- };
10
- const natsStepStore = () => ({
11
- get(_runId, _name) {
12
- return unavailable('steps.get');
13
- },
14
- put(_record) {
15
- return unavailable('steps.put');
16
- },
17
- list(_runId) {
18
- return unavailable('steps.list');
19
- },
20
- del(_runId, _name) {
21
- return unavailable('steps.del');
22
- },
23
- clear(_runId) {
24
- return unavailable('steps.clear');
25
- },
26
- });
27
- export function createNatsDriver(_options = {}) {
28
- return {
29
- name: 'nats',
30
- steps: natsStepStore(),
31
- enqueue(_request) {
32
- return unavailable('enqueue');
33
- },
34
- claim(_options) {
35
- return unavailable('claim');
36
- },
37
- ack(_jobId) {
38
- return unavailable('ack');
39
- },
40
- nack(_jobId, _options) {
41
- return unavailable('nack');
42
- },
43
- heartbeat(_jobId, _options) {
44
- return unavailable('heartbeat');
45
- },
46
- stats() {
47
- return unavailable('stats');
48
- },
49
- };
50
- }
51
- //# sourceMappingURL=driver-nats.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"driver-nats.js","sourceRoot":"","sources":["driver-nats.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,4FAA4F;AAC5F,mFAAmF;AACnF,0EAA0E;AAW1E,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAGnD,MAAM,GAAG,GAAG,wEAAwE,CAAC;AAErF,MAAM,WAAW,GAAG,CAAC,MAAc,EAAS,EAAE;IAC5C,MAAM,IAAI,uBAAuB,CAAC,EAAE,OAAO,EAAE,qBAAqB,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;AAC3F,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,GAAc,EAAE,CAAC,CAAC;IACtC,GAAG,CAAC,MAAc,EAAE,KAAa;QAC/B,OAAO,WAAW,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;IACD,GAAG,CAAC,OAAmB;QACrB,OAAO,WAAW,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;IACD,IAAI,CAAC,MAAc;QACjB,OAAO,WAAW,CAAC,YAAY,CAAC,CAAC;IACnC,CAAC;IACD,GAAG,CAAC,MAAc,EAAE,KAAa;QAC/B,OAAO,WAAW,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;IACD,KAAK,CAAC,MAAc;QAClB,OAAO,WAAW,CAAC,aAAa,CAAC,CAAC;IACpC,CAAC;CACF,CAAC,CAAC;AAOH,MAAM,UAAU,gBAAgB,CAAC,QAAQ,GAAsB,EAAE;IAC/D,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,aAAa,EAAE;QACtB,OAAO,CAAC,QAAwB;YAC9B,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;QAChC,CAAC;QACD,KAAK,CAAC,QAAsB;YAC1B,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;QACD,GAAG,CAAC,MAAc;YAChB,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,MAAc,EAAE,QAAqB;YACxC,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC;QACD,SAAS,CAAC,MAAc,EAAE,QAAkD;YAC1E,OAAO,WAAW,CAAC,WAAW,CAAC,CAAC;QAClC,CAAC;QACD,KAAK;YACH,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1,19 +0,0 @@
1
- export declare const SQL_JOBS_TABLE: string;
2
- export declare const SQL_ENQUEUE: string;
3
- export declare const SQL_FIND_LIVE_BY_KEY: string;
4
- /**
5
- * The claim. SKIP LOCKED is the whole design: without it, worker 2 blocks on worker 1's row
6
- * lock and throughput collapses to one worker. `visible_at` in the predicate reclaims leases
7
- * abandoned by a crashed worker.
8
- */
9
- export declare const SQL_CLAIM: string;
10
- export declare const SQL_ACK: string;
11
- export declare const SQL_NACK: string;
12
- export declare const SQL_HEARTBEAT: string;
13
- export declare const SQL_STATS: string;
14
- /** Scheduler leader election. Session-scoped, so a crashed node's lock releases itself. */
15
- export declare const SQL_TRY_ADVISORY_LOCK = "select pg_try_advisory_lock($1) as locked";
16
- export declare const SQL_ADVISORY_UNLOCK = "select pg_advisory_unlock($1) as unlocked";
17
- export declare const SQL_STEP_GET: string;
18
- export declare const SQL_STEP_PUT: string;
19
- //# sourceMappingURL=driver-pg-sql.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"driver-pg-sql.d.ts","sourceRoot":"","sources":["driver-pg-sql.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,cAAc,QA4CnB,CAAC;AAET,eAAO,MAAM,WAAW,QAWhB,CAAC;AAET,eAAO,MAAM,oBAAoB,QAKzB,CAAC;AAET;;;;GAIG;AACH,eAAO,MAAM,SAAS,QA4Bd,CAAC;AAET,eAAO,MAAM,OAAO,QAIZ,CAAC;AAET,eAAO,MAAM,QAAQ,QAUb,CAAC;AAET,eAAO,MAAM,aAAa,QAIlB,CAAC;AAET,eAAO,MAAM,SAAS,QAYd,CAAC;AAET,2FAA2F;AAC3F,eAAO,MAAM,qBAAqB,8CAA8C,CAAC;AACjF,eAAO,MAAM,mBAAmB,8CAA8C,CAAC;AAE/E,eAAO,MAAM,YAAY,QAOjB,CAAC;AAET,eAAO,MAAM,YAAY,QAYjB,CAAC"}
@@ -1,160 +0,0 @@
1
- // Every statement the Postgres driver runs, spelled out as template strings on purpose: an
2
- // agent debugging a stuck queue should be able to read, run and correct the exact statement
3
- // it saw in a log, without reassembling it from a builder. Kept beside the driver rather than
4
- // inside it so the driver file stays the control flow and this one stays the wire format.
5
- export const SQL_JOBS_TABLE = `
6
- create table if not exists x_jobs (
7
- id uuid primary key,
8
- name text not null,
9
- queue text not null default 'default',
10
- input jsonb not null,
11
- idempotency_key text not null,
12
- run_id uuid not null,
13
- attempt int not null default 0,
14
- max_attempts int not null default 3,
15
- state text not null default 'ready',
16
- run_at timestamptz not null default now(),
17
- visible_at timestamptz,
18
- claimed_by text,
19
- last_error text,
20
- tenant_id text,
21
- created_at timestamptz not null default now(),
22
- updated_at timestamptz not null default now()
23
- );
24
-
25
- -- Partial unique index: one LIVE job per idempotency key. Completed rows stay for history,
26
- -- so re-running the same work tomorrow is allowed and re-delivering it today is not.
27
- create unique index if not exists x_jobs_idempotency_live_idx
28
- on x_jobs (idempotency_key)
29
- where state in ('ready', 'delayed', 'running', 'suspended');
30
-
31
- create index if not exists x_jobs_claim_idx
32
- on x_jobs (queue, run_at)
33
- where state in ('ready', 'delayed', 'suspended');
34
-
35
- create table if not exists x_job_steps (
36
- run_id uuid not null,
37
- name text not null,
38
- status text not null,
39
- output jsonb,
40
- started_at timestamptz not null default now(),
41
- completed_at timestamptz,
42
- wake_at timestamptz,
43
- event text,
44
- correlation_key text,
45
- attempts int not null default 1,
46
- error text,
47
- primary key (run_id, name)
48
- );
49
- `.trim();
50
- export const SQL_ENQUEUE = `
51
- insert into x_jobs
52
- (id, name, queue, input, idempotency_key, run_id, max_attempts, state, run_at, tenant_id)
53
- values
54
- ($1, $2, $3, $4::jsonb, $5, $6, $7,
55
- case when to_timestamp($8 / 1000.0) > now() then 'delayed' else 'ready' end,
56
- to_timestamp($8 / 1000.0), $9)
57
- on conflict (idempotency_key)
58
- where state in ('ready', 'delayed', 'running', 'suspended')
59
- do nothing
60
- returning id, run_id
61
- `.trim();
62
- export const SQL_FIND_LIVE_BY_KEY = `
63
- select id, run_id from x_jobs
64
- where idempotency_key = $1
65
- and state in ('ready', 'delayed', 'running', 'suspended')
66
- limit 1
67
- `.trim();
68
- /**
69
- * The claim. SKIP LOCKED is the whole design: without it, worker 2 blocks on worker 1's row
70
- * lock and throughput collapses to one worker. `visible_at` in the predicate reclaims leases
71
- * abandoned by a crashed worker.
72
- */
73
- export const SQL_CLAIM = `
74
- with claimed as (
75
- select id
76
- from x_jobs
77
- where queue = any($1::text[])
78
- and run_at <= now()
79
- and (
80
- state in ('ready', 'delayed', 'suspended')
81
- or (state = 'running' and visible_at <= now())
82
- )
83
- order by run_at
84
- limit $2
85
- for update skip locked
86
- )
87
- update x_jobs j
88
- set state = 'running',
89
- attempt = j.attempt + 1,
90
- claimed_by = $3,
91
- visible_at = now() + ($4::bigint * interval '1 millisecond'),
92
- updated_at = now()
93
- from claimed c
94
- where j.id = c.id
95
- returning j.id, j.name, j.queue, j.input, j.idempotency_key, j.run_id, j.attempt,
96
- j.max_attempts, j.state, j.tenant_id, j.last_error, j.claimed_by,
97
- (extract(epoch from j.run_at) * 1000)::bigint as run_at,
98
- (extract(epoch from j.visible_at) * 1000)::bigint as visible_at,
99
- (extract(epoch from j.created_at) * 1000)::bigint as created_at,
100
- (extract(epoch from j.updated_at) * 1000)::bigint as updated_at
101
- `.trim();
102
- export const SQL_ACK = `
103
- update x_jobs
104
- set state = 'done', visible_at = null, claimed_by = null, updated_at = now()
105
- where id = $1
106
- `.trim();
107
- export const SQL_NACK = `
108
- update x_jobs
109
- set state = $2,
110
- attempt = case when $3::boolean then attempt else greatest(attempt - 1, 0) end,
111
- run_at = now() + ($4::bigint * interval '1 millisecond'),
112
- visible_at = null,
113
- claimed_by = null,
114
- last_error = coalesce($5, last_error),
115
- updated_at = now()
116
- where id = $1
117
- `.trim();
118
- export const SQL_HEARTBEAT = `
119
- update x_jobs
120
- set visible_at = now() + ($2::bigint * interval '1 millisecond'), updated_at = now()
121
- where id = $1 and state = 'running'
122
- `.trim();
123
- export const SQL_STATS = `
124
- select queue,
125
- count(*) filter (where state = 'ready' and run_at <= now()) as ready,
126
- count(*) filter (where state = 'delayed' or run_at > now()) as delayed,
127
- count(*) filter (where state = 'running') as running,
128
- count(*) filter (where state = 'suspended') as suspended,
129
- count(*) filter (where state = 'dead') as dead,
130
- coalesce(max(extract(epoch from now() - run_at)) filter
131
- (where state = 'ready' and run_at <= now()), 0) * 1000 as oldest_ready_ms
132
- from x_jobs
133
- group by queue
134
- order by queue
135
- `.trim();
136
- /** Scheduler leader election. Session-scoped, so a crashed node's lock releases itself. */
137
- export const SQL_TRY_ADVISORY_LOCK = 'select pg_try_advisory_lock($1) as locked';
138
- export const SQL_ADVISORY_UNLOCK = 'select pg_advisory_unlock($1) as unlocked';
139
- export const SQL_STEP_GET = `
140
- select run_id, name, status, output, attempts, error,
141
- (extract(epoch from started_at) * 1000)::bigint as started_at,
142
- (extract(epoch from completed_at) * 1000)::bigint as completed_at,
143
- (extract(epoch from wake_at) * 1000)::bigint as wake_at,
144
- event, correlation_key
145
- from x_job_steps where run_id = $1 and name = $2
146
- `.trim();
147
- export const SQL_STEP_PUT = `
148
- insert into x_job_steps
149
- (run_id, name, status, output, started_at, completed_at, wake_at, event,
150
- correlation_key, attempts, error)
151
- values ($1, $2, $3, $4::jsonb, to_timestamp($5 / 1000.0),
152
- case when $6::bigint is null then null else to_timestamp($6 / 1000.0) end,
153
- case when $7::bigint is null then null else to_timestamp($7 / 1000.0) end,
154
- $8, $9, $10, $11)
155
- on conflict (run_id, name) do update
156
- set status = excluded.status, output = excluded.output,
157
- completed_at = excluded.completed_at, wake_at = excluded.wake_at,
158
- attempts = excluded.attempts, error = excluded.error
159
- `.trim();
160
- //# sourceMappingURL=driver-pg-sql.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"driver-pg-sql.js","sourceRoot":"","sources":["driver-pg-sql.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,4FAA4F;AAC5F,8FAA8F;AAC9F,0FAA0F;AAE1F,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4C7B,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;CAW1B,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;CAKnC,CAAC,IAAI,EAAE,CAAC;AAET;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BxB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,OAAO,GAAG;;;;CAItB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;CAUvB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,aAAa,GAAG;;;;CAI5B,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;CAYxB,CAAC,IAAI,EAAE,CAAC;AAET,2FAA2F;AAC3F,MAAM,CAAC,MAAM,qBAAqB,GAAG,2CAA2C,CAAC;AACjF,MAAM,CAAC,MAAM,mBAAmB,GAAG,2CAA2C,CAAC;AAE/E,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;CAO3B,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;CAY3B,CAAC,IAAI,EAAE,CAAC"}
@@ -1,17 +0,0 @@
1
- import type { Clock } from '@ultimat3/core';
2
- import type { JobDriver } from './driver';
3
- /** The one thing this driver needs from the DB layer. Satisfied by `Bun.sql` and by a Tx. */
4
- export interface PgExecutor {
5
- query<R>(sql: string, params: readonly unknown[]): Promise<readonly R[]>;
6
- }
7
- export interface PgDriverOptions {
8
- readonly executor?: PgExecutor;
9
- readonly clock?: Clock;
10
- }
11
- export declare function createPgDriver(options?: PgDriverOptions): JobDriver;
12
- /** Advisory-lock leader election, used by the `scheduler` role. */
13
- export declare function createPgLeader(lockKey: number, options?: PgDriverOptions): {
14
- acquire(): Promise<boolean>;
15
- release(): Promise<void>;
16
- };
17
- //# sourceMappingURL=driver-pg.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"driver-pg.d.ts","sourceRoot":"","sources":["driver-pg.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAG5C,OAAO,KAAK,EAKV,SAAS,EAMV,MAAM,UAAU,CAAC;AAkBlB,6FAA6F;AAC7F,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;CAC1E;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACxB;AAgID,wBAAgB,cAAc,CAAC,OAAO,GAAE,eAAoB,GAAG,SAAS,CAqKvE;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,eAAoB,GAC5B;IAAE,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAW3D"}