@ultimat3/jobs 1.0.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 (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/package.json +38 -0
  4. package/src/clock.d.ts +7 -0
  5. package/src/clock.d.ts.map +1 -0
  6. package/src/clock.js +21 -0
  7. package/src/clock.js.map +1 -0
  8. package/src/clock.ts +23 -0
  9. package/src/describe.ts +61 -0
  10. package/src/driver-memory.d.ts +9 -0
  11. package/src/driver-memory.d.ts.map +1 -0
  12. package/src/driver-memory.js +189 -0
  13. package/src/driver-memory.js.map +1 -0
  14. package/src/driver-memory.ts +216 -0
  15. package/src/driver-nats.d.ts +7 -0
  16. package/src/driver-nats.d.ts.map +1 -0
  17. package/src/driver-nats.js +51 -0
  18. package/src/driver-nats.js.map +1 -0
  19. package/src/driver-nats.ts +73 -0
  20. package/src/driver-pg-sql.d.ts +19 -0
  21. package/src/driver-pg-sql.d.ts.map +1 -0
  22. package/src/driver-pg-sql.js +160 -0
  23. package/src/driver-pg-sql.js.map +1 -0
  24. package/src/driver-pg-sql.ts +170 -0
  25. package/src/driver-pg.d.ts +17 -0
  26. package/src/driver-pg.d.ts.map +1 -0
  27. package/src/driver-pg.js +246 -0
  28. package/src/driver-pg.js.map +1 -0
  29. package/src/driver-pg.ts +356 -0
  30. package/src/driver-redis.d.ts +7 -0
  31. package/src/driver-redis.d.ts.map +1 -0
  32. package/src/driver-redis.js +54 -0
  33. package/src/driver-redis.js.map +1 -0
  34. package/src/driver-redis.ts +76 -0
  35. package/src/driver.d.ts +114 -0
  36. package/src/driver.d.ts.map +1 -0
  37. package/src/driver.js +14 -0
  38. package/src/driver.js.map +1 -0
  39. package/src/driver.ts +143 -0
  40. package/src/errors.d.ts +63 -0
  41. package/src/errors.d.ts.map +1 -0
  42. package/src/errors.js +105 -0
  43. package/src/errors.js.map +1 -0
  44. package/src/errors.ts +165 -0
  45. package/src/events.d.ts +35 -0
  46. package/src/events.d.ts.map +1 -0
  47. package/src/events.js +92 -0
  48. package/src/events.js.map +1 -0
  49. package/src/events.ts +134 -0
  50. package/src/index.d.ts +32 -0
  51. package/src/index.d.ts.map +1 -0
  52. package/src/index.js +18 -0
  53. package/src/index.js.map +1 -0
  54. package/src/index.ts +172 -0
  55. package/src/inspect.d.ts +82 -0
  56. package/src/inspect.d.ts.map +1 -0
  57. package/src/inspect.js +113 -0
  58. package/src/inspect.js.map +1 -0
  59. package/src/inspect.ts +213 -0
  60. package/src/job.d.ts +71 -0
  61. package/src/job.d.ts.map +1 -0
  62. package/src/job.js +99 -0
  63. package/src/job.js.map +1 -0
  64. package/src/job.ts +261 -0
  65. package/src/limits.d.ts +47 -0
  66. package/src/limits.d.ts.map +1 -0
  67. package/src/limits.js +0 -0
  68. package/src/limits.js.map +1 -0
  69. package/src/limits.ts +0 -0
  70. package/src/outbox.d.ts +81 -0
  71. package/src/outbox.d.ts.map +1 -0
  72. package/src/outbox.js +202 -0
  73. package/src/outbox.js.map +1 -0
  74. package/src/outbox.ts +336 -0
  75. package/src/register.ts +40 -0
  76. package/src/retry.d.ts +40 -0
  77. package/src/retry.d.ts.map +1 -0
  78. package/src/retry.js +59 -0
  79. package/src/retry.js.map +1 -0
  80. package/src/retry.ts +90 -0
  81. package/src/scheduler.d.ts +79 -0
  82. package/src/scheduler.d.ts.map +1 -0
  83. package/src/scheduler.js +183 -0
  84. package/src/scheduler.js.map +1 -0
  85. package/src/scheduler.ts +417 -0
  86. package/src/steps.d.ts +86 -0
  87. package/src/steps.d.ts.map +1 -0
  88. package/src/steps.js +227 -0
  89. package/src/steps.js.map +1 -0
  90. package/src/steps.ts +339 -0
  91. package/src/worker.d.ts +68 -0
  92. package/src/worker.d.ts.map +1 -0
  93. package/src/worker.js +273 -0
  94. package/src/worker.js.map +1 -0
  95. package/src/worker.ts +356 -0
@@ -0,0 +1,47 @@
1
+ import type { Clock } from '@ultimat3/core';
2
+ export interface RateLimit {
3
+ readonly limit: number;
4
+ readonly windowMs: number;
5
+ }
6
+ export interface LimitConfig {
7
+ /** Max concurrent runs per tenant, across every queue. */
8
+ readonly perTenant?: number;
9
+ /** Max concurrent runs per queue, across every tenant. */
10
+ readonly perQueue?: number;
11
+ /** Fleet-wide ceiling for this worker process. */
12
+ readonly global?: number;
13
+ /** Starts per tenant per window — protects downstream APIs, not just CPU. */
14
+ readonly ratePerTenant?: RateLimit;
15
+ }
16
+ export interface LimitKey {
17
+ readonly queue: string;
18
+ readonly tenantId?: string;
19
+ }
20
+ /** Returned on success; `release()` is idempotent so a double-release cannot leak slots. */
21
+ export interface Lease {
22
+ readonly queue: string;
23
+ readonly tenantId: string;
24
+ release(): void;
25
+ }
26
+ export type LimitReason = 'per-tenant' | 'per-queue' | 'global' | 'rate';
27
+ export interface LimitSnapshot {
28
+ readonly global: number;
29
+ readonly byQueue: Readonly<Record<string, number>>;
30
+ readonly byTenant: Readonly<Record<string, number>>;
31
+ readonly config: LimitConfig;
32
+ }
33
+ export interface Limiter {
34
+ /** `undefined` means "over a limit" — the claim loop leaves the job for another worker. */
35
+ tryAcquire(key: LimitKey): Lease | undefined;
36
+ /** Which limit blocked the last refusal for this key, for `/_x` and logs. */
37
+ blockedBy(key: LimitKey): LimitReason | undefined;
38
+ inFlight(key?: LimitKey): number;
39
+ snapshot(): LimitSnapshot;
40
+ }
41
+ export declare const NO_TENANT = "global";
42
+ /** Tenant key resolution. Structural on purpose: `jobs` must not import the auth types. */
43
+ export declare function tenantKeyFrom(actor: {
44
+ readonly orgId?: string;
45
+ } | undefined): string;
46
+ export declare function createLimiter(config: LimitConfig, clock?: Clock): Limiter;
47
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["limits.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAI5C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,kDAAkD;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;CACpC;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,4FAA4F;AAC5F,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEzE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,WAAW,OAAO;IACtB,2FAA2F;IAC3F,UAAU,CAAC,GAAG,EAAE,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;IAC7C,6EAA6E;IAC7E,SAAS,CAAC,GAAG,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;IAClD,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,IAAI,aAAa,CAAC;CAC3B;AAED,eAAO,MAAM,SAAS,WAAW,CAAC;AAElC,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,KAAK,EAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAAG,MAAM,CAEpF;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,GAAE,KAAmB,GAAG,OAAO,CAoFtF"}
package/src/limits.js ADDED
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["limits.ts"],"names":[],"mappings":"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"}
package/src/limits.ts ADDED
Binary file
@@ -0,0 +1,81 @@
1
+ import type { Clock } from '@ultimat3/core';
2
+ import type { Tx } from '@ultimat3/entity';
3
+ import type { EnqueueResult, JobDriver } from './driver';
4
+ import type { JobHandle } from './job';
5
+ export interface OutboxRecord {
6
+ readonly id: string;
7
+ readonly job: string;
8
+ readonly queue: string;
9
+ readonly input: unknown;
10
+ readonly idempotencyKey: string;
11
+ readonly maxAttempts: number;
12
+ readonly runAt: number;
13
+ readonly stagedAt: number;
14
+ readonly tenantId?: string;
15
+ readonly publishedAt?: number;
16
+ }
17
+ export interface OutboxStore {
18
+ /** Write inside `tx`. Nothing is visible to the relay until that tx commits. */
19
+ stage(tx: Tx, record: OutboxRecord): Promise<void>;
20
+ /** Called by the tx runner after COMMIT. */
21
+ commit(tx: Tx): Promise<readonly OutboxRecord[]>;
22
+ /** Called by the tx runner after ROLLBACK. Staged rows vanish with the transaction. */
23
+ rollback(tx: Tx): Promise<void>;
24
+ /** Unpublished, committed rows — the relay's work queue. */
25
+ claim(limit: number): Promise<readonly OutboxRecord[]>;
26
+ markPublished(id: string, at: number): Promise<void>;
27
+ pendingCount(): Promise<number>;
28
+ }
29
+ /**
30
+ * Default store. Staged rows hang off the `Tx` object itself in a WeakMap, so the "same
31
+ * transaction" guarantee needs no cooperation from the DB layer and rollback is a delete.
32
+ * The pg store swaps this for a real `x_outbox` table written by the same connection.
33
+ */
34
+ export declare function createMemoryOutboxStore(): OutboxStore;
35
+ export interface EnqueueOptions {
36
+ /** Epoch ms, or a delay via `runAt: nowMs() + toMs('5m')`. */
37
+ readonly runAt?: number;
38
+ readonly tenantId?: string;
39
+ readonly queue?: string;
40
+ /** Escape hatch for enqueues that must fire regardless of the caller's transaction. */
41
+ readonly outbox?: boolean;
42
+ }
43
+ export interface OutboxDeps {
44
+ readonly store: OutboxStore;
45
+ readonly driver: JobDriver;
46
+ readonly clock?: Clock;
47
+ /** `'required'` fails an out-of-transaction enqueue instead of publishing it directly. */
48
+ readonly mode?: 'default' | 'required';
49
+ }
50
+ /** Stage a job inside `tx`. Returns the row that the relay will publish after commit. */
51
+ export declare function enqueueInTx<I>(deps: OutboxDeps, tx: Tx, handle: JobHandle<I>, input: I, options?: EnqueueOptions): Promise<OutboxRecord>;
52
+ export interface JobsFacade {
53
+ /**
54
+ * Joins the ambient transaction when there is one. Same call site in a request handler,
55
+ * a job, or a script — the transactional behaviour is the framework's problem, not yours.
56
+ */
57
+ enqueue<I>(handle: JobHandle<I>, input: I, options?: EnqueueOptions): Promise<EnqueueResult>;
58
+ }
59
+ export declare function createJobsFacade(deps: OutboxDeps, currentTx: () => Tx | undefined): JobsFacade;
60
+ export interface RelayOptions extends OutboxDeps {
61
+ readonly batchSize?: number;
62
+ readonly intervalMs?: number;
63
+ }
64
+ export interface OutboxRelay {
65
+ /** One pass. Returns how many rows were published. Call it directly in tests. */
66
+ tick(): Promise<number>;
67
+ start(): void;
68
+ stop(): void;
69
+ pending(): Promise<number>;
70
+ }
71
+ /**
72
+ * At-least-once by construction: publish, THEN mark published. A crash between the two
73
+ * re-publishes, which the idempotency key collapses — the opposite order would lose jobs.
74
+ */
75
+ export declare function createOutboxRelay(options: RelayOptions): OutboxRelay;
76
+ /** SQL for the pg-backed outbox. The relay publishes rows this INSERT created. */
77
+ export declare const SQL_OUTBOX_TABLE: string;
78
+ export declare const SQL_OUTBOX_STAGE: string;
79
+ export declare const SQL_OUTBOX_CLAIM: string;
80
+ export declare const SQL_OUTBOX_MARK_PUBLISHED: string;
81
+ //# sourceMappingURL=outbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox.d.ts","sourceRoot":"","sources":["outbox.ts"],"names":[],"mappings":"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"}
package/src/outbox.js ADDED
@@ -0,0 +1,202 @@
1
+ // The transactional outbox — ON BY DEFAULT, because the alternative is a bug you cannot see
2
+ // in review. `ctx.jobs.enqueue()` inside a request writes the job row in the SAME `tx` as the
3
+ // business rows and a relay publishes it after commit. Without it, every enqueue is a
4
+ // distributed-transaction coin flip:
5
+ //
6
+ // enqueue then rollback -> the job runs against rows that never existed
7
+ // commit then enqueue -> the process dies in between and the job is lost forever
8
+ //
9
+ // Both are load-dependent, both pass every test, and both are the top source of "the email
10
+ // went out but the order isn't in the database" tickets. Joining the transaction removes the
11
+ // window entirely; the relay's at-least-once delivery is deduped by the job's idempotencyKey.
12
+ import { logger, uuid } from '@ultimat3/core';
13
+ import { nowMs } from './clock';
14
+ import { DEFAULT_QUEUE } from './driver';
15
+ import { OutboxNoTxError } from './errors';
16
+ /**
17
+ * Default store. Staged rows hang off the `Tx` object itself in a WeakMap, so the "same
18
+ * transaction" guarantee needs no cooperation from the DB layer and rollback is a delete.
19
+ * The pg store swaps this for a real `x_outbox` table written by the same connection.
20
+ */
21
+ export function createMemoryOutboxStore() {
22
+ const staged = new WeakMap();
23
+ const committed = new Map();
24
+ const key = (tx) => tx;
25
+ return {
26
+ stage(tx, record) {
27
+ const bucket = staged.get(key(tx)) ?? [];
28
+ bucket.push(record);
29
+ staged.set(key(tx), bucket);
30
+ return Promise.resolve();
31
+ },
32
+ commit(tx) {
33
+ const bucket = staged.get(key(tx)) ?? [];
34
+ staged.delete(key(tx));
35
+ for (const record of bucket)
36
+ committed.set(record.id, record);
37
+ return Promise.resolve(bucket);
38
+ },
39
+ rollback(tx) {
40
+ staged.delete(key(tx));
41
+ return Promise.resolve();
42
+ },
43
+ claim(limit) {
44
+ const ready = [...committed.values()]
45
+ .filter((record) => record.publishedAt === undefined)
46
+ .sort((a, b) => a.stagedAt - b.stagedAt)
47
+ .slice(0, limit);
48
+ return Promise.resolve(ready);
49
+ },
50
+ markPublished(id, at) {
51
+ const record = committed.get(id);
52
+ if (record !== undefined)
53
+ committed.set(id, { ...record, publishedAt: at });
54
+ return Promise.resolve();
55
+ },
56
+ pendingCount() {
57
+ let count = 0;
58
+ for (const record of committed.values()) {
59
+ if (record.publishedAt === undefined)
60
+ count += 1;
61
+ }
62
+ return Promise.resolve(count);
63
+ },
64
+ };
65
+ }
66
+ /** Stage a job inside `tx`. Returns the row that the relay will publish after commit. */
67
+ export function enqueueInTx(deps, tx, handle, input, options = {}) {
68
+ const at = nowMs(deps.clock);
69
+ const record = {
70
+ id: uuid(),
71
+ job: handle.name,
72
+ queue: options.queue ?? handle.queue ?? DEFAULT_QUEUE,
73
+ input,
74
+ idempotencyKey: handle.idempotencyKeyFor(input),
75
+ maxAttempts: handle.retry.attempts,
76
+ runAt: options.runAt ?? at,
77
+ stagedAt: at,
78
+ ...(options.tenantId === undefined ? {} : { tenantId: options.tenantId }),
79
+ };
80
+ return deps.store.stage(tx, record).then(() => record);
81
+ }
82
+ const STAGED_RESULT = { id: '', runId: '', deduped: false };
83
+ export function createJobsFacade(deps, currentTx) {
84
+ return {
85
+ async enqueue(handle, input, options = {}) {
86
+ const tx = options.outbox === false ? undefined : currentTx();
87
+ if (tx === undefined) {
88
+ if (deps.mode === 'required' && options.outbox !== false) {
89
+ throw new OutboxNoTxError({ job: handle.name });
90
+ }
91
+ return deps.driver.enqueue({
92
+ name: handle.name,
93
+ queue: options.queue ?? handle.queue,
94
+ input,
95
+ idempotencyKey: handle.idempotencyKeyFor(input),
96
+ maxAttempts: handle.retry.attempts,
97
+ runAt: options.runAt ?? nowMs(deps.clock),
98
+ ...(options.tenantId === undefined ? {} : { tenantId: options.tenantId }),
99
+ });
100
+ }
101
+ const record = await enqueueInTx(deps, tx, handle, input, options);
102
+ // No queue id yet by design: the row does not exist until COMMIT.
103
+ return { ...STAGED_RESULT, id: record.id };
104
+ },
105
+ };
106
+ }
107
+ /**
108
+ * At-least-once by construction: publish, THEN mark published. A crash between the two
109
+ * re-publishes, which the idempotency key collapses — the opposite order would lose jobs.
110
+ */
111
+ export function createOutboxRelay(options) {
112
+ const batchSize = options.batchSize ?? 100;
113
+ const intervalMs = options.intervalMs ?? 200;
114
+ let timer;
115
+ let running = false;
116
+ const tick = async () => {
117
+ const batch = await options.store.claim(batchSize);
118
+ let published = 0;
119
+ for (const record of batch) {
120
+ try {
121
+ await options.driver.enqueue({
122
+ name: record.job,
123
+ queue: record.queue,
124
+ input: record.input,
125
+ idempotencyKey: record.idempotencyKey,
126
+ maxAttempts: record.maxAttempts,
127
+ runAt: record.runAt,
128
+ ...(record.tenantId === undefined ? {} : { tenantId: record.tenantId }),
129
+ });
130
+ await options.store.markPublished(record.id, nowMs(options.clock));
131
+ published += 1;
132
+ }
133
+ catch (error) {
134
+ // Leave the row unpublished; the next tick retries it. Order is preserved per queue.
135
+ logger.warn('jobs.outbox.publish-failed', {
136
+ job: record.job,
137
+ id: record.id,
138
+ error: error instanceof Error ? error.message : String(error),
139
+ });
140
+ }
141
+ }
142
+ return published;
143
+ };
144
+ return {
145
+ tick,
146
+ start() {
147
+ if (timer !== undefined)
148
+ return;
149
+ timer = setInterval(() => {
150
+ if (running)
151
+ return;
152
+ running = true;
153
+ void tick().finally(() => {
154
+ running = false;
155
+ });
156
+ }, intervalMs);
157
+ },
158
+ stop() {
159
+ if (timer !== undefined)
160
+ clearInterval(timer);
161
+ timer = undefined;
162
+ },
163
+ pending: () => options.store.pendingCount(),
164
+ };
165
+ }
166
+ /** SQL for the pg-backed outbox. The relay publishes rows this INSERT created. */
167
+ export const SQL_OUTBOX_TABLE = `
168
+ create table if not exists x_outbox (
169
+ id uuid primary key,
170
+ job text not null,
171
+ queue text not null default 'default',
172
+ input jsonb not null,
173
+ idempotency_key text not null,
174
+ max_attempts int not null default 3,
175
+ run_at timestamptz not null default now(),
176
+ staged_at timestamptz not null default now(),
177
+ tenant_id text,
178
+ published_at timestamptz
179
+ );
180
+ create index if not exists x_outbox_unpublished_idx
181
+ on x_outbox (staged_at) where published_at is null;
182
+ `.trim();
183
+ export const SQL_OUTBOX_STAGE = `
184
+ insert into x_outbox
185
+ (id, job, queue, input, idempotency_key, max_attempts, run_at, staged_at, tenant_id)
186
+ values ($1, $2, $3, $4::jsonb, $5, $6, to_timestamp($7 / 1000.0), to_timestamp($8 / 1000.0), $9)
187
+ `.trim();
188
+ export const SQL_OUTBOX_CLAIM = `
189
+ select id, job, queue, input, idempotency_key, max_attempts,
190
+ (extract(epoch from run_at) * 1000)::bigint as run_at,
191
+ (extract(epoch from staged_at) * 1000)::bigint as staged_at,
192
+ tenant_id
193
+ from x_outbox
194
+ where published_at is null
195
+ order by staged_at
196
+ limit $1
197
+ for update skip locked
198
+ `.trim();
199
+ export const SQL_OUTBOX_MARK_PUBLISHED = `
200
+ update x_outbox set published_at = to_timestamp($2 / 1000.0) where id = $1
201
+ `.trim();
202
+ //# sourceMappingURL=outbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox.js","sourceRoot":"","sources":["outbox.ts"],"names":[],"mappings":"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"}