@workglow/supabase 0.2.35 → 0.2.37
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/SupabaseJobStore.d.ts +29 -0
- package/dist/job-queue/SupabaseJobStore.d.ts.map +1 -0
- package/dist/job-queue/SupabaseMessageQueue.d.ts +38 -0
- package/dist/job-queue/SupabaseMessageQueue.d.ts.map +1 -0
- package/dist/job-queue/SupabaseQueueStorage.d.ts +39 -9
- package/dist/job-queue/SupabaseQueueStorage.d.ts.map +1 -1
- package/dist/job-queue/SupabaseRateLimiterStorage.d.ts +1 -2
- package/dist/job-queue/SupabaseRateLimiterStorage.d.ts.map +1 -1
- package/dist/job-queue/browser.js +360 -40
- package/dist/job-queue/browser.js.map +8 -5
- package/dist/job-queue/common.d.ts +3 -0
- package/dist/job-queue/common.d.ts.map +1 -1
- package/dist/job-queue/createSupabaseQueue.d.ts +22 -0
- package/dist/job-queue/createSupabaseQueue.d.ts.map +1 -0
- package/dist/job-queue/node.js +360 -40
- package/dist/job-queue/node.js.map +8 -5
- package/dist/storage/SupabaseKvStorage.d.ts +1 -1
- package/dist/storage/SupabaseKvStorage.d.ts.map +1 -1
- package/dist/storage/SupabaseTabularStorage.d.ts +1 -1
- package/dist/storage/SupabaseTabularStorage.d.ts.map +1 -1
- package/dist/storage/browser.js +10 -10
- package/dist/storage/browser.js.map +4 -4
- package/dist/storage/node.js +10 -10
- package/dist/storage/node.js.map +4 -4
- package/package.json +7 -7
package/dist/job-queue/node.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/job-queue/SupabaseQueueStorage.ts
|
|
2
|
-
import {
|
|
2
|
+
import { JobStatus, validateLeaseMs } from "@workglow/job-queue";
|
|
3
3
|
import { PollingSubscriptionManager } from "@workglow/storage";
|
|
4
|
-
import {
|
|
4
|
+
import { createServiceToken, deepEqual, makeFingerprint, uuid4 } from "@workglow/util";
|
|
5
5
|
var SUPABASE_QUEUE_STORAGE = createServiceToken("jobqueue.storage.supabase");
|
|
6
6
|
|
|
7
7
|
class SupabaseQueueStorage {
|
|
@@ -81,7 +81,8 @@ class SupabaseQueueStorage {
|
|
|
81
81
|
return value.replace(/'/g, "''");
|
|
82
82
|
}
|
|
83
83
|
async migrate() {
|
|
84
|
-
const
|
|
84
|
+
const enumValues = [...Object.values(JobStatus), "ABORTING"].filter((v, i, a) => a.indexOf(v) === i).map((v) => `'${v}'`).join(",");
|
|
85
|
+
const createTypeSql = `CREATE TYPE job_status AS ENUM (${enumValues})`;
|
|
85
86
|
const { error: typeError } = await this.client.rpc("exec_sql", { query: createTypeSql });
|
|
86
87
|
if (typeError && typeError.code !== "42710") {
|
|
87
88
|
throw typeError;
|
|
@@ -99,10 +100,10 @@ class SupabaseQueueStorage {
|
|
|
99
100
|
status job_status NOT NULL default 'PENDING',
|
|
100
101
|
input jsonb NOT NULL,
|
|
101
102
|
output jsonb,
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
attempts integer default 0,
|
|
104
|
+
max_attempts integer default 10,
|
|
105
|
+
visible_at timestamp with time zone DEFAULT now(),
|
|
106
|
+
last_attempted_at timestamp with time zone,
|
|
106
107
|
created_at timestamp with time zone DEFAULT now(),
|
|
107
108
|
deadline_at timestamp with time zone,
|
|
108
109
|
completed_at timestamp with time zone,
|
|
@@ -111,7 +112,9 @@ class SupabaseQueueStorage {
|
|
|
111
112
|
progress real DEFAULT 0,
|
|
112
113
|
progress_message text DEFAULT '',
|
|
113
114
|
progress_details jsonb,
|
|
114
|
-
|
|
115
|
+
lease_owner text,
|
|
116
|
+
abort_requested_at timestamp with time zone,
|
|
117
|
+
lease_expires_at timestamp with time zone
|
|
115
118
|
)`;
|
|
116
119
|
const { error: tableError } = await this.client.rpc("exec_sql", { query: createTableSql });
|
|
117
120
|
if (tableError) {
|
|
@@ -119,9 +122,24 @@ class SupabaseQueueStorage {
|
|
|
119
122
|
throw tableError;
|
|
120
123
|
}
|
|
121
124
|
}
|
|
125
|
+
const alterSqls = [
|
|
126
|
+
`ALTER TABLE ${this.tableName} ADD COLUMN IF NOT EXISTS abort_requested_at timestamp with time zone`,
|
|
127
|
+
`ALTER TABLE ${this.tableName} ADD COLUMN IF NOT EXISTS lease_expires_at timestamp with time zone`,
|
|
128
|
+
`ALTER TABLE ${this.tableName} RENAME COLUMN run_after TO visible_at`,
|
|
129
|
+
`ALTER TABLE ${this.tableName} RENAME COLUMN last_ran_at TO last_attempted_at`,
|
|
130
|
+
`ALTER TABLE ${this.tableName} RENAME COLUMN run_attempts TO attempts`,
|
|
131
|
+
`ALTER TABLE ${this.tableName} RENAME COLUMN max_retries TO max_attempts`,
|
|
132
|
+
`ALTER TABLE ${this.tableName} RENAME COLUMN worker_id TO lease_owner`
|
|
133
|
+
];
|
|
134
|
+
for (const sql of alterSqls) {
|
|
135
|
+
const { error } = await this.client.rpc("exec_sql", { query: sql });
|
|
136
|
+
if (error && error.code !== "42703") {
|
|
137
|
+
throw new Error(`Failed to rename column: ${error.message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
122
140
|
const indexes = [
|
|
123
|
-
`CREATE INDEX IF NOT EXISTS job_fetcher${indexSuffix}_idx ON ${this.tableName} (${prefixIndexPrefix}id, status,
|
|
124
|
-
`CREATE INDEX IF NOT EXISTS job_queue_fetcher${indexSuffix}_idx ON ${this.tableName} (${prefixIndexPrefix}queue, status,
|
|
141
|
+
`CREATE INDEX IF NOT EXISTS job_fetcher${indexSuffix}_idx ON ${this.tableName} (${prefixIndexPrefix}id, status, visible_at)`,
|
|
142
|
+
`CREATE INDEX IF NOT EXISTS job_queue_fetcher${indexSuffix}_idx ON ${this.tableName} (${prefixIndexPrefix}queue, status, visible_at)`,
|
|
125
143
|
`CREATE INDEX IF NOT EXISTS jobs_fingerprint${indexSuffix}_unique_idx ON ${this.tableName} (${prefixIndexPrefix}queue, fingerprint, status)`
|
|
126
144
|
];
|
|
127
145
|
for (const indexSql of indexes) {
|
|
@@ -141,17 +159,17 @@ class SupabaseQueueStorage {
|
|
|
141
159
|
job.progress_message = "";
|
|
142
160
|
job.progress_details = null;
|
|
143
161
|
job.created_at = now;
|
|
144
|
-
job.
|
|
162
|
+
job.visible_at = now;
|
|
145
163
|
const prefixInsertValues = this.getPrefixInsertValues();
|
|
146
164
|
const { data, error } = await this.client.from(this.tableName).insert({
|
|
147
165
|
...prefixInsertValues,
|
|
148
166
|
queue: job.queue,
|
|
149
167
|
fingerprint: job.fingerprint,
|
|
150
168
|
input: job.input,
|
|
151
|
-
|
|
169
|
+
visible_at: job.visible_at,
|
|
152
170
|
created_at: job.created_at,
|
|
153
171
|
deadline_at: job.deadline_at,
|
|
154
|
-
|
|
172
|
+
max_attempts: job.max_attempts,
|
|
155
173
|
job_run_id: job.job_run_id,
|
|
156
174
|
progress: job.progress,
|
|
157
175
|
progress_message: job.progress_message,
|
|
@@ -179,12 +197,14 @@ class SupabaseQueueStorage {
|
|
|
179
197
|
num = Number(num) || 100;
|
|
180
198
|
let query = this.client.from(this.tableName).select("*").eq("queue", this.queueName).eq("status", status);
|
|
181
199
|
query = this.applyPrefixFilters(query);
|
|
182
|
-
const { data, error } = await query.order("
|
|
200
|
+
const { data, error } = await query.order("visible_at", { ascending: true }).limit(num);
|
|
183
201
|
if (error)
|
|
184
202
|
throw error;
|
|
185
203
|
return data ?? [];
|
|
186
204
|
}
|
|
187
|
-
async next(workerId) {
|
|
205
|
+
async next(workerId, opts) {
|
|
206
|
+
const leaseMs = opts?.leaseMs ?? 30000;
|
|
207
|
+
validateLeaseMs(leaseMs, "leaseMs");
|
|
188
208
|
const prefixConditions = this.buildPrefixWhereSql();
|
|
189
209
|
const validatedQueueName = this.validateSqlValue(this.queueName, "queueName");
|
|
190
210
|
const validatedWorkerId = this.validateSqlValue(workerId, "workerId");
|
|
@@ -192,15 +212,27 @@ class SupabaseQueueStorage {
|
|
|
192
212
|
const escapedWorkerId = this.escapeSqlString(validatedWorkerId);
|
|
193
213
|
const sql = `
|
|
194
214
|
UPDATE ${this.tableName}
|
|
195
|
-
SET status = '${JobStatus.PROCESSING}',
|
|
215
|
+
SET status = '${JobStatus.PROCESSING}',
|
|
216
|
+
last_attempted_at = NOW() AT TIME ZONE 'UTC',
|
|
217
|
+
lease_owner = '${escapedWorkerId}',
|
|
218
|
+
lease_expires_at = NOW() AT TIME ZONE 'UTC' + (${Number(leaseMs)} * INTERVAL '1 millisecond'),
|
|
219
|
+
-- Lease-expiry reclaim consumes one attempt against max_attempts;
|
|
220
|
+
-- PENDING claims do not (the worker's validateJobState will FAIL
|
|
221
|
+
-- the job when attempts >= max_attempts at next-step time).
|
|
222
|
+
attempts = CASE WHEN status = '${JobStatus.PROCESSING}' THEN attempts + 1 ELSE attempts END,
|
|
223
|
+
-- Always clear stale abort_requested_at on (re)claim so a flag set
|
|
224
|
+
-- by an earlier worker doesn't immediately abort the new lease.
|
|
225
|
+
abort_requested_at = NULL
|
|
196
226
|
WHERE id = (
|
|
197
227
|
SELECT id
|
|
198
228
|
FROM ${this.tableName}
|
|
199
229
|
WHERE queue = '${escapedQueueName}'
|
|
200
|
-
AND
|
|
230
|
+
AND (
|
|
231
|
+
(status = '${JobStatus.PENDING}' AND visible_at <= NOW() AT TIME ZONE 'UTC')
|
|
232
|
+
OR (status = '${JobStatus.PROCESSING}' AND (lease_expires_at IS NULL OR lease_expires_at < NOW() AT TIME ZONE 'UTC'))
|
|
233
|
+
)
|
|
201
234
|
${prefixConditions}
|
|
202
|
-
|
|
203
|
-
ORDER BY run_after ASC
|
|
235
|
+
ORDER BY visible_at ASC
|
|
204
236
|
FOR UPDATE SKIP LOCKED
|
|
205
237
|
LIMIT 1
|
|
206
238
|
)
|
|
@@ -213,6 +245,31 @@ class SupabaseQueueStorage {
|
|
|
213
245
|
}
|
|
214
246
|
return data[0];
|
|
215
247
|
}
|
|
248
|
+
async extendLease(id, workerId, ms) {
|
|
249
|
+
validateLeaseMs(ms, "ms");
|
|
250
|
+
const validatedWorkerId = this.validateSqlValue(workerId, "workerId");
|
|
251
|
+
const escapedWorkerId = this.escapeSqlString(validatedWorkerId);
|
|
252
|
+
const numericId = Number(id);
|
|
253
|
+
if (!Number.isFinite(numericId)) {
|
|
254
|
+
throw new Error(`Invalid job id: ${id}`);
|
|
255
|
+
}
|
|
256
|
+
const prefixConditions = this.buildPrefixWhereSql();
|
|
257
|
+
const sql = `
|
|
258
|
+
UPDATE ${this.tableName}
|
|
259
|
+
SET lease_expires_at = NOW() AT TIME ZONE 'UTC' + (${Number(ms)} * INTERVAL '1 millisecond')
|
|
260
|
+
WHERE id = ${numericId}
|
|
261
|
+
AND queue = '${this.escapeSqlString(this.validateSqlValue(this.queueName, "queueName"))}'
|
|
262
|
+
AND lease_owner = '${escapedWorkerId}'
|
|
263
|
+
AND status = '${JobStatus.PROCESSING}'
|
|
264
|
+
${prefixConditions}
|
|
265
|
+
RETURNING id`;
|
|
266
|
+
const { data, error } = await this.client.rpc("exec_sql", { query: sql });
|
|
267
|
+
if (error)
|
|
268
|
+
throw error;
|
|
269
|
+
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
270
|
+
throw new Error(`extendLease failed: job ${String(id)} is not PROCESSING or lease is not owned by worker ${workerId}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
216
273
|
async size(status = JobStatus.PENDING) {
|
|
217
274
|
let query = this.client.from(this.tableName).select("*", { count: "exact", head: true }).eq("queue", this.queueName).eq("status", status);
|
|
218
275
|
query = this.applyPrefixFilters(query);
|
|
@@ -238,7 +295,7 @@ class SupabaseQueueStorage {
|
|
|
238
295
|
progress_message: "",
|
|
239
296
|
progress_details: null,
|
|
240
297
|
completed_at: now,
|
|
241
|
-
|
|
298
|
+
last_attempted_at: now
|
|
242
299
|
}).eq("id", jobDetails.id).eq("queue", this.queueName);
|
|
243
300
|
query2 = this.applyPrefixFilters(query2);
|
|
244
301
|
const { error: error2 } = await query2;
|
|
@@ -246,25 +303,25 @@ class SupabaseQueueStorage {
|
|
|
246
303
|
throw error2;
|
|
247
304
|
return;
|
|
248
305
|
}
|
|
249
|
-
let getQuery = this.client.from(this.tableName).select("
|
|
306
|
+
let getQuery = this.client.from(this.tableName).select("attempts, max_attempts").eq("id", jobDetails.id).eq("queue", this.queueName);
|
|
250
307
|
getQuery = this.applyPrefixFilters(getQuery);
|
|
251
308
|
const { data: current, error: getError } = await getQuery.single();
|
|
252
309
|
if (getError)
|
|
253
310
|
throw getError;
|
|
254
|
-
const currentAttempts = current?.
|
|
255
|
-
const
|
|
311
|
+
const currentAttempts = current?.attempts ?? 0;
|
|
312
|
+
const maxAttempts = current?.max_attempts ?? jobDetails.max_attempts ?? 10;
|
|
256
313
|
const nextAttempts = currentAttempts + 1;
|
|
257
314
|
if (jobDetails.status === JobStatus.PENDING) {
|
|
258
|
-
if (nextAttempts
|
|
315
|
+
if (nextAttempts >= maxAttempts) {
|
|
259
316
|
let failQuery = this.client.from(this.tableName).update({
|
|
260
317
|
status: JobStatus.FAILED,
|
|
261
|
-
error: "Max
|
|
262
|
-
error_code: "
|
|
318
|
+
error: "Max attempts reached",
|
|
319
|
+
error_code: "MAX_ATTEMPTS_REACHED",
|
|
263
320
|
progress: 100,
|
|
264
321
|
progress_message: "",
|
|
265
322
|
progress_details: null,
|
|
266
323
|
completed_at: now,
|
|
267
|
-
|
|
324
|
+
last_attempted_at: now
|
|
268
325
|
}).eq("id", jobDetails.id).eq("queue", this.queueName);
|
|
269
326
|
failQuery = this.applyPrefixFilters(failQuery);
|
|
270
327
|
const { error: failError } = await failQuery;
|
|
@@ -276,12 +333,13 @@ class SupabaseQueueStorage {
|
|
|
276
333
|
error: jobDetails.error ?? null,
|
|
277
334
|
error_code: jobDetails.error_code ?? null,
|
|
278
335
|
status: jobDetails.status,
|
|
279
|
-
|
|
336
|
+
visible_at: jobDetails.visible_at,
|
|
280
337
|
progress: 0,
|
|
281
338
|
progress_message: "",
|
|
282
339
|
progress_details: null,
|
|
283
|
-
|
|
284
|
-
|
|
340
|
+
attempts: nextAttempts,
|
|
341
|
+
last_attempted_at: now,
|
|
342
|
+
abort_requested_at: null
|
|
285
343
|
}).eq("id", jobDetails.id).eq("queue", this.queueName);
|
|
286
344
|
query2 = this.applyPrefixFilters(query2);
|
|
287
345
|
const { error: error2 } = await query2;
|
|
@@ -298,9 +356,9 @@ class SupabaseQueueStorage {
|
|
|
298
356
|
progress: 100,
|
|
299
357
|
progress_message: "",
|
|
300
358
|
progress_details: null,
|
|
301
|
-
|
|
359
|
+
attempts: nextAttempts,
|
|
302
360
|
completed_at: now,
|
|
303
|
-
|
|
361
|
+
last_attempted_at: now
|
|
304
362
|
}).eq("id", jobDetails.id).eq("queue", this.queueName);
|
|
305
363
|
query2 = this.applyPrefixFilters(query2);
|
|
306
364
|
const { error: error2 } = await query2;
|
|
@@ -313,28 +371,60 @@ class SupabaseQueueStorage {
|
|
|
313
371
|
output: jobDetails.output ?? null,
|
|
314
372
|
error: jobDetails.error ?? null,
|
|
315
373
|
error_code: jobDetails.error_code ?? null,
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
374
|
+
visible_at: jobDetails.visible_at ?? null,
|
|
375
|
+
attempts: nextAttempts,
|
|
376
|
+
last_attempted_at: now
|
|
319
377
|
}).eq("id", jobDetails.id).eq("queue", this.queueName);
|
|
320
378
|
query = this.applyPrefixFilters(query);
|
|
321
379
|
const { error } = await query;
|
|
322
380
|
if (error)
|
|
323
381
|
throw error;
|
|
324
382
|
}
|
|
325
|
-
async
|
|
383
|
+
async releaseClaim(jobId) {
|
|
326
384
|
let query = this.client.from(this.tableName).update({
|
|
327
385
|
status: JobStatus.PENDING,
|
|
328
|
-
|
|
386
|
+
lease_owner: null,
|
|
329
387
|
progress: 0,
|
|
330
388
|
progress_message: "",
|
|
331
|
-
progress_details: null
|
|
389
|
+
progress_details: null,
|
|
390
|
+
abort_requested_at: null
|
|
332
391
|
}).eq("id", jobId).eq("queue", this.queueName);
|
|
333
392
|
query = this.applyPrefixFilters(query);
|
|
334
393
|
const { error } = await query;
|
|
335
394
|
if (error)
|
|
336
395
|
throw error;
|
|
337
396
|
}
|
|
397
|
+
async finalize(id, fields) {
|
|
398
|
+
const patch = {};
|
|
399
|
+
if ("output" in fields)
|
|
400
|
+
patch.output = fields.output ?? null;
|
|
401
|
+
if ("error" in fields)
|
|
402
|
+
patch.error = fields.error ?? null;
|
|
403
|
+
if ("error_code" in fields)
|
|
404
|
+
patch.error_code = fields.error_code ?? null;
|
|
405
|
+
if ("status" in fields)
|
|
406
|
+
patch.status = fields.status;
|
|
407
|
+
if ("completed_at" in fields)
|
|
408
|
+
patch.completed_at = fields.completed_at ?? null;
|
|
409
|
+
if ("abort_requested_at" in fields) {
|
|
410
|
+
patch.abort_requested_at = fields.abort_requested_at ?? null;
|
|
411
|
+
}
|
|
412
|
+
if ("lease_owner" in fields)
|
|
413
|
+
patch.lease_owner = fields.lease_owner ?? null;
|
|
414
|
+
if ("progress" in fields)
|
|
415
|
+
patch.progress = fields.progress ?? 0;
|
|
416
|
+
if ("progress_message" in fields)
|
|
417
|
+
patch.progress_message = fields.progress_message ?? "";
|
|
418
|
+
if ("progress_details" in fields)
|
|
419
|
+
patch.progress_details = fields.progress_details ?? null;
|
|
420
|
+
if (Object.keys(patch).length === 0)
|
|
421
|
+
return;
|
|
422
|
+
let query = this.client.from(this.tableName).update(patch).eq("id", id).eq("queue", this.queueName);
|
|
423
|
+
query = this.applyPrefixFilters(query);
|
|
424
|
+
const { error } = await query;
|
|
425
|
+
if (error)
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
338
428
|
async deleteAll() {
|
|
339
429
|
let query = this.client.from(this.tableName).delete().eq("queue", this.queueName);
|
|
340
430
|
query = this.applyPrefixFilters(query);
|
|
@@ -355,7 +445,28 @@ class SupabaseQueueStorage {
|
|
|
355
445
|
return data?.output ?? null;
|
|
356
446
|
}
|
|
357
447
|
async abort(jobId) {
|
|
358
|
-
|
|
448
|
+
const now = new Date().toISOString();
|
|
449
|
+
{
|
|
450
|
+
let query = this.client.from(this.tableName).update({
|
|
451
|
+
status: JobStatus.FAILED,
|
|
452
|
+
abort_requested_at: now,
|
|
453
|
+
completed_at: now
|
|
454
|
+
}).eq("id", jobId).eq("queue", this.queueName).eq("status", JobStatus.PENDING);
|
|
455
|
+
query = this.applyPrefixFilters(query);
|
|
456
|
+
const { error } = await query;
|
|
457
|
+
if (error)
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
{
|
|
461
|
+
let query = this.client.from(this.tableName).update({ abort_requested_at: now }).eq("id", jobId).eq("queue", this.queueName).eq("status", JobStatus.PROCESSING);
|
|
462
|
+
query = this.applyPrefixFilters(query);
|
|
463
|
+
const { error } = await query;
|
|
464
|
+
if (error)
|
|
465
|
+
throw error;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async saveStatus(jobId, status) {
|
|
469
|
+
let query = this.client.from(this.tableName).update({ status }).eq("id", jobId).eq("queue", this.queueName);
|
|
359
470
|
query = this.applyPrefixFilters(query);
|
|
360
471
|
const { error } = await query;
|
|
361
472
|
if (error)
|
|
@@ -761,11 +872,220 @@ class SupabaseRateLimiterStorage {
|
|
|
761
872
|
throw nextError;
|
|
762
873
|
}
|
|
763
874
|
}
|
|
875
|
+
// src/job-queue/SupabaseMessageQueue.ts
|
|
876
|
+
class SupabaseClaim {
|
|
877
|
+
core;
|
|
878
|
+
pending;
|
|
879
|
+
id;
|
|
880
|
+
body;
|
|
881
|
+
attempts;
|
|
882
|
+
workerId;
|
|
883
|
+
constructor(core, pending, id, body, attempts, workerId) {
|
|
884
|
+
this.core = core;
|
|
885
|
+
this.pending = pending;
|
|
886
|
+
this.id = id;
|
|
887
|
+
this.body = body;
|
|
888
|
+
this.attempts = attempts;
|
|
889
|
+
this.workerId = workerId;
|
|
890
|
+
}
|
|
891
|
+
async ack(result) {
|
|
892
|
+
const buf = this.pending.get(this.id);
|
|
893
|
+
this.pending.delete(this.id);
|
|
894
|
+
const current = await this.core.get(this.id) ?? this.body;
|
|
895
|
+
const output = result !== undefined ? result : buf?.output !== undefined ? buf.output : current.output ?? null;
|
|
896
|
+
await this.core.finalize(this.id, {
|
|
897
|
+
output,
|
|
898
|
+
error: null,
|
|
899
|
+
error_code: null,
|
|
900
|
+
status: "COMPLETED",
|
|
901
|
+
completed_at: current.completed_at ?? new Date().toISOString()
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
async retry(opts) {
|
|
905
|
+
this.pending.delete(this.id);
|
|
906
|
+
const delay = opts?.delaySeconds ?? 0;
|
|
907
|
+
const current = await this.core.get(this.id) ?? this.body;
|
|
908
|
+
await this.core.complete({
|
|
909
|
+
...current,
|
|
910
|
+
status: "PENDING",
|
|
911
|
+
lease_owner: null,
|
|
912
|
+
lease_expires_at: null,
|
|
913
|
+
visible_at: new Date(Date.now() + delay * 1000).toISOString(),
|
|
914
|
+
progress: 0,
|
|
915
|
+
progress_message: "",
|
|
916
|
+
progress_details: null
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
async fail(opts) {
|
|
920
|
+
opts?.permanent;
|
|
921
|
+
const buf = this.pending.get(this.id);
|
|
922
|
+
this.pending.delete(this.id);
|
|
923
|
+
const current = await this.core.get(this.id) ?? this.body;
|
|
924
|
+
const error = opts?.error !== undefined ? opts.error : buf?.error !== undefined ? buf.error : current.error ?? null;
|
|
925
|
+
const errorCode = opts?.errorCode !== undefined ? opts.errorCode : buf?.errorCode !== undefined ? buf.errorCode : current.error_code ?? null;
|
|
926
|
+
const abortRequested = opts?.abortRequested !== undefined ? opts.abortRequested : buf?.abortRequested ?? false;
|
|
927
|
+
await this.core.finalize(this.id, {
|
|
928
|
+
error,
|
|
929
|
+
error_code: errorCode,
|
|
930
|
+
abort_requested_at: abortRequested ? current.abort_requested_at ?? new Date().toISOString() : current.abort_requested_at ?? null,
|
|
931
|
+
status: "FAILED",
|
|
932
|
+
completed_at: current.completed_at ?? new Date().toISOString()
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
async extendLease(ms) {
|
|
936
|
+
await this.core.extendLease(this.id, this.workerId, ms);
|
|
937
|
+
}
|
|
938
|
+
async disable() {
|
|
939
|
+
this.pending.delete(this.id);
|
|
940
|
+
const current = await this.core.get(this.id);
|
|
941
|
+
const completedAt = current?.completed_at ?? new Date().toISOString();
|
|
942
|
+
await this.core.finalize(this.id, {
|
|
943
|
+
status: "DISABLED",
|
|
944
|
+
completed_at: completedAt,
|
|
945
|
+
lease_owner: null,
|
|
946
|
+
progress: 0,
|
|
947
|
+
progress_message: "",
|
|
948
|
+
progress_details: null
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
class SupabaseMessageQueue {
|
|
954
|
+
scope;
|
|
955
|
+
core;
|
|
956
|
+
pending;
|
|
957
|
+
constructor(core, pending) {
|
|
958
|
+
this.core = core;
|
|
959
|
+
this.pending = pending;
|
|
960
|
+
this.scope = core.scope;
|
|
961
|
+
}
|
|
962
|
+
async send(body, opts) {
|
|
963
|
+
return this.core.add(applySendOptions(body, opts));
|
|
964
|
+
}
|
|
965
|
+
async sendBatch(bodies, opts) {
|
|
966
|
+
const ids = [];
|
|
967
|
+
for (const body of bodies) {
|
|
968
|
+
ids.push(await this.send(body, opts));
|
|
969
|
+
}
|
|
970
|
+
return ids;
|
|
971
|
+
}
|
|
972
|
+
async receive(opts) {
|
|
973
|
+
const max = Math.max(1, opts.max ?? 1);
|
|
974
|
+
const claims = [];
|
|
975
|
+
while (claims.length < max) {
|
|
976
|
+
const job = await this.core.next(opts.workerId, { leaseMs: opts.leaseMs });
|
|
977
|
+
if (!job)
|
|
978
|
+
break;
|
|
979
|
+
claims.push(new SupabaseClaim(this.core, this.pending, job.id, job, job.attempts ?? 0, opts.workerId));
|
|
980
|
+
}
|
|
981
|
+
return claims;
|
|
982
|
+
}
|
|
983
|
+
async releaseClaim(id) {
|
|
984
|
+
this.pending.delete(id);
|
|
985
|
+
await this.core.releaseClaim(id);
|
|
986
|
+
}
|
|
987
|
+
async migrate() {
|
|
988
|
+
await this.core.migrate();
|
|
989
|
+
}
|
|
990
|
+
getMigrations() {
|
|
991
|
+
return this.core.getMigrations();
|
|
992
|
+
}
|
|
993
|
+
subscribeToChanges(callback, options) {
|
|
994
|
+
return this.core.subscribeToChanges(callback, options);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function applySendOptions(body, opts) {
|
|
998
|
+
if (!opts)
|
|
999
|
+
return body;
|
|
1000
|
+
const out = { ...body };
|
|
1001
|
+
if (opts.delaySeconds != null) {
|
|
1002
|
+
out.visible_at = new Date(Date.now() + opts.delaySeconds * 1000).toISOString();
|
|
1003
|
+
}
|
|
1004
|
+
if (opts.timeoutSeconds != null) {
|
|
1005
|
+
out.deadline_at = new Date(Date.now() + opts.timeoutSeconds * 1000).toISOString();
|
|
1006
|
+
}
|
|
1007
|
+
if (opts.fingerprint != null)
|
|
1008
|
+
out.fingerprint = opts.fingerprint;
|
|
1009
|
+
if (opts.jobRunId != null)
|
|
1010
|
+
out.job_run_id = opts.jobRunId;
|
|
1011
|
+
if (opts.maxAttempts != null)
|
|
1012
|
+
out.max_attempts = opts.maxAttempts;
|
|
1013
|
+
return out;
|
|
1014
|
+
}
|
|
1015
|
+
// src/job-queue/SupabaseJobStore.ts
|
|
1016
|
+
class SupabaseJobStore {
|
|
1017
|
+
core;
|
|
1018
|
+
pending;
|
|
1019
|
+
constructor(core, pending) {
|
|
1020
|
+
this.core = core;
|
|
1021
|
+
this.pending = pending;
|
|
1022
|
+
}
|
|
1023
|
+
get(id) {
|
|
1024
|
+
return this.core.get(id);
|
|
1025
|
+
}
|
|
1026
|
+
async peek(status, num) {
|
|
1027
|
+
return this.core.peek(status, num);
|
|
1028
|
+
}
|
|
1029
|
+
size(status) {
|
|
1030
|
+
return this.core.size(status);
|
|
1031
|
+
}
|
|
1032
|
+
async getByRunId(runId) {
|
|
1033
|
+
return this.core.getByRunId(runId);
|
|
1034
|
+
}
|
|
1035
|
+
outputForInput(input) {
|
|
1036
|
+
return this.core.outputForInput(input);
|
|
1037
|
+
}
|
|
1038
|
+
async saveProgress(id, progress, message, details) {
|
|
1039
|
+
await this.core.saveProgress(id, progress, message, details);
|
|
1040
|
+
}
|
|
1041
|
+
async saveResult(id, output) {
|
|
1042
|
+
const buf = this.pending.get(id) ?? {};
|
|
1043
|
+
buf.output = output ?? null;
|
|
1044
|
+
this.pending.set(id, buf);
|
|
1045
|
+
}
|
|
1046
|
+
async saveError(id, error, errorCode, abortRequested) {
|
|
1047
|
+
const buf = this.pending.get(id) ?? {};
|
|
1048
|
+
buf.error = error;
|
|
1049
|
+
buf.errorCode = errorCode;
|
|
1050
|
+
buf.abortRequested = abortRequested;
|
|
1051
|
+
this.pending.set(id, buf);
|
|
1052
|
+
}
|
|
1053
|
+
async deleteByStatusAndAge(status, olderThanMs) {
|
|
1054
|
+
await this.core.deleteJobsByStatusAndAge(status, olderThanMs);
|
|
1055
|
+
}
|
|
1056
|
+
async delete(id) {
|
|
1057
|
+
this.pending.delete(id);
|
|
1058
|
+
await this.core.delete(id);
|
|
1059
|
+
}
|
|
1060
|
+
async deleteAll() {
|
|
1061
|
+
this.pending.clear();
|
|
1062
|
+
await this.core.deleteAll();
|
|
1063
|
+
}
|
|
1064
|
+
async abort(id) {
|
|
1065
|
+
await this.core.abort(id);
|
|
1066
|
+
}
|
|
1067
|
+
async saveStatus(id, status) {
|
|
1068
|
+
await this.core.saveStatus(id, status);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
// src/job-queue/createSupabaseQueue.ts
|
|
1072
|
+
function createSupabaseQueue(queueName, client, opts) {
|
|
1073
|
+
const core = new SupabaseQueueStorage(client, queueName, opts);
|
|
1074
|
+
const pending = new Map;
|
|
1075
|
+
return {
|
|
1076
|
+
messageQueue: new SupabaseMessageQueue(core, pending),
|
|
1077
|
+
jobStore: new SupabaseJobStore(core, pending),
|
|
1078
|
+
core
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
764
1081
|
export {
|
|
1082
|
+
createSupabaseQueue,
|
|
765
1083
|
SupabaseRateLimiterStorage,
|
|
766
1084
|
SupabaseQueueStorage,
|
|
1085
|
+
SupabaseMessageQueue,
|
|
1086
|
+
SupabaseJobStore,
|
|
767
1087
|
SUPABASE_RATE_LIMITER_STORAGE,
|
|
768
1088
|
SUPABASE_QUEUE_STORAGE
|
|
769
1089
|
};
|
|
770
1090
|
|
|
771
|
-
//# debugId=
|
|
1091
|
+
//# debugId=BA461B9EF049A7F664756E2164756E21
|