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 +109 -0
- package/dist/cjs/index.js +527 -0
- package/dist/index.d.ts +171 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +488 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|