@strav/queue 0.3.30 → 0.3.32
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/package.json +3 -3
- package/src/queue/queue.ts +88 -1
- package/src/queue/worker.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strav/queue",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Background job processing and task scheduling for the Strav framework",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"./providers/*": "./src/providers/*.ts"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@strav/kernel": "0.3.
|
|
32
|
-
"@strav/database": "0.3.
|
|
31
|
+
"@strav/kernel": "0.3.32",
|
|
32
|
+
"@strav/database": "0.3.32"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"test": "bun test tests/",
|
package/src/queue/queue.ts
CHANGED
|
@@ -26,6 +26,29 @@ export interface JobMeta {
|
|
|
26
26
|
job: string
|
|
27
27
|
attempts: number
|
|
28
28
|
maxAttempts: number
|
|
29
|
+
/**
|
|
30
|
+
* Report progress for a long-running job. `value` is `0..1`. The reported
|
|
31
|
+
* value is persisted to the job row so external consumers can poll via
|
|
32
|
+
* {@link Queue.progressOf}, and a `queue:progress` event is emitted for
|
|
33
|
+
* live consumers (e.g. SSE).
|
|
34
|
+
*
|
|
35
|
+
* Returns immediately after persisting; safe to call from a tight loop
|
|
36
|
+
* but throttle to avoid hammering the database (e.g. every N rows or
|
|
37
|
+
* every 1 s).
|
|
38
|
+
*/
|
|
39
|
+
progress: (value: number, message?: string) => Promise<void>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Snapshot of a job's current progress, returned by {@link Queue.progressOf}. */
|
|
43
|
+
export interface JobProgress {
|
|
44
|
+
/** Job id. */
|
|
45
|
+
id: number
|
|
46
|
+
/** 0..1, last reported by the handler. */
|
|
47
|
+
value: number
|
|
48
|
+
/** Optional human-readable message attached to the last update. */
|
|
49
|
+
message: string | null
|
|
50
|
+
/** Current attempt count. */
|
|
51
|
+
attempts: number
|
|
29
52
|
}
|
|
30
53
|
|
|
31
54
|
/** A raw job row from the _strav_jobs table. */
|
|
@@ -116,10 +139,23 @@ export default class Queue {
|
|
|
116
139
|
"timeout" INT NOT NULL DEFAULT 60000,
|
|
117
140
|
"available_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
118
141
|
"reserved_at" TIMESTAMPTZ,
|
|
119
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
142
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
143
|
+
"progress" NUMERIC NOT NULL DEFAULT 0,
|
|
144
|
+
"progress_message" TEXT
|
|
120
145
|
)
|
|
121
146
|
`
|
|
122
147
|
|
|
148
|
+
// Additive migrations for progress columns — for tables that existed
|
|
149
|
+
// before progress reporting was introduced.
|
|
150
|
+
await sql`
|
|
151
|
+
ALTER TABLE "_strav_jobs"
|
|
152
|
+
ADD COLUMN IF NOT EXISTS "progress" NUMERIC NOT NULL DEFAULT 0
|
|
153
|
+
`
|
|
154
|
+
await sql`
|
|
155
|
+
ALTER TABLE "_strav_jobs"
|
|
156
|
+
ADD COLUMN IF NOT EXISTS "progress_message" TEXT
|
|
157
|
+
`
|
|
158
|
+
|
|
123
159
|
await sql`
|
|
124
160
|
CREATE INDEX IF NOT EXISTS "idx_strav_jobs_queue_available"
|
|
125
161
|
ON "_strav_jobs" ("queue", "available_at")
|
|
@@ -168,6 +204,57 @@ export default class Queue {
|
|
|
168
204
|
return id
|
|
169
205
|
}
|
|
170
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Persist progress for an in-flight job and emit a `queue:progress` event.
|
|
209
|
+
* Called by the `JobMeta.progress` callback that workers hand to handlers,
|
|
210
|
+
* but exposed statically so other code (e.g. retry replay tools) can update
|
|
211
|
+
* progress directly. `value` is clamped to `[0, 1]`.
|
|
212
|
+
*/
|
|
213
|
+
static async reportProgress(
|
|
214
|
+
id: number,
|
|
215
|
+
value: number,
|
|
216
|
+
message?: string
|
|
217
|
+
): Promise<void> {
|
|
218
|
+
const sql = Queue.db.sql
|
|
219
|
+
const clamped = Math.max(0, Math.min(1, value))
|
|
220
|
+
const msg = message ?? null
|
|
221
|
+
await sql`
|
|
222
|
+
UPDATE "_strav_jobs"
|
|
223
|
+
SET "progress" = ${clamped}, "progress_message" = ${msg}
|
|
224
|
+
WHERE "id" = ${id}
|
|
225
|
+
`
|
|
226
|
+
if (Emitter.listenerCount('queue:progress') > 0) {
|
|
227
|
+
Emitter.emit('queue:progress', {
|
|
228
|
+
id,
|
|
229
|
+
value: clamped,
|
|
230
|
+
message: msg,
|
|
231
|
+
}).catch(() => {})
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Read the latest progress snapshot for a job. Returns `null` once the
|
|
237
|
+
* job has completed (the row is deleted on success) or if the id is
|
|
238
|
+
* unknown.
|
|
239
|
+
*/
|
|
240
|
+
static async progressOf(id: number): Promise<JobProgress | null> {
|
|
241
|
+
const sql = Queue.db.sql
|
|
242
|
+
const rows = await sql`
|
|
243
|
+
SELECT "id", "progress", "progress_message", "attempts"
|
|
244
|
+
FROM "_strav_jobs"
|
|
245
|
+
WHERE "id" = ${id}
|
|
246
|
+
LIMIT 1
|
|
247
|
+
`
|
|
248
|
+
if (rows.length === 0) return null
|
|
249
|
+
const row = rows[0] as Record<string, unknown>
|
|
250
|
+
return {
|
|
251
|
+
id: Number(row.id),
|
|
252
|
+
value: Number(row.progress ?? 0),
|
|
253
|
+
message: (row.progress_message as string | null) ?? null,
|
|
254
|
+
attempts: Number(row.attempts ?? 0),
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
171
258
|
/**
|
|
172
259
|
* Create a listener function suitable for Emitter.on().
|
|
173
260
|
* When the event fires, the payload is pushed onto the queue.
|
package/src/queue/worker.ts
CHANGED