absurd-sdk 0.0.2

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 ADDED
@@ -0,0 +1,109 @@
1
+ # Absurd SDK for TypeScript
2
+
3
+ TypeScript SDK for [Absurd](https://github.com/earendil-works/absurd): a PostgreSQL-based durable task execution system.
4
+
5
+ Absurd is the simplest durable execution workflow system you can think of. It's entirely based on Postgres and nothing else. It's almost as easy to use as a queue, but it handles scheduling and retries, and it does all of that without needing any other services to run in addition to Postgres.
6
+
7
+ **Warning:** *this is an early experiment and should not be used in production.*
8
+
9
+ ## What is Durable Execution?
10
+
11
+ Durable execution (or durable workflows) is a way to run long-lived, reliable functions that can survive crashes, restarts, and network failures without losing state or duplicating work. Instead of running your logic in memory, a durable execution system decomposes a task into smaller pieces (step functions) and records every step and decision.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install absurd-sdk
17
+ ```
18
+
19
+ ## Prerequisites
20
+
21
+ Before using the SDK, you need to initialize Absurd in your PostgreSQL database:
22
+
23
+ ```bash
24
+ # Install absurdctl from https://github.com/earendil-works/absurd/releases
25
+ absurdctl init -d your-database-name
26
+ absurdctl create-queue -d your-database-name default
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ```typescript
32
+ import { Absurd } from 'absurd-sdk';
33
+
34
+ const app = new Absurd({
35
+ connectionString: 'postgresql://localhost/mydb'
36
+ });
37
+
38
+ // Register a task
39
+ app.registerTask({ name: 'order-fulfillment' }, async (params, ctx) => {
40
+ // Each step is checkpointed, so if the process crashes, we resume
41
+ // from the last completed step
42
+ const payment = await ctx.step('process-payment', async () => {
43
+ return await processPayment(params.amount);
44
+ });
45
+
46
+ const inventory = await ctx.step('reserve-inventory', async () => {
47
+ return await reserveItems(params.items);
48
+ });
49
+
50
+ // Wait for an event - the task suspends until the event arrives
51
+ const shipment = await ctx.awaitEvent(`shipment.packed:${params.orderId}`);
52
+
53
+ await ctx.step('send-notification', async () => {
54
+ return await sendEmail(params.email, shipment);
55
+ });
56
+
57
+ return { orderId: payment.id, trackingNumber: shipment.trackingNumber };
58
+ });
59
+
60
+ // Start a worker that pulls tasks from Postgres
61
+ await app.startWorker();
62
+ ```
63
+
64
+ ## Spawning Tasks
65
+
66
+ ```typescript
67
+ // Spawn a task - it will be executed durably with automatic retries
68
+ await app.spawn('order-fulfillment', {
69
+ orderId: '42',
70
+ amount: 9999,
71
+ items: ['widget-1', 'gadget-2'],
72
+ email: 'customer@example.com'
73
+ });
74
+ ```
75
+
76
+ ## Emitting Events
77
+
78
+ ```typescript
79
+ // Emit an event that a suspended task might be waiting for
80
+ await app.emitEvent('shipment.packed:42', {
81
+ trackingNumber: 'TRACK123'
82
+ });
83
+ ```
84
+
85
+ ## Idempotency Keys
86
+
87
+ Use the task ID to derive idempotency keys for external APIs:
88
+
89
+ ```typescript
90
+ const payment = await ctx.step('process-payment', async () => {
91
+ const idempotencyKey = `${ctx.taskID}:payment`;
92
+ return await stripe.charges.create({
93
+ amount: params.amount,
94
+ idempotencyKey,
95
+ });
96
+ });
97
+ ```
98
+
99
+ ## Documentation
100
+
101
+ For more information, examples, and documentation, visit:
102
+
103
+ - [Main Repository](https://github.com/earendil-works/absurd)
104
+ - [Examples](https://github.com/earendil-works/absurd/tree/main/sdks/typescript/examples)
105
+ - [Issue Tracker](https://github.com/earendil-works/absurd/issues)
106
+
107
+ ## License
108
+
109
+ Apache-2.0
@@ -0,0 +1,527 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.Absurd = exports.TaskContext = exports.TimeoutError = exports.SuspendTask = void 0;
37
+ /**
38
+ * Absurd SDK for TypeScript and JavaScript
39
+ */
40
+ const pg = __importStar(require("pg"));
41
+ const os = __importStar(require("os"));
42
+ /**
43
+ * Internal exception that is thrown to suspend a run. As a user
44
+ * you should never see this exception.
45
+ */
46
+ class SuspendTask extends Error {
47
+ constructor() {
48
+ super("Task suspended");
49
+ this.name = "SuspendTask";
50
+ }
51
+ }
52
+ exports.SuspendTask = SuspendTask;
53
+ /**
54
+ * This error is thrown when awaiting an event ran into a timeout.
55
+ */
56
+ class TimeoutError extends Error {
57
+ constructor(message) {
58
+ super(message);
59
+ this.name = "TimeoutError";
60
+ }
61
+ }
62
+ exports.TimeoutError = TimeoutError;
63
+ class TaskContext {
64
+ taskID;
65
+ pool;
66
+ queueName;
67
+ task;
68
+ checkpointCache;
69
+ claimTimeout;
70
+ stepNameCounter = new Map();
71
+ constructor(taskID, pool, queueName, task, checkpointCache, claimTimeout) {
72
+ this.taskID = taskID;
73
+ this.pool = pool;
74
+ this.queueName = queueName;
75
+ this.task = task;
76
+ this.checkpointCache = checkpointCache;
77
+ this.claimTimeout = claimTimeout;
78
+ }
79
+ static async create(args) {
80
+ const { taskID, pool, queueName, task, claimTimeout } = args;
81
+ const result = await pool.query(`SELECT checkpoint_name, state, status, owner_run_id, updated_at
82
+ FROM absurd.get_task_checkpoint_states($1, $2, $3)`, [queueName, task.task_id, task.run_id]);
83
+ const cache = new Map();
84
+ for (const row of result.rows) {
85
+ cache.set(row.checkpoint_name, row.state);
86
+ }
87
+ return new TaskContext(taskID, pool, queueName, task, cache, claimTimeout);
88
+ }
89
+ /**
90
+ * Defines a step in the task execution. Steps are idempotent in
91
+ * that they are executed exactly once (unless they fail) and their
92
+ * results are cached. As a result the return value of this function
93
+ * must support `JSON.stringify`.
94
+ */
95
+ async step(name, fn) {
96
+ const checkpointName = this.getCheckpointName(name);
97
+ const state = await this.lookupCheckpoint(checkpointName);
98
+ if (state !== undefined) {
99
+ return state;
100
+ }
101
+ const rv = await fn();
102
+ await this.persistCheckpoint(checkpointName, rv);
103
+ return rv;
104
+ }
105
+ /**
106
+ * Sleeps for a given number of seconds. Note that this
107
+ * *always* suspends the task, even if you only wait for a very
108
+ * short period of time.
109
+ */
110
+ async sleepFor(stepName, duration) {
111
+ return await this.sleepUntil(stepName, new Date(Date.now() + duration * 1000));
112
+ }
113
+ /**
114
+ * Like `sleepFor` but with an absolute time when the task should be
115
+ * awoken again.
116
+ */
117
+ async sleepUntil(stepName, wakeAt) {
118
+ const checkpointName = this.getCheckpointName(stepName);
119
+ const state = await this.lookupCheckpoint(checkpointName);
120
+ let actualWakeAt = typeof state === "string" ? new Date(state) : wakeAt;
121
+ if (!state) {
122
+ await this.persistCheckpoint(checkpointName, wakeAt.toISOString());
123
+ }
124
+ if (Date.now() < actualWakeAt.getTime()) {
125
+ await this.scheduleRun(actualWakeAt);
126
+ throw new SuspendTask();
127
+ }
128
+ }
129
+ getCheckpointName(name) {
130
+ const count = (this.stepNameCounter.get(name) ?? 0) + 1;
131
+ this.stepNameCounter.set(name, count);
132
+ const actualStepName = count === 1 ? name : `${name}#${count}`;
133
+ return actualStepName;
134
+ }
135
+ async lookupCheckpoint(checkpointName) {
136
+ const cached = this.checkpointCache.get(checkpointName);
137
+ if (cached !== undefined) {
138
+ return cached;
139
+ }
140
+ const result = await this.pool.query(`SELECT checkpoint_name, state, status, owner_run_id, updated_at
141
+ FROM absurd.get_task_checkpoint_state($1, $2, $3)`, [this.queueName, this.task.task_id, checkpointName]);
142
+ if (result.rows.length > 0) {
143
+ const state = result.rows[0].state;
144
+ this.checkpointCache.set(checkpointName, state);
145
+ return state;
146
+ }
147
+ return undefined;
148
+ }
149
+ async persistCheckpoint(checkpointName, value) {
150
+ await this.pool.query(`SELECT absurd.set_task_checkpoint_state($1, $2, $3, $4, $5, $6)`, [
151
+ this.queueName,
152
+ this.task.task_id,
153
+ checkpointName,
154
+ JSON.stringify(value),
155
+ this.task.run_id,
156
+ this.claimTimeout,
157
+ ]);
158
+ this.checkpointCache.set(checkpointName, value);
159
+ }
160
+ async scheduleRun(wakeAt) {
161
+ await this.pool.query(`SELECT absurd.schedule_run($1, $2, $3)`, [
162
+ this.queueName,
163
+ this.task.run_id,
164
+ wakeAt,
165
+ ]);
166
+ }
167
+ /**
168
+ * Awaits the arrival of an event. Events need to be uniquely
169
+ * named so fold in the necessary parameters into the name (eg: customer id).
170
+ */
171
+ async awaitEvent(eventName, options) {
172
+ // the default step name is derived from the event name.
173
+ const stepName = options?.stepName || `$awaitEvent:${eventName}`;
174
+ let timeout = null;
175
+ if (options?.timeout !== undefined &&
176
+ Number.isFinite(options?.timeout) &&
177
+ options?.timeout >= 0) {
178
+ timeout = Math.floor(options?.timeout);
179
+ }
180
+ const checkpointName = this.getCheckpointName(stepName);
181
+ const cached = await this.lookupCheckpoint(checkpointName);
182
+ if (cached !== undefined) {
183
+ return cached;
184
+ }
185
+ if (this.task.wake_event === eventName &&
186
+ (this.task.event_payload === null ||
187
+ this.task.event_payload === undefined)) {
188
+ this.task.wake_event = null;
189
+ this.task.event_payload = null;
190
+ throw new TimeoutError(`Timed out waiting for event "${eventName}"`);
191
+ }
192
+ const result = await this.pool.query(`SELECT should_suspend, payload
193
+ FROM absurd.await_event($1, $2, $3, $4, $5, $6)`, [
194
+ this.queueName,
195
+ this.task.task_id,
196
+ this.task.run_id,
197
+ checkpointName,
198
+ eventName,
199
+ timeout,
200
+ ]);
201
+ if (result.rows.length === 0) {
202
+ throw new Error("Failed to await event");
203
+ }
204
+ const { should_suspend, payload } = result.rows[0];
205
+ if (!should_suspend) {
206
+ this.checkpointCache.set(checkpointName, payload);
207
+ this.task.event_payload = null;
208
+ return payload;
209
+ }
210
+ throw new SuspendTask();
211
+ }
212
+ /**
213
+ * Emits an event that can be awaited.
214
+ */
215
+ async emitEvent(eventName, payload) {
216
+ if (!eventName) {
217
+ throw new Error("eventName must be a non-empty string");
218
+ }
219
+ await this.pool.query(`SELECT absurd.emit_event($1, $2, $3)`, [
220
+ this.queueName,
221
+ eventName,
222
+ JSON.stringify(payload ?? null),
223
+ ]);
224
+ }
225
+ async complete(result) {
226
+ await this.pool.query(`SELECT absurd.complete_run($1, $2, $3)`, [
227
+ this.queueName,
228
+ this.task.run_id,
229
+ JSON.stringify(result ?? null),
230
+ ]);
231
+ }
232
+ async fail(err) {
233
+ console.error("[absurd] task execution failed:", err);
234
+ await this.pool.query(`SELECT absurd.fail_run($1, $2, $3, $4)`, [
235
+ this.queueName,
236
+ this.task.run_id,
237
+ JSON.stringify(serializeError(err)),
238
+ null,
239
+ ]);
240
+ }
241
+ }
242
+ exports.TaskContext = TaskContext;
243
+ class Absurd {
244
+ pool;
245
+ ownedPool;
246
+ queueName;
247
+ defaultMaxAttempts;
248
+ registry = new Map();
249
+ worker = null;
250
+ constructor(poolOrUrl, queueName = "default", defaultMaxAttempts = 5) {
251
+ if (!poolOrUrl) {
252
+ poolOrUrl =
253
+ process.env.ABSURD_DATABASE_URL || "postgresql://localhost/absurd";
254
+ }
255
+ if (typeof poolOrUrl === "string") {
256
+ this.pool = new pg.Pool({ connectionString: poolOrUrl });
257
+ this.ownedPool = true;
258
+ }
259
+ else {
260
+ this.pool = poolOrUrl;
261
+ this.ownedPool = false;
262
+ }
263
+ this.queueName = queueName;
264
+ this.defaultMaxAttempts = defaultMaxAttempts;
265
+ }
266
+ /**
267
+ * This registers a given function as task.
268
+ */
269
+ registerTask(options, handler) {
270
+ if (!options?.name) {
271
+ throw new Error("Task registration requires a name");
272
+ }
273
+ if (options.defaultMaxAttempts !== undefined &&
274
+ options.defaultMaxAttempts < 1) {
275
+ throw new Error("defaultMaxAttempts must be at least 1");
276
+ }
277
+ if (options.defaultCancellation) {
278
+ normalizeCancellation(options.defaultCancellation);
279
+ }
280
+ const queue = options.queue ?? this.queueName;
281
+ if (!queue) {
282
+ throw new Error(`Task "${options.name}" must specify a queue or use a client with a default queue`);
283
+ }
284
+ this.registry.set(options.name, {
285
+ name: options.name,
286
+ queue,
287
+ defaultMaxAttempts: options.defaultMaxAttempts,
288
+ defaultCancellation: options.defaultCancellation,
289
+ handler: handler,
290
+ });
291
+ }
292
+ async createQueue(queueName) {
293
+ const queue = queueName ?? this.queueName;
294
+ await this.pool.query(`SELECT absurd.create_queue($1)`, [queue]);
295
+ }
296
+ async dropQueue(queueName) {
297
+ const queue = queueName ?? this.queueName;
298
+ await this.pool.query(`SELECT absurd.drop_queue($1)`, [queue]);
299
+ }
300
+ async listQueues() {
301
+ const result = await this.pool.query(`SELECT * FROM absurd.list_queues()`);
302
+ const rv = [];
303
+ console.log(result);
304
+ for (const row of result.rows) {
305
+ rv.push(row.queue_name);
306
+ }
307
+ return rv;
308
+ }
309
+ /**
310
+ * Spawns a specific task.
311
+ */
312
+ async spawn(taskName, params, options = {}) {
313
+ const registration = this.registry.get(taskName);
314
+ let queue;
315
+ if (registration) {
316
+ queue = registration.queue;
317
+ if (options.queue !== undefined && options.queue !== registration.queue) {
318
+ throw new Error(`Task "${taskName}" is registered for queue "${registration.queue}" but spawn requested queue "${options.queue}".`);
319
+ }
320
+ }
321
+ else if (!options.queue) {
322
+ throw new Error(`Task "${taskName}" is not registered. Provide options.queue when spawning unregistered tasks.`);
323
+ }
324
+ else {
325
+ queue = options.queue;
326
+ }
327
+ const effectiveMaxAttempts = options.maxAttempts !== undefined
328
+ ? options.maxAttempts
329
+ : (registration?.defaultMaxAttempts ?? this.defaultMaxAttempts);
330
+ const effectiveCancellation = options.cancellation !== undefined
331
+ ? options.cancellation
332
+ : registration?.defaultCancellation;
333
+ const normalizedOptions = normalizeSpawnOptions({
334
+ ...options,
335
+ maxAttempts: effectiveMaxAttempts,
336
+ cancellation: effectiveCancellation,
337
+ });
338
+ const result = await this.pool.query(`SELECT task_id, run_id, attempt
339
+ FROM absurd.spawn_task($1, $2, $3, $4)`, [
340
+ queue,
341
+ taskName,
342
+ JSON.stringify(params),
343
+ JSON.stringify(normalizedOptions),
344
+ ]);
345
+ if (result.rows.length === 0) {
346
+ throw new Error("Failed to spawn task");
347
+ }
348
+ const row = result.rows[0];
349
+ return {
350
+ taskID: row.task_id,
351
+ runID: row.run_id,
352
+ attempt: row.attempt,
353
+ };
354
+ }
355
+ /**
356
+ * Emits an event from outside of a task.
357
+ */
358
+ async emitEvent(eventName, payload, queueName) {
359
+ if (!eventName) {
360
+ throw new Error("eventName must be a non-empty string");
361
+ }
362
+ await this.pool.query(`SELECT absurd.emit_event($1, $2, $3)`, [
363
+ queueName || this.queueName,
364
+ eventName,
365
+ JSON.stringify(payload ?? null),
366
+ ]);
367
+ }
368
+ async claimTasks(options) {
369
+ const { batchSize: count = 1, claimTimeout = 120, workerId = "worker", } = options ?? {};
370
+ const result = await this.pool.query(`SELECT run_id, task_id, attempt, task_name, params, retry_strategy, max_attempts,
371
+ headers, wake_event, event_payload
372
+ FROM absurd.claim_task($1, $2, $3, $4)`, [this.queueName, workerId, claimTimeout, count]);
373
+ return result.rows;
374
+ }
375
+ /**
376
+ * Polls and processes a batch of messages sequentially.
377
+ * For parallel processing, use startWorker with concurrency option.
378
+ */
379
+ async workBatch(workerId = "worker", claimTimeout = 120, batchSize = 1) {
380
+ const tasks = await this.claimTasks({ batchSize, claimTimeout, workerId });
381
+ for (const task of tasks) {
382
+ await this.executeTask(task, claimTimeout);
383
+ }
384
+ }
385
+ /**
386
+ * Starts a worker that continuously polls for tasks and processes them.
387
+ * Returns a Worker instance with a close() method for graceful shutdown.
388
+ */
389
+ async startWorker(options = {}) {
390
+ const { workerId = `${os.hostname?.() || "host"}:${process.pid}`, claimTimeout = 120, concurrency = 1, batchSize, pollInterval = 0.25, onError = (err) => console.error("Worker error:", err), } = options;
391
+ const effectiveBatchSize = batchSize ?? concurrency;
392
+ let running = true;
393
+ let workerLoopPromise;
394
+ const worker = {
395
+ close: async () => {
396
+ running = false;
397
+ await workerLoopPromise;
398
+ },
399
+ };
400
+ this.worker = worker;
401
+ workerLoopPromise = (async () => {
402
+ while (running) {
403
+ try {
404
+ const messages = await this.claimTasks({
405
+ batchSize: effectiveBatchSize,
406
+ claimTimeout: claimTimeout,
407
+ workerId,
408
+ });
409
+ if (messages.length === 0) {
410
+ await sleep(pollInterval);
411
+ continue;
412
+ }
413
+ const executing = new Set();
414
+ for (const task of messages) {
415
+ const promise = this.executeTask(task, claimTimeout)
416
+ .catch((err) => onError(err))
417
+ .finally(() => executing.delete(promise));
418
+ executing.add(promise);
419
+ if (executing.size >= concurrency) {
420
+ await Promise.race(executing);
421
+ }
422
+ }
423
+ await Promise.all(executing);
424
+ }
425
+ catch (err) {
426
+ onError(err);
427
+ await sleep(pollInterval);
428
+ }
429
+ }
430
+ })();
431
+ return worker;
432
+ }
433
+ async close() {
434
+ if (this.worker) {
435
+ await this.worker.close();
436
+ }
437
+ if (this.ownedPool) {
438
+ await this.pool.end();
439
+ }
440
+ }
441
+ async executeTask(task, claimTimeout) {
442
+ const registration = this.registry.get(task.task_name);
443
+ const ctx = await TaskContext.create({
444
+ taskID: task.task_id,
445
+ pool: this.pool,
446
+ queueName: registration?.queue ?? "unknown",
447
+ task: task,
448
+ claimTimeout,
449
+ });
450
+ try {
451
+ if (!registration) {
452
+ throw new Error("Unknown task");
453
+ }
454
+ else if (registration.queue !== this.queueName) {
455
+ throw new Error("Misconfigured task (queue mismatch)");
456
+ }
457
+ const result = await registration.handler(task.params, ctx);
458
+ await ctx.complete(result);
459
+ }
460
+ catch (err) {
461
+ if (err instanceof SuspendTask) {
462
+ // Task suspended (sleep or await), don't complete or fail
463
+ return;
464
+ }
465
+ await ctx.fail(err);
466
+ }
467
+ }
468
+ }
469
+ exports.Absurd = Absurd;
470
+ function serializeError(err) {
471
+ if (err instanceof Error) {
472
+ return {
473
+ name: err.name,
474
+ message: err.message,
475
+ stack: err.stack || null,
476
+ };
477
+ }
478
+ return { message: String(err) };
479
+ }
480
+ function normalizeSpawnOptions(options) {
481
+ const normalized = {};
482
+ if (options.headers !== undefined) {
483
+ normalized.headers = options.headers;
484
+ }
485
+ if (options.maxAttempts !== undefined) {
486
+ normalized.max_attempts = options.maxAttempts;
487
+ }
488
+ if (options.retryStrategy) {
489
+ normalized.retry_strategy = serializeRetryStrategy(options.retryStrategy);
490
+ }
491
+ const cancellation = normalizeCancellation(options.cancellation);
492
+ if (cancellation) {
493
+ normalized.cancellation = cancellation;
494
+ }
495
+ return normalized;
496
+ }
497
+ function serializeRetryStrategy(strategy) {
498
+ const serialized = {
499
+ kind: strategy.kind,
500
+ };
501
+ if (strategy.baseSeconds !== undefined) {
502
+ serialized.base_seconds = strategy.baseSeconds;
503
+ }
504
+ if (strategy.factor !== undefined) {
505
+ serialized.factor = strategy.factor;
506
+ }
507
+ if (strategy.maxSeconds !== undefined) {
508
+ serialized.max_seconds = strategy.maxSeconds;
509
+ }
510
+ return serialized;
511
+ }
512
+ function normalizeCancellation(policy) {
513
+ if (!policy) {
514
+ return undefined;
515
+ }
516
+ const normalized = {};
517
+ if (policy.maxDuration !== undefined) {
518
+ normalized.max_duration = policy.maxDuration;
519
+ }
520
+ if (policy.maxDelay !== undefined) {
521
+ normalized.max_delay = policy.maxDelay;
522
+ }
523
+ return Object.keys(normalized).length > 0 ? normalized : undefined;
524
+ }
525
+ async function sleep(ms) {
526
+ return new Promise((resolve) => setTimeout(resolve, ms * 1000));
527
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Absurd SDK for TypeScript and JavaScript
3
+ */
4
+ import * as pg from "pg";
5
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
6
+ [key: string]: JsonValue;
7
+ };
8
+ export type JsonObject = {
9
+ [key: string]: JsonValue;
10
+ };
11
+ export interface RetryStrategy {
12
+ kind: "fixed" | "exponential" | "none";
13
+ baseSeconds?: number;
14
+ factor?: number;
15
+ maxSeconds?: number;
16
+ }
17
+ export interface CancellationPolicy {
18
+ maxDuration?: number;
19
+ maxDelay?: number;
20
+ }
21
+ export interface SpawnOptions {
22
+ maxAttempts?: number;
23
+ retryStrategy?: RetryStrategy;
24
+ headers?: JsonObject;
25
+ queue?: string;
26
+ cancellation?: CancellationPolicy;
27
+ }
28
+ export interface ClaimedTask {
29
+ run_id: string;
30
+ task_id: string;
31
+ task_name: string;
32
+ attempt: number;
33
+ params: JsonValue;
34
+ retry_strategy: JsonValue;
35
+ max_attempts: number | null;
36
+ headers: JsonObject | null;
37
+ wake_event: string | null;
38
+ event_payload: JsonValue | null;
39
+ }
40
+ export interface WorkerOptions {
41
+ workerId?: string;
42
+ claimTimeout?: number;
43
+ batchSize?: number;
44
+ concurrency?: number;
45
+ pollInterval?: number;
46
+ onError?: (error: Error) => void;
47
+ }
48
+ export interface Worker {
49
+ close(): Promise<void>;
50
+ }
51
+ interface SpawnResult {
52
+ taskID: string;
53
+ runID: string;
54
+ attempt: number;
55
+ }
56
+ export type TaskHandler<P = any, R = any> = (params: P, ctx: TaskContext) => Promise<R>;
57
+ /**
58
+ * Internal exception that is thrown to suspend a run. As a user
59
+ * you should never see this exception.
60
+ */
61
+ export declare class SuspendTask extends Error {
62
+ constructor();
63
+ }
64
+ /**
65
+ * This error is thrown when awaiting an event ran into a timeout.
66
+ */
67
+ export declare class TimeoutError extends Error {
68
+ constructor(message: string);
69
+ }
70
+ export interface TaskRegistrationOptions {
71
+ name: string;
72
+ queue?: string;
73
+ defaultMaxAttempts?: number;
74
+ defaultCancellation?: CancellationPolicy;
75
+ }
76
+ export declare class TaskContext {
77
+ readonly taskID: string;
78
+ private readonly pool;
79
+ private readonly queueName;
80
+ private readonly task;
81
+ private readonly checkpointCache;
82
+ private readonly claimTimeout;
83
+ private stepNameCounter;
84
+ private constructor();
85
+ static create(args: {
86
+ taskID: string;
87
+ pool: pg.Pool;
88
+ queueName: string;
89
+ task: ClaimedTask;
90
+ claimTimeout: number;
91
+ }): Promise<TaskContext>;
92
+ /**
93
+ * Defines a step in the task execution. Steps are idempotent in
94
+ * that they are executed exactly once (unless they fail) and their
95
+ * results are cached. As a result the return value of this function
96
+ * must support `JSON.stringify`.
97
+ */
98
+ step<T>(name: string, fn: () => Promise<T>): Promise<T>;
99
+ /**
100
+ * Sleeps for a given number of seconds. Note that this
101
+ * *always* suspends the task, even if you only wait for a very
102
+ * short period of time.
103
+ */
104
+ sleepFor(stepName: string, duration: number): Promise<void>;
105
+ /**
106
+ * Like `sleepFor` but with an absolute time when the task should be
107
+ * awoken again.
108
+ */
109
+ sleepUntil(stepName: string, wakeAt: Date): Promise<void>;
110
+ private getCheckpointName;
111
+ private lookupCheckpoint;
112
+ private persistCheckpoint;
113
+ private scheduleRun;
114
+ /**
115
+ * Awaits the arrival of an event. Events need to be uniquely
116
+ * named so fold in the necessary parameters into the name (eg: customer id).
117
+ */
118
+ awaitEvent(eventName: string, options?: {
119
+ stepName?: string;
120
+ timeout?: number;
121
+ }): Promise<JsonValue>;
122
+ /**
123
+ * Emits an event that can be awaited.
124
+ */
125
+ emitEvent(eventName: string, payload?: JsonValue): Promise<void>;
126
+ complete(result?: any): Promise<void>;
127
+ fail(err: unknown): Promise<void>;
128
+ }
129
+ export declare class Absurd {
130
+ private readonly pool;
131
+ private readonly ownedPool;
132
+ private readonly queueName;
133
+ private readonly defaultMaxAttempts;
134
+ private readonly registry;
135
+ private worker;
136
+ constructor(poolOrUrl?: pg.Pool | string | null, queueName?: string, defaultMaxAttempts?: number);
137
+ /**
138
+ * This registers a given function as task.
139
+ */
140
+ registerTask<P = any, R = any>(options: TaskRegistrationOptions, handler: TaskHandler<P, R>): void;
141
+ createQueue(queueName?: string): Promise<void>;
142
+ dropQueue(queueName?: string): Promise<void>;
143
+ listQueues(): Promise<Array<string>>;
144
+ /**
145
+ * Spawns a specific task.
146
+ */
147
+ spawn<P = any>(taskName: string, params: P, options?: SpawnOptions): Promise<SpawnResult>;
148
+ /**
149
+ * Emits an event from outside of a task.
150
+ */
151
+ emitEvent(eventName: string, payload?: JsonValue, queueName?: string): Promise<void>;
152
+ claimTasks(options?: {
153
+ batchSize?: number;
154
+ claimTimeout?: number;
155
+ workerId?: string;
156
+ }): Promise<ClaimedTask[]>;
157
+ /**
158
+ * Polls and processes a batch of messages sequentially.
159
+ * For parallel processing, use startWorker with concurrency option.
160
+ */
161
+ workBatch(workerId?: string, claimTimeout?: number, batchSize?: number): Promise<void>;
162
+ /**
163
+ * Starts a worker that continuously polls for tasks and processes them.
164
+ * Returns a Worker instance with a close() method for graceful shutdown.
165
+ */
166
+ startWorker(options?: WorkerOptions): Promise<Worker>;
167
+ close(): Promise<void>;
168
+ private executeTask;
169
+ }
170
+ export {};
171
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,488 @@
1
+ /**
2
+ * Absurd SDK for TypeScript and JavaScript
3
+ */
4
+ import * as pg from "pg";
5
+ import * as os from "os";
6
+ /**
7
+ * Internal exception that is thrown to suspend a run. As a user
8
+ * you should never see this exception.
9
+ */
10
+ export class SuspendTask extends Error {
11
+ constructor() {
12
+ super("Task suspended");
13
+ this.name = "SuspendTask";
14
+ }
15
+ }
16
+ /**
17
+ * This error is thrown when awaiting an event ran into a timeout.
18
+ */
19
+ export class TimeoutError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = "TimeoutError";
23
+ }
24
+ }
25
+ export class TaskContext {
26
+ taskID;
27
+ pool;
28
+ queueName;
29
+ task;
30
+ checkpointCache;
31
+ claimTimeout;
32
+ stepNameCounter = new Map();
33
+ constructor(taskID, pool, queueName, task, checkpointCache, claimTimeout) {
34
+ this.taskID = taskID;
35
+ this.pool = pool;
36
+ this.queueName = queueName;
37
+ this.task = task;
38
+ this.checkpointCache = checkpointCache;
39
+ this.claimTimeout = claimTimeout;
40
+ }
41
+ static async create(args) {
42
+ const { taskID, pool, queueName, task, claimTimeout } = args;
43
+ const result = await pool.query(`SELECT checkpoint_name, state, status, owner_run_id, updated_at
44
+ FROM absurd.get_task_checkpoint_states($1, $2, $3)`, [queueName, task.task_id, task.run_id]);
45
+ const cache = new Map();
46
+ for (const row of result.rows) {
47
+ cache.set(row.checkpoint_name, row.state);
48
+ }
49
+ return new TaskContext(taskID, pool, queueName, task, cache, claimTimeout);
50
+ }
51
+ /**
52
+ * Defines a step in the task execution. Steps are idempotent in
53
+ * that they are executed exactly once (unless they fail) and their
54
+ * results are cached. As a result the return value of this function
55
+ * must support `JSON.stringify`.
56
+ */
57
+ async step(name, fn) {
58
+ const checkpointName = this.getCheckpointName(name);
59
+ const state = await this.lookupCheckpoint(checkpointName);
60
+ if (state !== undefined) {
61
+ return state;
62
+ }
63
+ const rv = await fn();
64
+ await this.persistCheckpoint(checkpointName, rv);
65
+ return rv;
66
+ }
67
+ /**
68
+ * Sleeps for a given number of seconds. Note that this
69
+ * *always* suspends the task, even if you only wait for a very
70
+ * short period of time.
71
+ */
72
+ async sleepFor(stepName, duration) {
73
+ return await this.sleepUntil(stepName, new Date(Date.now() + duration * 1000));
74
+ }
75
+ /**
76
+ * Like `sleepFor` but with an absolute time when the task should be
77
+ * awoken again.
78
+ */
79
+ async sleepUntil(stepName, wakeAt) {
80
+ const checkpointName = this.getCheckpointName(stepName);
81
+ const state = await this.lookupCheckpoint(checkpointName);
82
+ let actualWakeAt = typeof state === "string" ? new Date(state) : wakeAt;
83
+ if (!state) {
84
+ await this.persistCheckpoint(checkpointName, wakeAt.toISOString());
85
+ }
86
+ if (Date.now() < actualWakeAt.getTime()) {
87
+ await this.scheduleRun(actualWakeAt);
88
+ throw new SuspendTask();
89
+ }
90
+ }
91
+ getCheckpointName(name) {
92
+ const count = (this.stepNameCounter.get(name) ?? 0) + 1;
93
+ this.stepNameCounter.set(name, count);
94
+ const actualStepName = count === 1 ? name : `${name}#${count}`;
95
+ return actualStepName;
96
+ }
97
+ async lookupCheckpoint(checkpointName) {
98
+ const cached = this.checkpointCache.get(checkpointName);
99
+ if (cached !== undefined) {
100
+ return cached;
101
+ }
102
+ const result = await this.pool.query(`SELECT checkpoint_name, state, status, owner_run_id, updated_at
103
+ FROM absurd.get_task_checkpoint_state($1, $2, $3)`, [this.queueName, this.task.task_id, checkpointName]);
104
+ if (result.rows.length > 0) {
105
+ const state = result.rows[0].state;
106
+ this.checkpointCache.set(checkpointName, state);
107
+ return state;
108
+ }
109
+ return undefined;
110
+ }
111
+ async persistCheckpoint(checkpointName, value) {
112
+ await this.pool.query(`SELECT absurd.set_task_checkpoint_state($1, $2, $3, $4, $5, $6)`, [
113
+ this.queueName,
114
+ this.task.task_id,
115
+ checkpointName,
116
+ JSON.stringify(value),
117
+ this.task.run_id,
118
+ this.claimTimeout,
119
+ ]);
120
+ this.checkpointCache.set(checkpointName, value);
121
+ }
122
+ async scheduleRun(wakeAt) {
123
+ await this.pool.query(`SELECT absurd.schedule_run($1, $2, $3)`, [
124
+ this.queueName,
125
+ this.task.run_id,
126
+ wakeAt,
127
+ ]);
128
+ }
129
+ /**
130
+ * Awaits the arrival of an event. Events need to be uniquely
131
+ * named so fold in the necessary parameters into the name (eg: customer id).
132
+ */
133
+ async awaitEvent(eventName, options) {
134
+ // the default step name is derived from the event name.
135
+ const stepName = options?.stepName || `$awaitEvent:${eventName}`;
136
+ let timeout = null;
137
+ if (options?.timeout !== undefined &&
138
+ Number.isFinite(options?.timeout) &&
139
+ options?.timeout >= 0) {
140
+ timeout = Math.floor(options?.timeout);
141
+ }
142
+ const checkpointName = this.getCheckpointName(stepName);
143
+ const cached = await this.lookupCheckpoint(checkpointName);
144
+ if (cached !== undefined) {
145
+ return cached;
146
+ }
147
+ if (this.task.wake_event === eventName &&
148
+ (this.task.event_payload === null ||
149
+ this.task.event_payload === undefined)) {
150
+ this.task.wake_event = null;
151
+ this.task.event_payload = null;
152
+ throw new TimeoutError(`Timed out waiting for event "${eventName}"`);
153
+ }
154
+ const result = await this.pool.query(`SELECT should_suspend, payload
155
+ FROM absurd.await_event($1, $2, $3, $4, $5, $6)`, [
156
+ this.queueName,
157
+ this.task.task_id,
158
+ this.task.run_id,
159
+ checkpointName,
160
+ eventName,
161
+ timeout,
162
+ ]);
163
+ if (result.rows.length === 0) {
164
+ throw new Error("Failed to await event");
165
+ }
166
+ const { should_suspend, payload } = result.rows[0];
167
+ if (!should_suspend) {
168
+ this.checkpointCache.set(checkpointName, payload);
169
+ this.task.event_payload = null;
170
+ return payload;
171
+ }
172
+ throw new SuspendTask();
173
+ }
174
+ /**
175
+ * Emits an event that can be awaited.
176
+ */
177
+ async emitEvent(eventName, payload) {
178
+ if (!eventName) {
179
+ throw new Error("eventName must be a non-empty string");
180
+ }
181
+ await this.pool.query(`SELECT absurd.emit_event($1, $2, $3)`, [
182
+ this.queueName,
183
+ eventName,
184
+ JSON.stringify(payload ?? null),
185
+ ]);
186
+ }
187
+ async complete(result) {
188
+ await this.pool.query(`SELECT absurd.complete_run($1, $2, $3)`, [
189
+ this.queueName,
190
+ this.task.run_id,
191
+ JSON.stringify(result ?? null),
192
+ ]);
193
+ }
194
+ async fail(err) {
195
+ console.error("[absurd] task execution failed:", err);
196
+ await this.pool.query(`SELECT absurd.fail_run($1, $2, $3, $4)`, [
197
+ this.queueName,
198
+ this.task.run_id,
199
+ JSON.stringify(serializeError(err)),
200
+ null,
201
+ ]);
202
+ }
203
+ }
204
+ export class Absurd {
205
+ pool;
206
+ ownedPool;
207
+ queueName;
208
+ defaultMaxAttempts;
209
+ registry = new Map();
210
+ worker = null;
211
+ constructor(poolOrUrl, queueName = "default", defaultMaxAttempts = 5) {
212
+ if (!poolOrUrl) {
213
+ poolOrUrl =
214
+ process.env.ABSURD_DATABASE_URL || "postgresql://localhost/absurd";
215
+ }
216
+ if (typeof poolOrUrl === "string") {
217
+ this.pool = new pg.Pool({ connectionString: poolOrUrl });
218
+ this.ownedPool = true;
219
+ }
220
+ else {
221
+ this.pool = poolOrUrl;
222
+ this.ownedPool = false;
223
+ }
224
+ this.queueName = queueName;
225
+ this.defaultMaxAttempts = defaultMaxAttempts;
226
+ }
227
+ /**
228
+ * This registers a given function as task.
229
+ */
230
+ registerTask(options, handler) {
231
+ if (!options?.name) {
232
+ throw new Error("Task registration requires a name");
233
+ }
234
+ if (options.defaultMaxAttempts !== undefined &&
235
+ options.defaultMaxAttempts < 1) {
236
+ throw new Error("defaultMaxAttempts must be at least 1");
237
+ }
238
+ if (options.defaultCancellation) {
239
+ normalizeCancellation(options.defaultCancellation);
240
+ }
241
+ const queue = options.queue ?? this.queueName;
242
+ if (!queue) {
243
+ throw new Error(`Task "${options.name}" must specify a queue or use a client with a default queue`);
244
+ }
245
+ this.registry.set(options.name, {
246
+ name: options.name,
247
+ queue,
248
+ defaultMaxAttempts: options.defaultMaxAttempts,
249
+ defaultCancellation: options.defaultCancellation,
250
+ handler: handler,
251
+ });
252
+ }
253
+ async createQueue(queueName) {
254
+ const queue = queueName ?? this.queueName;
255
+ await this.pool.query(`SELECT absurd.create_queue($1)`, [queue]);
256
+ }
257
+ async dropQueue(queueName) {
258
+ const queue = queueName ?? this.queueName;
259
+ await this.pool.query(`SELECT absurd.drop_queue($1)`, [queue]);
260
+ }
261
+ async listQueues() {
262
+ const result = await this.pool.query(`SELECT * FROM absurd.list_queues()`);
263
+ const rv = [];
264
+ console.log(result);
265
+ for (const row of result.rows) {
266
+ rv.push(row.queue_name);
267
+ }
268
+ return rv;
269
+ }
270
+ /**
271
+ * Spawns a specific task.
272
+ */
273
+ async spawn(taskName, params, options = {}) {
274
+ const registration = this.registry.get(taskName);
275
+ let queue;
276
+ if (registration) {
277
+ queue = registration.queue;
278
+ if (options.queue !== undefined && options.queue !== registration.queue) {
279
+ throw new Error(`Task "${taskName}" is registered for queue "${registration.queue}" but spawn requested queue "${options.queue}".`);
280
+ }
281
+ }
282
+ else if (!options.queue) {
283
+ throw new Error(`Task "${taskName}" is not registered. Provide options.queue when spawning unregistered tasks.`);
284
+ }
285
+ else {
286
+ queue = options.queue;
287
+ }
288
+ const effectiveMaxAttempts = options.maxAttempts !== undefined
289
+ ? options.maxAttempts
290
+ : (registration?.defaultMaxAttempts ?? this.defaultMaxAttempts);
291
+ const effectiveCancellation = options.cancellation !== undefined
292
+ ? options.cancellation
293
+ : registration?.defaultCancellation;
294
+ const normalizedOptions = normalizeSpawnOptions({
295
+ ...options,
296
+ maxAttempts: effectiveMaxAttempts,
297
+ cancellation: effectiveCancellation,
298
+ });
299
+ const result = await this.pool.query(`SELECT task_id, run_id, attempt
300
+ FROM absurd.spawn_task($1, $2, $3, $4)`, [
301
+ queue,
302
+ taskName,
303
+ JSON.stringify(params),
304
+ JSON.stringify(normalizedOptions),
305
+ ]);
306
+ if (result.rows.length === 0) {
307
+ throw new Error("Failed to spawn task");
308
+ }
309
+ const row = result.rows[0];
310
+ return {
311
+ taskID: row.task_id,
312
+ runID: row.run_id,
313
+ attempt: row.attempt,
314
+ };
315
+ }
316
+ /**
317
+ * Emits an event from outside of a task.
318
+ */
319
+ async emitEvent(eventName, payload, queueName) {
320
+ if (!eventName) {
321
+ throw new Error("eventName must be a non-empty string");
322
+ }
323
+ await this.pool.query(`SELECT absurd.emit_event($1, $2, $3)`, [
324
+ queueName || this.queueName,
325
+ eventName,
326
+ JSON.stringify(payload ?? null),
327
+ ]);
328
+ }
329
+ async claimTasks(options) {
330
+ const { batchSize: count = 1, claimTimeout = 120, workerId = "worker", } = options ?? {};
331
+ const result = await this.pool.query(`SELECT run_id, task_id, attempt, task_name, params, retry_strategy, max_attempts,
332
+ headers, wake_event, event_payload
333
+ FROM absurd.claim_task($1, $2, $3, $4)`, [this.queueName, workerId, claimTimeout, count]);
334
+ return result.rows;
335
+ }
336
+ /**
337
+ * Polls and processes a batch of messages sequentially.
338
+ * For parallel processing, use startWorker with concurrency option.
339
+ */
340
+ async workBatch(workerId = "worker", claimTimeout = 120, batchSize = 1) {
341
+ const tasks = await this.claimTasks({ batchSize, claimTimeout, workerId });
342
+ for (const task of tasks) {
343
+ await this.executeTask(task, claimTimeout);
344
+ }
345
+ }
346
+ /**
347
+ * Starts a worker that continuously polls for tasks and processes them.
348
+ * Returns a Worker instance with a close() method for graceful shutdown.
349
+ */
350
+ async startWorker(options = {}) {
351
+ const { workerId = `${os.hostname?.() || "host"}:${process.pid}`, claimTimeout = 120, concurrency = 1, batchSize, pollInterval = 0.25, onError = (err) => console.error("Worker error:", err), } = options;
352
+ const effectiveBatchSize = batchSize ?? concurrency;
353
+ let running = true;
354
+ let workerLoopPromise;
355
+ const worker = {
356
+ close: async () => {
357
+ running = false;
358
+ await workerLoopPromise;
359
+ },
360
+ };
361
+ this.worker = worker;
362
+ workerLoopPromise = (async () => {
363
+ while (running) {
364
+ try {
365
+ const messages = await this.claimTasks({
366
+ batchSize: effectiveBatchSize,
367
+ claimTimeout: claimTimeout,
368
+ workerId,
369
+ });
370
+ if (messages.length === 0) {
371
+ await sleep(pollInterval);
372
+ continue;
373
+ }
374
+ const executing = new Set();
375
+ for (const task of messages) {
376
+ const promise = this.executeTask(task, claimTimeout)
377
+ .catch((err) => onError(err))
378
+ .finally(() => executing.delete(promise));
379
+ executing.add(promise);
380
+ if (executing.size >= concurrency) {
381
+ await Promise.race(executing);
382
+ }
383
+ }
384
+ await Promise.all(executing);
385
+ }
386
+ catch (err) {
387
+ onError(err);
388
+ await sleep(pollInterval);
389
+ }
390
+ }
391
+ })();
392
+ return worker;
393
+ }
394
+ async close() {
395
+ if (this.worker) {
396
+ await this.worker.close();
397
+ }
398
+ if (this.ownedPool) {
399
+ await this.pool.end();
400
+ }
401
+ }
402
+ async executeTask(task, claimTimeout) {
403
+ const registration = this.registry.get(task.task_name);
404
+ const ctx = await TaskContext.create({
405
+ taskID: task.task_id,
406
+ pool: this.pool,
407
+ queueName: registration?.queue ?? "unknown",
408
+ task: task,
409
+ claimTimeout,
410
+ });
411
+ try {
412
+ if (!registration) {
413
+ throw new Error("Unknown task");
414
+ }
415
+ else if (registration.queue !== this.queueName) {
416
+ throw new Error("Misconfigured task (queue mismatch)");
417
+ }
418
+ const result = await registration.handler(task.params, ctx);
419
+ await ctx.complete(result);
420
+ }
421
+ catch (err) {
422
+ if (err instanceof SuspendTask) {
423
+ // Task suspended (sleep or await), don't complete or fail
424
+ return;
425
+ }
426
+ await ctx.fail(err);
427
+ }
428
+ }
429
+ }
430
+ function serializeError(err) {
431
+ if (err instanceof Error) {
432
+ return {
433
+ name: err.name,
434
+ message: err.message,
435
+ stack: err.stack || null,
436
+ };
437
+ }
438
+ return { message: String(err) };
439
+ }
440
+ function normalizeSpawnOptions(options) {
441
+ const normalized = {};
442
+ if (options.headers !== undefined) {
443
+ normalized.headers = options.headers;
444
+ }
445
+ if (options.maxAttempts !== undefined) {
446
+ normalized.max_attempts = options.maxAttempts;
447
+ }
448
+ if (options.retryStrategy) {
449
+ normalized.retry_strategy = serializeRetryStrategy(options.retryStrategy);
450
+ }
451
+ const cancellation = normalizeCancellation(options.cancellation);
452
+ if (cancellation) {
453
+ normalized.cancellation = cancellation;
454
+ }
455
+ return normalized;
456
+ }
457
+ function serializeRetryStrategy(strategy) {
458
+ const serialized = {
459
+ kind: strategy.kind,
460
+ };
461
+ if (strategy.baseSeconds !== undefined) {
462
+ serialized.base_seconds = strategy.baseSeconds;
463
+ }
464
+ if (strategy.factor !== undefined) {
465
+ serialized.factor = strategy.factor;
466
+ }
467
+ if (strategy.maxSeconds !== undefined) {
468
+ serialized.max_seconds = strategy.maxSeconds;
469
+ }
470
+ return serialized;
471
+ }
472
+ function normalizeCancellation(policy) {
473
+ if (!policy) {
474
+ return undefined;
475
+ }
476
+ const normalized = {};
477
+ if (policy.maxDuration !== undefined) {
478
+ normalized.max_duration = policy.maxDuration;
479
+ }
480
+ if (policy.maxDelay !== undefined) {
481
+ normalized.max_delay = policy.maxDelay;
482
+ }
483
+ return Object.keys(normalized).length > 0 ? normalized : undefined;
484
+ }
485
+ async function sleep(ms) {
486
+ return new Promise((resolve) => setTimeout(resolve, ms * 1000));
487
+ }
488
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "absurd-sdk",
3
+ "version": "0.0.2",
4
+ "description": "TypeScript SDK for Absurd - PostgreSQL-based durable task execution",
5
+ "type": "module",
6
+ "main": "./dist/cjs/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/cjs/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc --project tsconfig.json && tsc --project tsconfig.cjs.json",
21
+ "prepare": "npm run build",
22
+ "test": "echo \"Error: no test specified\" && exit 1"
23
+ },
24
+ "keywords": [
25
+ "absurd",
26
+ "postgresql",
27
+ "queue",
28
+ "task",
29
+ "durable",
30
+ "workflow"
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/earendil-works/absurd"
35
+ },
36
+ "author": "",
37
+ "license": "Apache-2.0",
38
+ "dependencies": {
39
+ "pg": "^8.13.1",
40
+ "typescript": "^5.9.3"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.10.2",
44
+ "@types/pg": "^8.11.10"
45
+ },
46
+ "engines": {
47
+ "node": ">=18.0.0"
48
+ }
49
+ }