@gobing-ai/ts-db 0.5.4 → 0.5.5
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/queue-job-dao.d.ts.map +1 -1
- package/dist/queue-job-dao.js +42 -1
- package/package.json +3 -3
- package/src/queue-job-dao.ts +47 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-job-dao.d.ts","sourceRoot":"","sources":["../src/queue-job-dao.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAQ3C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,mFAAmF;AACnF,MAAM,WAAW,mBAAmB;IAChC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAC9B,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAoBD;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,SAAS,CAAC,YAAY,CAAC;AAE3D;;;;;GAKG;AACH,qBAAa,WAAY,SAAQ,SAAS,CAAC,OAAO,SAAS,EAAE,OAAO,SAAS,CAAC,EAAE,CAAC;gBACjE,OAAO,EAAE,SAAS;IAI9B;;OAEG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;IAoB7F;;OAEG;IACG,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,GAAG,mBAAmB,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAmC5G;;OAEG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAI9D;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC;IAoBrC;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASpD;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAe/D;;;;;;;;;;;;;;;OAeG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"queue-job-dao.d.ts","sourceRoot":"","sources":["../src/queue-job-dao.ts"],"names":[],"mappings":"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"}
|
package/dist/queue-job-dao.js
CHANGED
|
@@ -155,6 +155,21 @@ export class QueueJobDao extends EntityDao {
|
|
|
155
155
|
const leaseMs = options?.leaseMs;
|
|
156
156
|
const leased = leaseMs !== undefined;
|
|
157
157
|
const leaseExpiresAt = leased ? now + leaseMs : null;
|
|
158
|
+
// Task 0086 R7: attempts exhausted on an expired lease — fail the job
|
|
159
|
+
// instead of reclaiming it, mirroring exactly what markFailed writes.
|
|
160
|
+
await this.db
|
|
161
|
+
.update(queueJobs)
|
|
162
|
+
.set({
|
|
163
|
+
status: 'failed',
|
|
164
|
+
attempts: sql `${queueJobs.attempts} + 1`,
|
|
165
|
+
lastError: 'lease expired: attempts exhausted',
|
|
166
|
+
processingAt: null,
|
|
167
|
+
attemptToken: null,
|
|
168
|
+
leaseExpiresAt: null,
|
|
169
|
+
updatedAt: now,
|
|
170
|
+
})
|
|
171
|
+
.where(sql `${queueJobs.status} = 'processing' AND ${queueJobs.attemptToken} IS NOT NULL AND ${queueJobs.leaseExpiresAt} IS NOT NULL
|
|
172
|
+
AND ${queueJobs.leaseExpiresAt} <= ${now} AND ${queueJobs.attempts} + 1 >= ${queueJobs.maxRetries}`);
|
|
158
173
|
const reclaimable = sql `(
|
|
159
174
|
${queueJobs.status} = 'pending'
|
|
160
175
|
AND (${queueJobs.nextRetryAt} IS NULL OR ${queueJobs.nextRetryAt} <= ${now})
|
|
@@ -172,6 +187,11 @@ export class QueueJobDao extends EntityDao {
|
|
|
172
187
|
updatedAt: now,
|
|
173
188
|
attemptToken: leased ? sql `lower(hex(randomblob(16)))` : null,
|
|
174
189
|
leaseExpiresAt,
|
|
190
|
+
// Task 0086 R7: a reclaim of an expired lease counts as an
|
|
191
|
+
// attempt; SQLite evaluates SET against the pre-update row, so
|
|
192
|
+
// status here still reads 'processing'. Fresh pending claims
|
|
193
|
+
// keep their attempt count (attempt 0 on first claim).
|
|
194
|
+
attempts: sql `CASE WHEN ${queueJobs.status} = 'processing' THEN ${queueJobs.attempts} + 1 ELSE ${queueJobs.attempts} END`,
|
|
175
195
|
})
|
|
176
196
|
.where(sql `${queueJobs.id} IN (
|
|
177
197
|
SELECT id
|
|
@@ -286,9 +306,30 @@ export class QueueJobDao extends EntityDao {
|
|
|
286
306
|
*/
|
|
287
307
|
async resetStuckJobs(visibilityTimeout) {
|
|
288
308
|
const cutoff = this.now() - visibilityTimeout;
|
|
309
|
+
// Task 0086 R7: age sweep exhaustion — a stuck legacy (token-less) row
|
|
310
|
+
// whose next increment would hit maxRetries fails outright instead of
|
|
311
|
+
// being reset to pending forever.
|
|
312
|
+
await this.db
|
|
313
|
+
.update(queueJobs)
|
|
314
|
+
.set({
|
|
315
|
+
status: 'failed',
|
|
316
|
+
attempts: sql `${queueJobs.attempts} + 1`,
|
|
317
|
+
lastError: 'stuck job: attempts exhausted',
|
|
318
|
+
processingAt: null,
|
|
319
|
+
attemptToken: null,
|
|
320
|
+
leaseExpiresAt: null,
|
|
321
|
+
updatedAt: this.now(),
|
|
322
|
+
})
|
|
323
|
+
.where(sql `${queueJobs.status} = 'processing' AND ${queueJobs.processingAt} IS NOT NULL AND ${queueJobs.processingAt} <= ${cutoff}
|
|
324
|
+
AND ${queueJobs.attemptToken} IS NULL AND ${queueJobs.attempts} + 1 >= ${queueJobs.maxRetries}`);
|
|
289
325
|
const result = await this.db
|
|
290
326
|
.update(queueJobs)
|
|
291
|
-
.set({
|
|
327
|
+
.set({
|
|
328
|
+
status: 'pending',
|
|
329
|
+
processingAt: null,
|
|
330
|
+
updatedAt: this.now(),
|
|
331
|
+
attempts: sql `${queueJobs.attempts} + 1`,
|
|
332
|
+
})
|
|
292
333
|
.where(sql `${queueJobs.status} = 'processing' AND ${queueJobs.processingAt} IS NOT NULL AND ${queueJobs.processingAt} <= ${cutoff}
|
|
293
334
|
AND ${queueJobs.attemptToken} IS NULL`);
|
|
294
335
|
return result.changes;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-db",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "@gobing-ai/ts-db — a drizzle-free database facade: typed DAOs over Bun SQLite / Cloudflare D1, a small predicate query spec, single-source-of-truth tables, and migrations. Drizzle stays an internal detail.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-db-v<version> && git push --tags' && exit 1"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@gobing-ai/ts-runtime": "^0.5.
|
|
70
|
-
"@gobing-ai/ts-utils": "^0.5.
|
|
69
|
+
"@gobing-ai/ts-runtime": "^0.5.5",
|
|
70
|
+
"@gobing-ai/ts-utils": "^0.5.5"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"drizzle-orm": ">=0.38.0",
|
package/src/queue-job-dao.ts
CHANGED
|
@@ -220,6 +220,23 @@ export class QueueJobDao extends EntityDao<typeof queueJobs, typeof queueJobs.id
|
|
|
220
220
|
const leaseMs = options?.leaseMs;
|
|
221
221
|
const leased = leaseMs !== undefined;
|
|
222
222
|
const leaseExpiresAt = leased ? now + leaseMs : null;
|
|
223
|
+
// Task 0086 R7: attempts exhausted on an expired lease — fail the job
|
|
224
|
+
// instead of reclaiming it, mirroring exactly what markFailed writes.
|
|
225
|
+
await (this.db as UpdateChangesDb)
|
|
226
|
+
.update(queueJobs)
|
|
227
|
+
.set({
|
|
228
|
+
status: 'failed',
|
|
229
|
+
attempts: sql`${queueJobs.attempts} + 1`,
|
|
230
|
+
lastError: 'lease expired: attempts exhausted',
|
|
231
|
+
processingAt: null,
|
|
232
|
+
attemptToken: null,
|
|
233
|
+
leaseExpiresAt: null,
|
|
234
|
+
updatedAt: now,
|
|
235
|
+
})
|
|
236
|
+
.where(
|
|
237
|
+
sql`${queueJobs.status} = 'processing' AND ${queueJobs.attemptToken} IS NOT NULL AND ${queueJobs.leaseExpiresAt} IS NOT NULL
|
|
238
|
+
AND ${queueJobs.leaseExpiresAt} <= ${now} AND ${queueJobs.attempts} + 1 >= ${queueJobs.maxRetries}`,
|
|
239
|
+
);
|
|
223
240
|
const reclaimable = sql`(
|
|
224
241
|
${queueJobs.status} = 'pending'
|
|
225
242
|
AND (${queueJobs.nextRetryAt} IS NULL OR ${queueJobs.nextRetryAt} <= ${now})
|
|
@@ -238,6 +255,11 @@ export class QueueJobDao extends EntityDao<typeof queueJobs, typeof queueJobs.id
|
|
|
238
255
|
updatedAt: now,
|
|
239
256
|
attemptToken: leased ? sql`lower(hex(randomblob(16)))` : null,
|
|
240
257
|
leaseExpiresAt,
|
|
258
|
+
// Task 0086 R7: a reclaim of an expired lease counts as an
|
|
259
|
+
// attempt; SQLite evaluates SET against the pre-update row, so
|
|
260
|
+
// status here still reads 'processing'. Fresh pending claims
|
|
261
|
+
// keep their attempt count (attempt 0 on first claim).
|
|
262
|
+
attempts: sql`CASE WHEN ${queueJobs.status} = 'processing' THEN ${queueJobs.attempts} + 1 ELSE ${queueJobs.attempts} END`,
|
|
241
263
|
})
|
|
242
264
|
.where(
|
|
243
265
|
sql`${queueJobs.id} IN (
|
|
@@ -374,9 +396,33 @@ export class QueueJobDao extends EntityDao<typeof queueJobs, typeof queueJobs.id
|
|
|
374
396
|
async resetStuckJobs(visibilityTimeout: number): Promise<number> {
|
|
375
397
|
const cutoff = this.now() - visibilityTimeout;
|
|
376
398
|
|
|
399
|
+
// Task 0086 R7: age sweep exhaustion — a stuck legacy (token-less) row
|
|
400
|
+
// whose next increment would hit maxRetries fails outright instead of
|
|
401
|
+
// being reset to pending forever.
|
|
402
|
+
await (this.db as UpdateChangesDb)
|
|
403
|
+
.update(queueJobs)
|
|
404
|
+
.set({
|
|
405
|
+
status: 'failed',
|
|
406
|
+
attempts: sql`${queueJobs.attempts} + 1`,
|
|
407
|
+
lastError: 'stuck job: attempts exhausted',
|
|
408
|
+
processingAt: null,
|
|
409
|
+
attemptToken: null,
|
|
410
|
+
leaseExpiresAt: null,
|
|
411
|
+
updatedAt: this.now(),
|
|
412
|
+
})
|
|
413
|
+
.where(
|
|
414
|
+
sql`${queueJobs.status} = 'processing' AND ${queueJobs.processingAt} IS NOT NULL AND ${queueJobs.processingAt} <= ${cutoff}
|
|
415
|
+
AND ${queueJobs.attemptToken} IS NULL AND ${queueJobs.attempts} + 1 >= ${queueJobs.maxRetries}`,
|
|
416
|
+
);
|
|
417
|
+
|
|
377
418
|
const result = await (this.db as UpdateChangesDb)
|
|
378
419
|
.update(queueJobs)
|
|
379
|
-
.set({
|
|
420
|
+
.set({
|
|
421
|
+
status: 'pending',
|
|
422
|
+
processingAt: null,
|
|
423
|
+
updatedAt: this.now(),
|
|
424
|
+
attempts: sql`${queueJobs.attempts} + 1`,
|
|
425
|
+
})
|
|
380
426
|
.where(
|
|
381
427
|
sql`${queueJobs.status} = 'processing' AND ${queueJobs.processingAt} IS NOT NULL AND ${queueJobs.processingAt} <= ${cutoff}
|
|
382
428
|
AND ${queueJobs.attemptToken} IS NULL`,
|