@workglow/postgres 0.3.1 → 0.3.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/dist/job-queue/PostgresJobStore.d.ts +2 -6
- package/dist/job-queue/PostgresJobStore.d.ts.map +1 -1
- package/dist/job-queue/PostgresMessageQueue.d.ts +1 -14
- package/dist/job-queue/PostgresMessageQueue.d.ts.map +1 -1
- package/dist/job-queue/PostgresQueueStorage.d.ts +6 -0
- package/dist/job-queue/PostgresQueueStorage.d.ts.map +1 -1
- package/dist/job-queue/browser.js +40 -153
- package/dist/job-queue/browser.js.map +7 -7
- package/dist/job-queue/createPostgresQueue.d.ts +0 -3
- package/dist/job-queue/createPostgresQueue.d.ts.map +1 -1
- package/dist/job-queue/node.js +40 -153
- package/dist/job-queue/node.js.map +7 -7
- package/dist/migrations/postgresQueueMigrations.d.ts +4 -12
- package/dist/migrations/postgresQueueMigrations.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/job-queue/node.js
CHANGED
|
@@ -139,35 +139,18 @@ import {
|
|
|
139
139
|
getPrefixIndexSuffix,
|
|
140
140
|
PostgresDialect
|
|
141
141
|
} from "@workglow/storage";
|
|
142
|
-
var JOB_STATUS_V1 = [
|
|
143
|
-
"PENDING",
|
|
144
|
-
"PROCESSING",
|
|
145
|
-
"COMPLETED",
|
|
146
|
-
"ABORTING",
|
|
147
|
-
"FAILED",
|
|
148
|
-
"DISABLED"
|
|
149
|
-
];
|
|
150
|
-
function assertJobStatusMatchesV1() {
|
|
151
|
-
const current = new Set(Object.values(JobStatus));
|
|
152
|
-
for (const v of current) {
|
|
153
|
-
if (!JOB_STATUS_V1.includes(v)) {
|
|
154
|
-
throw new Error(`JobStatus contains "${v}" which is not in JOB_STATUS_V1. ` + `Add a new migration that runs "ALTER TYPE job_status ADD VALUE IF NOT EXISTS '${v}'" ` + `instead of mutating the v1 enum literal.`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
142
|
function postgresQueueMigrations(tableName, prefixes) {
|
|
159
|
-
assertJobStatusMatchesV1();
|
|
160
143
|
const component = `queue:postgres:${tableName}`;
|
|
161
144
|
const prefixColumnsSql = buildPrefixColumnsSql(PostgresDialect, prefixes);
|
|
162
145
|
const prefixIndexPrefix = getPrefixIndexPrefix(prefixes);
|
|
163
146
|
const indexSuffix = getPrefixIndexSuffix(prefixes);
|
|
147
|
+
const enumLiteral = Object.values(JobStatus).map((v) => `'${v}'`).join(",");
|
|
164
148
|
return [
|
|
165
149
|
{
|
|
166
150
|
component,
|
|
167
151
|
version: 1,
|
|
168
152
|
description: "Create job_status enum + queue table + indexes + notify trigger",
|
|
169
153
|
async up(db) {
|
|
170
|
-
const enumLiteral = JOB_STATUS_V1.map((v) => `'${v}'`).join(",");
|
|
171
154
|
await db.query(`
|
|
172
155
|
DO $$
|
|
173
156
|
BEGIN
|
|
@@ -185,10 +168,10 @@ function postgresQueueMigrations(tableName, prefixes) {
|
|
|
185
168
|
status job_status NOT NULL default 'PENDING',
|
|
186
169
|
input jsonb NOT NULL,
|
|
187
170
|
output jsonb,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
171
|
+
attempts integer default 0,
|
|
172
|
+
max_attempts integer default 10,
|
|
173
|
+
visible_at timestamp with time zone DEFAULT now(),
|
|
174
|
+
last_attempted_at timestamp with time zone,
|
|
192
175
|
created_at timestamp with time zone DEFAULT now(),
|
|
193
176
|
deadline_at timestamp with time zone,
|
|
194
177
|
completed_at timestamp with time zone,
|
|
@@ -197,21 +180,28 @@ function postgresQueueMigrations(tableName, prefixes) {
|
|
|
197
180
|
progress real DEFAULT 0,
|
|
198
181
|
progress_message text DEFAULT '',
|
|
199
182
|
progress_details jsonb,
|
|
200
|
-
|
|
183
|
+
lease_owner text,
|
|
184
|
+
abort_requested_at timestamp with time zone,
|
|
185
|
+
lease_expires_at timestamp with time zone
|
|
201
186
|
)
|
|
202
187
|
`);
|
|
203
188
|
await db.query(`
|
|
204
189
|
CREATE INDEX IF NOT EXISTS job_fetcher${indexSuffix}_idx
|
|
205
|
-
ON ${tableName} (${prefixIndexPrefix}id, status,
|
|
190
|
+
ON ${tableName} (${prefixIndexPrefix}id, status, visible_at)
|
|
206
191
|
`);
|
|
207
192
|
await db.query(`
|
|
208
193
|
CREATE INDEX IF NOT EXISTS job_queue_fetcher${indexSuffix}_idx
|
|
209
|
-
ON ${tableName} (${prefixIndexPrefix}queue, status,
|
|
194
|
+
ON ${tableName} (${prefixIndexPrefix}queue, status, visible_at)
|
|
210
195
|
`);
|
|
211
196
|
await db.query(`
|
|
212
197
|
CREATE INDEX IF NOT EXISTS jobs_fingerprint${indexSuffix}_unique_idx
|
|
213
198
|
ON ${tableName} (${prefixIndexPrefix}queue, fingerprint, status)
|
|
214
199
|
`);
|
|
200
|
+
await db.query(`
|
|
201
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_${tableName}_fingerprint_active
|
|
202
|
+
ON ${tableName}(${prefixIndexPrefix}queue, fingerprint)
|
|
203
|
+
WHERE status IN ('PENDING','PROCESSING')
|
|
204
|
+
`);
|
|
215
205
|
const fnName = `${tableName}_notify`;
|
|
216
206
|
const trgName = `${tableName}_notify_trg`;
|
|
217
207
|
await db.query("SAVEPOINT install_notify_trigger");
|
|
@@ -249,92 +239,6 @@ function postgresQueueMigrations(tableName, prefixes) {
|
|
|
249
239
|
});
|
|
250
240
|
}
|
|
251
241
|
}
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
component,
|
|
255
|
-
version: 2,
|
|
256
|
-
description: "Add abort_requested_at and lease_expires_at columns",
|
|
257
|
-
async up(db) {
|
|
258
|
-
await db.query(`
|
|
259
|
-
ALTER TABLE ${tableName}
|
|
260
|
-
ADD COLUMN IF NOT EXISTS abort_requested_at timestamp with time zone,
|
|
261
|
-
ADD COLUMN IF NOT EXISTS lease_expires_at timestamp with time zone
|
|
262
|
-
`);
|
|
263
|
-
}
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
component,
|
|
267
|
-
version: 3,
|
|
268
|
-
description: "Rename run_after→visible_at, last_ran_at→last_attempted_at, run_attempts→attempts, max_retries→max_attempts, worker_id→lease_owner; drop run_after-keyed indexes and recreate visible_at-keyed",
|
|
269
|
-
async up(db) {
|
|
270
|
-
await db.query(`
|
|
271
|
-
DO $$
|
|
272
|
-
BEGIN
|
|
273
|
-
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='${tableName}' AND column_name='run_after' AND table_schema=current_schema()) THEN
|
|
274
|
-
EXECUTE 'ALTER TABLE ${tableName} RENAME COLUMN run_after TO visible_at';
|
|
275
|
-
END IF;
|
|
276
|
-
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='${tableName}' AND column_name='last_ran_at' AND table_schema=current_schema()) THEN
|
|
277
|
-
EXECUTE 'ALTER TABLE ${tableName} RENAME COLUMN last_ran_at TO last_attempted_at';
|
|
278
|
-
END IF;
|
|
279
|
-
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='${tableName}' AND column_name='run_attempts' AND table_schema=current_schema()) THEN
|
|
280
|
-
EXECUTE 'ALTER TABLE ${tableName} RENAME COLUMN run_attempts TO attempts';
|
|
281
|
-
END IF;
|
|
282
|
-
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='${tableName}' AND column_name='max_retries' AND table_schema=current_schema()) THEN
|
|
283
|
-
EXECUTE 'ALTER TABLE ${tableName} RENAME COLUMN max_retries TO max_attempts';
|
|
284
|
-
EXECUTE 'ALTER TABLE ${tableName} ALTER COLUMN max_attempts SET DEFAULT 10';
|
|
285
|
-
END IF;
|
|
286
|
-
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='${tableName}' AND column_name='worker_id' AND table_schema=current_schema()) THEN
|
|
287
|
-
EXECUTE 'ALTER TABLE ${tableName} RENAME COLUMN worker_id TO lease_owner';
|
|
288
|
-
END IF;
|
|
289
|
-
END $$
|
|
290
|
-
`);
|
|
291
|
-
await db.query(`DROP INDEX IF EXISTS job_fetcher${indexSuffix}_idx`);
|
|
292
|
-
await db.query(`DROP INDEX IF EXISTS job_queue_fetcher${indexSuffix}_idx`);
|
|
293
|
-
await db.query(`
|
|
294
|
-
CREATE INDEX IF NOT EXISTS job_fetcher${indexSuffix}_idx
|
|
295
|
-
ON ${tableName} (${prefixIndexPrefix}id, status, visible_at)
|
|
296
|
-
`);
|
|
297
|
-
await db.query(`
|
|
298
|
-
CREATE INDEX IF NOT EXISTS job_queue_fetcher${indexSuffix}_idx
|
|
299
|
-
ON ${tableName} (${prefixIndexPrefix}queue, status, visible_at)
|
|
300
|
-
`);
|
|
301
|
-
}
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
component,
|
|
305
|
-
version: 4,
|
|
306
|
-
description: "Add UNIQUE partial index for findActiveByFingerprint O(1) lookup + fingerprint dedup at the DB layer (H2)",
|
|
307
|
-
async up(db) {
|
|
308
|
-
await db.query(`
|
|
309
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_${tableName}_fingerprint_active
|
|
310
|
-
ON ${tableName}(${prefixIndexPrefix}queue, fingerprint)
|
|
311
|
-
WHERE status IN ('PENDING','PROCESSING')
|
|
312
|
-
`);
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
component,
|
|
317
|
-
version: 5,
|
|
318
|
-
description: "Converge idx_<table>_fingerprint_active to UNIQUE for DBs that applied the pre-edit v4 (non-unique) variant",
|
|
319
|
-
async up(db) {
|
|
320
|
-
const indexName = `idx_${tableName}_fingerprint_active`;
|
|
321
|
-
const result = await db.query(`SELECT i.indisunique
|
|
322
|
-
FROM pg_class c
|
|
323
|
-
JOIN pg_index i ON i.indexrelid = c.oid
|
|
324
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
325
|
-
WHERE c.relname = $1
|
|
326
|
-
AND n.nspname = current_schema()`, [indexName]);
|
|
327
|
-
const existing = result.rows[0];
|
|
328
|
-
if (existing && existing.indisunique) {
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
await db.query(`DROP INDEX IF EXISTS ${indexName}`);
|
|
332
|
-
await db.query(`
|
|
333
|
-
CREATE UNIQUE INDEX IF NOT EXISTS ${indexName}
|
|
334
|
-
ON ${tableName}(${prefixIndexPrefix}queue, fingerprint)
|
|
335
|
-
WHERE status IN ('PENDING','PROCESSING')
|
|
336
|
-
`);
|
|
337
|
-
}
|
|
338
242
|
}
|
|
339
243
|
];
|
|
340
244
|
}
|
|
@@ -781,6 +685,17 @@ class PostgresQueueStorage {
|
|
|
781
685
|
...prefixParams
|
|
782
686
|
]);
|
|
783
687
|
}
|
|
688
|
+
async markDisabled(id) {
|
|
689
|
+
const { conditions: prefixConditions, params: prefixParams } = this.buildPrefixWhereClause(2);
|
|
690
|
+
await this.db.query(`UPDATE ${this.tableName}
|
|
691
|
+
SET status = 'DISABLED',
|
|
692
|
+
completed_at = COALESCE(completed_at, NOW() AT TIME ZONE 'UTC'),
|
|
693
|
+
lease_owner = NULL,
|
|
694
|
+
progress = 0,
|
|
695
|
+
progress_message = '',
|
|
696
|
+
progress_details = NULL
|
|
697
|
+
WHERE id = $1 AND queue = $2${prefixConditions}`, [id, this.queueName, ...prefixParams]);
|
|
698
|
+
}
|
|
784
699
|
async deleteJobsByStatusAndAge(status, olderThanMs) {
|
|
785
700
|
const cutoffDate = new Date(Date.now() - olderThanMs).toISOString();
|
|
786
701
|
const { conditions: prefixConditions, params: prefixParams } = this.buildPrefixWhereClause(4);
|
|
@@ -1150,24 +1065,20 @@ class PostgresRateLimiterStorage {
|
|
|
1150
1065
|
// src/job-queue/PostgresMessageQueue.ts
|
|
1151
1066
|
class PostgresClaim {
|
|
1152
1067
|
core;
|
|
1153
|
-
pending;
|
|
1154
1068
|
id;
|
|
1155
1069
|
body;
|
|
1156
1070
|
attempts;
|
|
1157
1071
|
workerId;
|
|
1158
|
-
constructor(core,
|
|
1072
|
+
constructor(core, id, body, attempts, workerId) {
|
|
1159
1073
|
this.core = core;
|
|
1160
|
-
this.pending = pending;
|
|
1161
1074
|
this.id = id;
|
|
1162
1075
|
this.body = body;
|
|
1163
1076
|
this.attempts = attempts;
|
|
1164
1077
|
this.workerId = workerId;
|
|
1165
1078
|
}
|
|
1166
1079
|
async ack(result) {
|
|
1167
|
-
const buf = this.pending.get(this.id);
|
|
1168
|
-
this.pending.delete(this.id);
|
|
1169
1080
|
const current = await this.core.get(this.id) ?? this.body;
|
|
1170
|
-
const output = result !== undefined ? result :
|
|
1081
|
+
const output = result !== undefined ? result : current.output ?? null;
|
|
1171
1082
|
await this.core.finalize(this.id, {
|
|
1172
1083
|
output,
|
|
1173
1084
|
error: null,
|
|
@@ -1177,7 +1088,6 @@ class PostgresClaim {
|
|
|
1177
1088
|
});
|
|
1178
1089
|
}
|
|
1179
1090
|
async retry(opts) {
|
|
1180
|
-
this.pending.delete(this.id);
|
|
1181
1091
|
const delay = opts?.delaySeconds ?? 0;
|
|
1182
1092
|
const current = await this.core.get(this.id) ?? this.body;
|
|
1183
1093
|
await this.core.complete({
|
|
@@ -1193,12 +1103,10 @@ class PostgresClaim {
|
|
|
1193
1103
|
}
|
|
1194
1104
|
async fail(opts) {
|
|
1195
1105
|
opts?.permanent;
|
|
1196
|
-
const buf = this.pending.get(this.id);
|
|
1197
|
-
this.pending.delete(this.id);
|
|
1198
1106
|
const current = await this.core.get(this.id) ?? this.body;
|
|
1199
|
-
const error = opts?.error !== undefined ? opts.error :
|
|
1200
|
-
const errorCode = opts?.errorCode !== undefined ? opts.errorCode :
|
|
1201
|
-
const abortRequested = opts?.abortRequested
|
|
1107
|
+
const error = opts?.error !== undefined ? opts.error : current.error ?? null;
|
|
1108
|
+
const errorCode = opts?.errorCode !== undefined ? opts.errorCode : current.error_code ?? null;
|
|
1109
|
+
const abortRequested = opts?.abortRequested === true;
|
|
1202
1110
|
await this.core.finalize(this.id, {
|
|
1203
1111
|
error,
|
|
1204
1112
|
error_code: errorCode,
|
|
@@ -1211,7 +1119,6 @@ class PostgresClaim {
|
|
|
1211
1119
|
await this.core.extendLease(this.id, this.workerId, ms);
|
|
1212
1120
|
}
|
|
1213
1121
|
async disable() {
|
|
1214
|
-
this.pending.delete(this.id);
|
|
1215
1122
|
const current = await this.core.get(this.id);
|
|
1216
1123
|
const completedAt = current?.completed_at ?? new Date().toISOString();
|
|
1217
1124
|
await this.core.finalize(this.id, {
|
|
@@ -1228,10 +1135,8 @@ class PostgresClaim {
|
|
|
1228
1135
|
class PostgresMessageQueue {
|
|
1229
1136
|
scope;
|
|
1230
1137
|
core;
|
|
1231
|
-
|
|
1232
|
-
constructor(core, pending) {
|
|
1138
|
+
constructor(core) {
|
|
1233
1139
|
this.core = core;
|
|
1234
|
-
this.pending = pending;
|
|
1235
1140
|
this.scope = core.scope;
|
|
1236
1141
|
}
|
|
1237
1142
|
async send(body, opts) {
|
|
@@ -1251,12 +1156,11 @@ class PostgresMessageQueue {
|
|
|
1251
1156
|
const job = await this.core.next(opts.workerId, { leaseMs: opts.leaseMs });
|
|
1252
1157
|
if (!job)
|
|
1253
1158
|
break;
|
|
1254
|
-
claims.push(new PostgresClaim(this.core,
|
|
1159
|
+
claims.push(new PostgresClaim(this.core, job.id, job, job.attempts ?? 0, opts.workerId));
|
|
1255
1160
|
}
|
|
1256
1161
|
return claims;
|
|
1257
1162
|
}
|
|
1258
1163
|
async releaseClaim(id) {
|
|
1259
|
-
this.pending.delete(id);
|
|
1260
1164
|
await this.core.releaseClaim(id);
|
|
1261
1165
|
}
|
|
1262
1166
|
async migrate() {
|
|
@@ -1290,10 +1194,8 @@ function applySendOptions(body, opts) {
|
|
|
1290
1194
|
// src/job-queue/PostgresJobStore.ts
|
|
1291
1195
|
class PostgresJobStore {
|
|
1292
1196
|
core;
|
|
1293
|
-
|
|
1294
|
-
constructor(core, pending) {
|
|
1197
|
+
constructor(core) {
|
|
1295
1198
|
this.core = core;
|
|
1296
|
-
this.pending = pending;
|
|
1297
1199
|
}
|
|
1298
1200
|
get(id) {
|
|
1299
1201
|
return this.core.get(id);
|
|
@@ -1313,27 +1215,13 @@ class PostgresJobStore {
|
|
|
1313
1215
|
async saveProgress(id, progress, message, details) {
|
|
1314
1216
|
await this.core.saveProgress(id, progress, message, details);
|
|
1315
1217
|
}
|
|
1316
|
-
async saveResult(id, output) {
|
|
1317
|
-
const buf = this.pending.get(id) ?? {};
|
|
1318
|
-
buf.output = output ?? null;
|
|
1319
|
-
this.pending.set(id, buf);
|
|
1320
|
-
}
|
|
1321
|
-
async saveError(id, error, errorCode, abortRequested) {
|
|
1322
|
-
const buf = this.pending.get(id) ?? {};
|
|
1323
|
-
buf.error = error;
|
|
1324
|
-
buf.errorCode = errorCode;
|
|
1325
|
-
buf.abortRequested = abortRequested;
|
|
1326
|
-
this.pending.set(id, buf);
|
|
1327
|
-
}
|
|
1328
1218
|
async deleteByStatusAndAge(status, olderThanMs) {
|
|
1329
1219
|
await this.core.deleteJobsByStatusAndAge(status, olderThanMs);
|
|
1330
1220
|
}
|
|
1331
1221
|
async delete(id) {
|
|
1332
|
-
this.pending.delete(id);
|
|
1333
1222
|
await this.core.delete(id);
|
|
1334
1223
|
}
|
|
1335
1224
|
async deleteAll() {
|
|
1336
|
-
this.pending.clear();
|
|
1337
1225
|
await this.core.deleteAll();
|
|
1338
1226
|
}
|
|
1339
1227
|
async abort(id) {
|
|
@@ -1359,13 +1247,14 @@ class PostgresJobStore {
|
|
|
1359
1247
|
return this.core.getMany(ids);
|
|
1360
1248
|
}
|
|
1361
1249
|
async completeWithResult(id, result) {
|
|
1362
|
-
this.pending.delete(id);
|
|
1363
1250
|
await this.core.completeWithResult(id, result);
|
|
1364
1251
|
}
|
|
1365
1252
|
async failWithError(id, opts) {
|
|
1366
|
-
this.pending.delete(id);
|
|
1367
1253
|
await this.core.failWithError(id, opts);
|
|
1368
1254
|
}
|
|
1255
|
+
async markDisabled(id) {
|
|
1256
|
+
await this.core.markDisabled(id);
|
|
1257
|
+
}
|
|
1369
1258
|
async markEnqueueDeferred(id, opts) {
|
|
1370
1259
|
await this.core.finalize(id, {
|
|
1371
1260
|
visible_at: opts.visible_at.toISOString(),
|
|
@@ -1376,11 +1265,9 @@ class PostgresJobStore {
|
|
|
1376
1265
|
// src/job-queue/createPostgresQueue.ts
|
|
1377
1266
|
function createPostgresQueue(queueName, pool, opts) {
|
|
1378
1267
|
const core = new PostgresQueueStorage(pool, queueName, opts);
|
|
1379
|
-
const pending = new Map;
|
|
1380
1268
|
return {
|
|
1381
|
-
messageQueue: new PostgresMessageQueue(core
|
|
1382
|
-
jobStore: new PostgresJobStore(core
|
|
1383
|
-
core
|
|
1269
|
+
messageQueue: new PostgresMessageQueue(core),
|
|
1270
|
+
jobStore: new PostgresJobStore(core)
|
|
1384
1271
|
};
|
|
1385
1272
|
}
|
|
1386
1273
|
export {
|
|
@@ -1396,4 +1283,4 @@ export {
|
|
|
1396
1283
|
POSTGRES_QUEUE_STORAGE
|
|
1397
1284
|
};
|
|
1398
1285
|
|
|
1399
|
-
//# debugId=
|
|
1286
|
+
//# debugId=9FAE6AD52527D77864756E2164756E21
|