workmatic 1.0.4 → 1.0.7
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 +82 -13
- package/dashboard/app.js +1 -3
- package/dashboard/index.html +0 -14
- package/dist/cli.cjs +4 -6
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +136 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -6
- package/dist/index.d.ts +49 -6
- package/dist/index.js +134 -25
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import * as http from 'http';
|
|
2
1
|
import * as better_sqlite3 from 'better-sqlite3';
|
|
2
|
+
import better_sqlite3__default from 'better-sqlite3';
|
|
3
|
+
import * as http from 'http';
|
|
3
4
|
import { Kysely, Generated } from 'kysely';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Job status types
|
|
7
8
|
*/
|
|
8
|
-
type JobStatus = 'ready' | 'running' | 'done' | '
|
|
9
|
+
type JobStatus = 'ready' | 'running' | 'done' | 'dead';
|
|
9
10
|
/**
|
|
10
11
|
* Database table schema for workmatic_jobs
|
|
11
12
|
*/
|
|
@@ -63,7 +64,7 @@ interface Job<TPayload = unknown> {
|
|
|
63
64
|
maxAttempts: number;
|
|
64
65
|
/** When the job was created (unix ms) */
|
|
65
66
|
createdAt: number;
|
|
66
|
-
/** Last error
|
|
67
|
+
/** Last error from a previous attempt (e.g. before retry) */
|
|
67
68
|
lastError: string | null;
|
|
68
69
|
}
|
|
69
70
|
/**
|
|
@@ -84,6 +85,13 @@ interface AddJobResult {
|
|
|
84
85
|
ok: true;
|
|
85
86
|
id: string;
|
|
86
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Result of adding multiple jobs in one transaction
|
|
90
|
+
*/
|
|
91
|
+
interface AddManyResult {
|
|
92
|
+
ok: true;
|
|
93
|
+
ids: string[];
|
|
94
|
+
}
|
|
87
95
|
/**
|
|
88
96
|
* Options for creating a database
|
|
89
97
|
*/
|
|
@@ -124,7 +132,7 @@ interface WorkerOptions {
|
|
|
124
132
|
leaseMs?: number;
|
|
125
133
|
/** Poll interval in ms when no jobs available. Default: 1000 */
|
|
126
134
|
pollMs?: number;
|
|
127
|
-
/** Job execution timeout in ms. Default:
|
|
135
|
+
/** Job execution timeout in ms. Default: 60000 (1 min). Set to 0 to disable. */
|
|
128
136
|
timeoutMs?: number;
|
|
129
137
|
/** Backoff function for retries. Default: exponential */
|
|
130
138
|
backoff?: BackoffFunction;
|
|
@@ -132,6 +140,18 @@ interface WorkerOptions {
|
|
|
132
140
|
persistState?: boolean;
|
|
133
141
|
/** Auto-restore worker state on creation. Default: true (only applies if persistState is true) */
|
|
134
142
|
autoRestore?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Minimum interval between database pause checks (CLI `pause`). Reduces round-trips while pumping.
|
|
145
|
+
* Default: 300 ms
|
|
146
|
+
*/
|
|
147
|
+
pauseCheckIntervalMs?: number;
|
|
148
|
+
/**
|
|
149
|
+
* Minimum interval between lease requeue scans. Default: every pump (0).
|
|
150
|
+
* Set to e.g. 1000 to run expired-lease recovery at most once per second.
|
|
151
|
+
*/
|
|
152
|
+
requeueExpiredIntervalMs?: number;
|
|
153
|
+
/** Called when the pump loop catches an error (after optional default logging) */
|
|
154
|
+
onPumpError?: (error: unknown) => void;
|
|
135
155
|
}
|
|
136
156
|
/**
|
|
137
157
|
* Job processor function type
|
|
@@ -144,7 +164,6 @@ interface JobStats {
|
|
|
144
164
|
ready: number;
|
|
145
165
|
running: number;
|
|
146
166
|
done: number;
|
|
147
|
-
failed: number;
|
|
148
167
|
dead: number;
|
|
149
168
|
total: number;
|
|
150
169
|
}
|
|
@@ -154,8 +173,17 @@ interface JobStats {
|
|
|
154
173
|
interface WorkmaticClient {
|
|
155
174
|
/** Add a job to the queue */
|
|
156
175
|
add<TPayload = unknown>(payload: TPayload, options?: AddJobOptions): Promise<AddJobResult>;
|
|
176
|
+
/**
|
|
177
|
+
* Add many jobs in a single transaction (shared priority, delay, maxAttempts).
|
|
178
|
+
* Faster than repeated `add()` when inserting large batches.
|
|
179
|
+
*/
|
|
180
|
+
addMany<TPayload = unknown>(payloads: TPayload[], options?: AddJobOptions): Promise<AddManyResult>;
|
|
157
181
|
/** Get job statistics */
|
|
158
182
|
stats(): Promise<JobStats>;
|
|
183
|
+
/** Clear all jobs from the queue */
|
|
184
|
+
clear(options?: {
|
|
185
|
+
status?: JobStatus;
|
|
186
|
+
}): Promise<number>;
|
|
159
187
|
}
|
|
160
188
|
/**
|
|
161
189
|
* Worker interface
|
|
@@ -175,6 +203,10 @@ interface WorkmaticWorker {
|
|
|
175
203
|
stats(): Promise<JobStats>;
|
|
176
204
|
/** Restore worker state from database (only when persistState is true) */
|
|
177
205
|
restoreState(): Promise<WorkerState | null>;
|
|
206
|
+
/** Clear all jobs from the queue */
|
|
207
|
+
clear(options?: {
|
|
208
|
+
status?: JobStatus;
|
|
209
|
+
}): Promise<number>;
|
|
178
210
|
/** Check if worker is running */
|
|
179
211
|
readonly isRunning: boolean;
|
|
180
212
|
/** Check if worker is paused */
|
|
@@ -227,6 +259,9 @@ interface ClaimedJob {
|
|
|
227
259
|
payload: string;
|
|
228
260
|
attempts: number;
|
|
229
261
|
max_attempts: number;
|
|
262
|
+
priority: number;
|
|
263
|
+
created_at: number;
|
|
264
|
+
last_error: string | null;
|
|
230
265
|
}
|
|
231
266
|
|
|
232
267
|
/**
|
|
@@ -250,6 +285,12 @@ interface ClaimedJob {
|
|
|
250
285
|
* ```
|
|
251
286
|
*/
|
|
252
287
|
declare function createDatabase(options?: DatabaseOptions): WorkmaticDb;
|
|
288
|
+
/**
|
|
289
|
+
* Get the underlying better-sqlite3 database instance from a Kysely instance
|
|
290
|
+
* created with {@link createDatabase}. For manually constructed `Kysely` instances,
|
|
291
|
+
* falls back to reading the dialect adapter (may break across Kysely versions).
|
|
292
|
+
*/
|
|
293
|
+
declare function getUnderlyingDb(db: WorkmaticDb): better_sqlite3__default.Database;
|
|
253
294
|
|
|
254
295
|
/**
|
|
255
296
|
* Create a job queue client for adding jobs
|
|
@@ -278,6 +319,8 @@ declare function createDatabase(options?: DatabaseOptions): WorkmaticDb;
|
|
|
278
319
|
*/
|
|
279
320
|
declare function createClient(options: ClientOptions): WorkmaticClient;
|
|
280
321
|
|
|
322
|
+
/** Default job execution timeout when `timeoutMs` is omitted (1 minute). Use `timeoutMs: 0` for no limit. */
|
|
323
|
+
declare const DEFAULT_WORKER_TIMEOUT_MS = 60000;
|
|
281
324
|
/**
|
|
282
325
|
* Create a job queue worker for processing jobs
|
|
283
326
|
*
|
|
@@ -375,4 +418,4 @@ declare const defaultBackoff: BackoffFunction;
|
|
|
375
418
|
*/
|
|
376
419
|
declare function validatePayload(payload: unknown): string;
|
|
377
420
|
|
|
378
|
-
export { type AddJobOptions, type AddJobResult, type BackoffFunction, type ClaimedJob, type ClientOptions, type DashboardMiddleware, type DashboardMiddlewareOptions, type DashboardOptions, type DatabaseOptions, type Job, type JobProcessor, type JobStats, type JobStatus, type WorkerOptions, type WorkerState, type WorkmaticClient, type WorkmaticDashboard, type WorkmaticDatabase, type WorkmaticDb, type WorkmaticJobsTable, type WorkmaticWorker, createClient, createDashboard, createDashboardMiddleware, createDatabase, createWorker, defaultBackoff, validatePayload };
|
|
421
|
+
export { type AddJobOptions, type AddJobResult, type AddManyResult, type BackoffFunction, type ClaimedJob, type ClientOptions, DEFAULT_WORKER_TIMEOUT_MS, type DashboardMiddleware, type DashboardMiddlewareOptions, type DashboardOptions, type DatabaseOptions, type Job, type JobProcessor, type JobStats, type JobStatus, type WorkerOptions, type WorkerState, type WorkmaticClient, type WorkmaticDashboard, type WorkmaticDatabase, type WorkmaticDb, type WorkmaticJobsTable, type WorkmaticWorker, createClient, createDashboard, createDashboardMiddleware, createDatabase, createWorker, defaultBackoff, getUnderlyingDb, validatePayload };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import * as http from 'http';
|
|
2
1
|
import * as better_sqlite3 from 'better-sqlite3';
|
|
2
|
+
import better_sqlite3__default from 'better-sqlite3';
|
|
3
|
+
import * as http from 'http';
|
|
3
4
|
import { Kysely, Generated } from 'kysely';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Job status types
|
|
7
8
|
*/
|
|
8
|
-
type JobStatus = 'ready' | 'running' | 'done' | '
|
|
9
|
+
type JobStatus = 'ready' | 'running' | 'done' | 'dead';
|
|
9
10
|
/**
|
|
10
11
|
* Database table schema for workmatic_jobs
|
|
11
12
|
*/
|
|
@@ -63,7 +64,7 @@ interface Job<TPayload = unknown> {
|
|
|
63
64
|
maxAttempts: number;
|
|
64
65
|
/** When the job was created (unix ms) */
|
|
65
66
|
createdAt: number;
|
|
66
|
-
/** Last error
|
|
67
|
+
/** Last error from a previous attempt (e.g. before retry) */
|
|
67
68
|
lastError: string | null;
|
|
68
69
|
}
|
|
69
70
|
/**
|
|
@@ -84,6 +85,13 @@ interface AddJobResult {
|
|
|
84
85
|
ok: true;
|
|
85
86
|
id: string;
|
|
86
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Result of adding multiple jobs in one transaction
|
|
90
|
+
*/
|
|
91
|
+
interface AddManyResult {
|
|
92
|
+
ok: true;
|
|
93
|
+
ids: string[];
|
|
94
|
+
}
|
|
87
95
|
/**
|
|
88
96
|
* Options for creating a database
|
|
89
97
|
*/
|
|
@@ -124,7 +132,7 @@ interface WorkerOptions {
|
|
|
124
132
|
leaseMs?: number;
|
|
125
133
|
/** Poll interval in ms when no jobs available. Default: 1000 */
|
|
126
134
|
pollMs?: number;
|
|
127
|
-
/** Job execution timeout in ms. Default:
|
|
135
|
+
/** Job execution timeout in ms. Default: 60000 (1 min). Set to 0 to disable. */
|
|
128
136
|
timeoutMs?: number;
|
|
129
137
|
/** Backoff function for retries. Default: exponential */
|
|
130
138
|
backoff?: BackoffFunction;
|
|
@@ -132,6 +140,18 @@ interface WorkerOptions {
|
|
|
132
140
|
persistState?: boolean;
|
|
133
141
|
/** Auto-restore worker state on creation. Default: true (only applies if persistState is true) */
|
|
134
142
|
autoRestore?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Minimum interval between database pause checks (CLI `pause`). Reduces round-trips while pumping.
|
|
145
|
+
* Default: 300 ms
|
|
146
|
+
*/
|
|
147
|
+
pauseCheckIntervalMs?: number;
|
|
148
|
+
/**
|
|
149
|
+
* Minimum interval between lease requeue scans. Default: every pump (0).
|
|
150
|
+
* Set to e.g. 1000 to run expired-lease recovery at most once per second.
|
|
151
|
+
*/
|
|
152
|
+
requeueExpiredIntervalMs?: number;
|
|
153
|
+
/** Called when the pump loop catches an error (after optional default logging) */
|
|
154
|
+
onPumpError?: (error: unknown) => void;
|
|
135
155
|
}
|
|
136
156
|
/**
|
|
137
157
|
* Job processor function type
|
|
@@ -144,7 +164,6 @@ interface JobStats {
|
|
|
144
164
|
ready: number;
|
|
145
165
|
running: number;
|
|
146
166
|
done: number;
|
|
147
|
-
failed: number;
|
|
148
167
|
dead: number;
|
|
149
168
|
total: number;
|
|
150
169
|
}
|
|
@@ -154,8 +173,17 @@ interface JobStats {
|
|
|
154
173
|
interface WorkmaticClient {
|
|
155
174
|
/** Add a job to the queue */
|
|
156
175
|
add<TPayload = unknown>(payload: TPayload, options?: AddJobOptions): Promise<AddJobResult>;
|
|
176
|
+
/**
|
|
177
|
+
* Add many jobs in a single transaction (shared priority, delay, maxAttempts).
|
|
178
|
+
* Faster than repeated `add()` when inserting large batches.
|
|
179
|
+
*/
|
|
180
|
+
addMany<TPayload = unknown>(payloads: TPayload[], options?: AddJobOptions): Promise<AddManyResult>;
|
|
157
181
|
/** Get job statistics */
|
|
158
182
|
stats(): Promise<JobStats>;
|
|
183
|
+
/** Clear all jobs from the queue */
|
|
184
|
+
clear(options?: {
|
|
185
|
+
status?: JobStatus;
|
|
186
|
+
}): Promise<number>;
|
|
159
187
|
}
|
|
160
188
|
/**
|
|
161
189
|
* Worker interface
|
|
@@ -175,6 +203,10 @@ interface WorkmaticWorker {
|
|
|
175
203
|
stats(): Promise<JobStats>;
|
|
176
204
|
/** Restore worker state from database (only when persistState is true) */
|
|
177
205
|
restoreState(): Promise<WorkerState | null>;
|
|
206
|
+
/** Clear all jobs from the queue */
|
|
207
|
+
clear(options?: {
|
|
208
|
+
status?: JobStatus;
|
|
209
|
+
}): Promise<number>;
|
|
178
210
|
/** Check if worker is running */
|
|
179
211
|
readonly isRunning: boolean;
|
|
180
212
|
/** Check if worker is paused */
|
|
@@ -227,6 +259,9 @@ interface ClaimedJob {
|
|
|
227
259
|
payload: string;
|
|
228
260
|
attempts: number;
|
|
229
261
|
max_attempts: number;
|
|
262
|
+
priority: number;
|
|
263
|
+
created_at: number;
|
|
264
|
+
last_error: string | null;
|
|
230
265
|
}
|
|
231
266
|
|
|
232
267
|
/**
|
|
@@ -250,6 +285,12 @@ interface ClaimedJob {
|
|
|
250
285
|
* ```
|
|
251
286
|
*/
|
|
252
287
|
declare function createDatabase(options?: DatabaseOptions): WorkmaticDb;
|
|
288
|
+
/**
|
|
289
|
+
* Get the underlying better-sqlite3 database instance from a Kysely instance
|
|
290
|
+
* created with {@link createDatabase}. For manually constructed `Kysely` instances,
|
|
291
|
+
* falls back to reading the dialect adapter (may break across Kysely versions).
|
|
292
|
+
*/
|
|
293
|
+
declare function getUnderlyingDb(db: WorkmaticDb): better_sqlite3__default.Database;
|
|
253
294
|
|
|
254
295
|
/**
|
|
255
296
|
* Create a job queue client for adding jobs
|
|
@@ -278,6 +319,8 @@ declare function createDatabase(options?: DatabaseOptions): WorkmaticDb;
|
|
|
278
319
|
*/
|
|
279
320
|
declare function createClient(options: ClientOptions): WorkmaticClient;
|
|
280
321
|
|
|
322
|
+
/** Default job execution timeout when `timeoutMs` is omitted (1 minute). Use `timeoutMs: 0` for no limit. */
|
|
323
|
+
declare const DEFAULT_WORKER_TIMEOUT_MS = 60000;
|
|
281
324
|
/**
|
|
282
325
|
* Create a job queue worker for processing jobs
|
|
283
326
|
*
|
|
@@ -375,4 +418,4 @@ declare const defaultBackoff: BackoffFunction;
|
|
|
375
418
|
*/
|
|
376
419
|
declare function validatePayload(payload: unknown): string;
|
|
377
420
|
|
|
378
|
-
export { type AddJobOptions, type AddJobResult, type BackoffFunction, type ClaimedJob, type ClientOptions, type DashboardMiddleware, type DashboardMiddlewareOptions, type DashboardOptions, type DatabaseOptions, type Job, type JobProcessor, type JobStats, type JobStatus, type WorkerOptions, type WorkerState, type WorkmaticClient, type WorkmaticDashboard, type WorkmaticDatabase, type WorkmaticDb, type WorkmaticJobsTable, type WorkmaticWorker, createClient, createDashboard, createDashboardMiddleware, createDatabase, createWorker, defaultBackoff, validatePayload };
|
|
421
|
+
export { type AddJobOptions, type AddJobResult, type AddManyResult, type BackoffFunction, type ClaimedJob, type ClientOptions, DEFAULT_WORKER_TIMEOUT_MS, type DashboardMiddleware, type DashboardMiddlewareOptions, type DashboardOptions, type DatabaseOptions, type Job, type JobProcessor, type JobStats, type JobStatus, type WorkerOptions, type WorkerState, type WorkmaticClient, type WorkmaticDashboard, type WorkmaticDatabase, type WorkmaticDb, type WorkmaticJobsTable, type WorkmaticWorker, createClient, createDashboard, createDashboardMiddleware, createDatabase, createWorker, defaultBackoff, getUnderlyingDb, validatePayload };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/database.ts
|
|
2
2
|
import Database from "better-sqlite3";
|
|
3
3
|
import { Kysely, SqliteDialect } from "kysely";
|
|
4
|
+
var kyselyToSqlite = /* @__PURE__ */ new WeakMap();
|
|
4
5
|
function createDatabase(options = {}) {
|
|
5
6
|
let sqliteDb;
|
|
6
7
|
if (options.db) {
|
|
@@ -18,6 +19,7 @@ function createDatabase(options = {}) {
|
|
|
18
19
|
})
|
|
19
20
|
});
|
|
20
21
|
createSchema(sqliteDb);
|
|
22
|
+
kyselyToSqlite.set(db, sqliteDb);
|
|
21
23
|
return db;
|
|
22
24
|
}
|
|
23
25
|
function createSchema(db) {
|
|
@@ -53,6 +55,26 @@ function createSchema(db) {
|
|
|
53
55
|
updated_at INTEGER NOT NULL
|
|
54
56
|
)
|
|
55
57
|
`);
|
|
58
|
+
db.exec(`
|
|
59
|
+
UPDATE workmatic_jobs SET status = 'dead' WHERE status = 'failed'
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
62
|
+
function getUnderlyingDb(db) {
|
|
63
|
+
const mapped = kyselyToSqlite.get(db);
|
|
64
|
+
if (mapped) {
|
|
65
|
+
return mapped;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const ex = db.getExecutor?.();
|
|
69
|
+
const dialect = ex?.adapter?.db;
|
|
70
|
+
if (dialect) {
|
|
71
|
+
return dialect;
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
throw new Error(
|
|
76
|
+
"getUnderlyingDb: could not resolve better-sqlite3 instance (use createDatabase() or pass db from it)"
|
|
77
|
+
);
|
|
56
78
|
}
|
|
57
79
|
|
|
58
80
|
// src/client.ts
|
|
@@ -121,6 +143,42 @@ function createClient(options) {
|
|
|
121
143
|
}).execute();
|
|
122
144
|
return { ok: true, id: publicId };
|
|
123
145
|
},
|
|
146
|
+
async addMany(payloads, opts = {}) {
|
|
147
|
+
const {
|
|
148
|
+
priority = 0,
|
|
149
|
+
delayMs = 0,
|
|
150
|
+
maxAttempts = 3
|
|
151
|
+
} = opts;
|
|
152
|
+
if (payloads.length === 0) {
|
|
153
|
+
return { ok: true, ids: [] };
|
|
154
|
+
}
|
|
155
|
+
const timestamp = now();
|
|
156
|
+
const runAt = timestamp + delayMs;
|
|
157
|
+
return await db.transaction().execute(async (trx) => {
|
|
158
|
+
const ids = [];
|
|
159
|
+
const rows = payloads.map((payload) => {
|
|
160
|
+
const payloadJson = validatePayload(payload);
|
|
161
|
+
const publicId = nanoid();
|
|
162
|
+
ids.push(publicId);
|
|
163
|
+
return {
|
|
164
|
+
public_id: publicId,
|
|
165
|
+
queue,
|
|
166
|
+
payload: payloadJson,
|
|
167
|
+
status: "ready",
|
|
168
|
+
priority,
|
|
169
|
+
run_at: runAt,
|
|
170
|
+
attempts: 0,
|
|
171
|
+
max_attempts: maxAttempts,
|
|
172
|
+
lease_until: 0,
|
|
173
|
+
created_at: timestamp,
|
|
174
|
+
updated_at: timestamp,
|
|
175
|
+
last_error: null
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
await trx.insertInto("workmatic_jobs").values(rows).execute();
|
|
179
|
+
return { ok: true, ids };
|
|
180
|
+
});
|
|
181
|
+
},
|
|
124
182
|
/**
|
|
125
183
|
* Get job statistics for the queue
|
|
126
184
|
*/
|
|
@@ -133,7 +191,6 @@ function createClient(options) {
|
|
|
133
191
|
ready: 0,
|
|
134
192
|
running: 0,
|
|
135
193
|
done: 0,
|
|
136
|
-
failed: 0,
|
|
137
194
|
dead: 0,
|
|
138
195
|
total: 0
|
|
139
196
|
};
|
|
@@ -146,6 +203,17 @@ function createClient(options) {
|
|
|
146
203
|
stats.total += count;
|
|
147
204
|
}
|
|
148
205
|
return stats;
|
|
206
|
+
},
|
|
207
|
+
/**
|
|
208
|
+
* Clear all jobs from the queue
|
|
209
|
+
*/
|
|
210
|
+
async clear(options2 = {}) {
|
|
211
|
+
let query = db.deleteFrom("workmatic_jobs").where("queue", "=", queue);
|
|
212
|
+
if (options2.status) {
|
|
213
|
+
query = query.where("status", "=", options2.status);
|
|
214
|
+
}
|
|
215
|
+
const result = await query.execute();
|
|
216
|
+
return Number(result[0]?.numDeletedRows ?? 0);
|
|
149
217
|
}
|
|
150
218
|
};
|
|
151
219
|
}
|
|
@@ -153,6 +221,7 @@ function createClient(options) {
|
|
|
153
221
|
// src/worker.ts
|
|
154
222
|
import fastq from "fastq";
|
|
155
223
|
import { sql as sql2 } from "kysely";
|
|
224
|
+
var DEFAULT_WORKER_TIMEOUT_MS = 6e4;
|
|
156
225
|
function createWorker(options) {
|
|
157
226
|
const {
|
|
158
227
|
db,
|
|
@@ -160,10 +229,13 @@ function createWorker(options) {
|
|
|
160
229
|
concurrency = 1,
|
|
161
230
|
leaseMs = 3e4,
|
|
162
231
|
pollMs = 1e3,
|
|
163
|
-
timeoutMs,
|
|
232
|
+
timeoutMs = DEFAULT_WORKER_TIMEOUT_MS,
|
|
164
233
|
backoff = defaultBackoff,
|
|
165
234
|
persistState = false,
|
|
166
|
-
autoRestore = true
|
|
235
|
+
autoRestore = true,
|
|
236
|
+
pauseCheckIntervalMs = 300,
|
|
237
|
+
requeueExpiredIntervalMs = 0,
|
|
238
|
+
onPumpError
|
|
167
239
|
} = options;
|
|
168
240
|
if (!db) {
|
|
169
241
|
throw new Error("Database instance is required");
|
|
@@ -173,6 +245,13 @@ function createWorker(options) {
|
|
|
173
245
|
let processor = null;
|
|
174
246
|
let pumpTimeout = null;
|
|
175
247
|
let fastqQueue = null;
|
|
248
|
+
let lastPauseCheckAt = 0;
|
|
249
|
+
let cachedDbPaused = false;
|
|
250
|
+
let lastRequeueAt = 0;
|
|
251
|
+
function notifyPumpError(error) {
|
|
252
|
+
console.error("[workmatic] Pump error:", error);
|
|
253
|
+
onPumpError?.(error);
|
|
254
|
+
}
|
|
176
255
|
function getStateKey() {
|
|
177
256
|
return `worker_state_${queue}`;
|
|
178
257
|
}
|
|
@@ -180,8 +259,6 @@ function createWorker(options) {
|
|
|
180
259
|
if (!persistState) return;
|
|
181
260
|
const timestamp = now();
|
|
182
261
|
const key = getStateKey();
|
|
183
|
-
await db.schema.createTable("workmatic_settings").ifNotExists().addColumn("queue", "text", (col) => col.primaryKey()).addColumn("paused", "integer", (col) => col.notNull().defaultTo(0)).addColumn("updated_at", "integer", (col) => col.notNull()).execute().catch(() => {
|
|
184
|
-
});
|
|
185
262
|
await sql2`
|
|
186
263
|
INSERT INTO workmatic_settings (queue, paused, updated_at)
|
|
187
264
|
VALUES (${key}, ${state === "paused" ? 1 : state === "running" ? 2 : 0}, ${timestamp})
|
|
@@ -216,17 +293,34 @@ function createWorker(options) {
|
|
|
216
293
|
const timestamp = now();
|
|
217
294
|
const leaseUntil = timestamp + leaseMs;
|
|
218
295
|
return await db.transaction().execute(async (trx) => {
|
|
219
|
-
const
|
|
220
|
-
|
|
296
|
+
const result = await sql2`
|
|
297
|
+
UPDATE workmatic_jobs
|
|
298
|
+
SET status = 'running', lease_until = ${leaseUntil}, updated_at = ${timestamp}
|
|
299
|
+
WHERE rowid IN (
|
|
300
|
+
SELECT rowid FROM workmatic_jobs
|
|
301
|
+
WHERE queue = ${queue}
|
|
302
|
+
AND status = 'ready'
|
|
303
|
+
AND run_at <= ${timestamp}
|
|
304
|
+
ORDER BY priority ASC, id ASC
|
|
305
|
+
LIMIT ${limit}
|
|
306
|
+
)
|
|
307
|
+
RETURNING id, public_id, queue, payload, attempts, max_attempts, priority, created_at, last_error
|
|
308
|
+
`.execute(trx);
|
|
309
|
+
const rows = result.rows;
|
|
310
|
+
if (!rows || !Array.isArray(rows)) {
|
|
221
311
|
return [];
|
|
222
312
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
313
|
+
return rows.map((row) => ({
|
|
314
|
+
id: row.id,
|
|
315
|
+
public_id: row.public_id,
|
|
316
|
+
queue: row.queue,
|
|
317
|
+
payload: row.payload,
|
|
318
|
+
attempts: row.attempts,
|
|
319
|
+
max_attempts: row.max_attempts,
|
|
320
|
+
priority: row.priority,
|
|
321
|
+
created_at: row.created_at,
|
|
322
|
+
last_error: row.last_error
|
|
323
|
+
}));
|
|
230
324
|
});
|
|
231
325
|
}
|
|
232
326
|
async function markDone(jobId) {
|
|
@@ -283,13 +377,11 @@ function createWorker(options) {
|
|
|
283
377
|
queue: claimedJob.queue,
|
|
284
378
|
payload,
|
|
285
379
|
status: "running",
|
|
286
|
-
priority:
|
|
287
|
-
// Not needed for processing
|
|
380
|
+
priority: claimedJob.priority,
|
|
288
381
|
attempts: claimedJob.attempts,
|
|
289
382
|
maxAttempts: claimedJob.max_attempts,
|
|
290
|
-
createdAt:
|
|
291
|
-
|
|
292
|
-
lastError: null
|
|
383
|
+
createdAt: claimedJob.created_at,
|
|
384
|
+
lastError: claimedJob.last_error
|
|
293
385
|
};
|
|
294
386
|
try {
|
|
295
387
|
if (timeoutMs) {
|
|
@@ -320,12 +412,21 @@ function createWorker(options) {
|
|
|
320
412
|
return;
|
|
321
413
|
}
|
|
322
414
|
try {
|
|
323
|
-
const
|
|
324
|
-
if (
|
|
415
|
+
const t = now();
|
|
416
|
+
if (t - lastPauseCheckAt >= pauseCheckIntervalMs) {
|
|
417
|
+
lastPauseCheckAt = t;
|
|
418
|
+
cachedDbPaused = await isQueuePausedInDb();
|
|
419
|
+
}
|
|
420
|
+
if (cachedDbPaused) {
|
|
325
421
|
pumpTimeout = setTimeout(pump, pollMs);
|
|
326
422
|
return;
|
|
327
423
|
}
|
|
328
|
-
|
|
424
|
+
if (requeueExpiredIntervalMs <= 0 || t - lastRequeueAt >= requeueExpiredIntervalMs) {
|
|
425
|
+
if (requeueExpiredIntervalMs > 0) {
|
|
426
|
+
lastRequeueAt = t;
|
|
427
|
+
}
|
|
428
|
+
await requeueExpiredLeases();
|
|
429
|
+
}
|
|
329
430
|
const batchSize = concurrency * 2;
|
|
330
431
|
const jobs = await claimBatch(batchSize);
|
|
331
432
|
if (jobs.length > 0) {
|
|
@@ -337,7 +438,7 @@ function createWorker(options) {
|
|
|
337
438
|
pumpTimeout = setTimeout(pump, pollMs);
|
|
338
439
|
}
|
|
339
440
|
} catch (error) {
|
|
340
|
-
|
|
441
|
+
notifyPumpError(error);
|
|
341
442
|
pumpTimeout = setTimeout(pump, pollMs);
|
|
342
443
|
}
|
|
343
444
|
}
|
|
@@ -393,7 +494,6 @@ function createWorker(options) {
|
|
|
393
494
|
ready: 0,
|
|
394
495
|
running: 0,
|
|
395
496
|
done: 0,
|
|
396
|
-
failed: 0,
|
|
397
497
|
dead: 0,
|
|
398
498
|
total: 0
|
|
399
499
|
};
|
|
@@ -425,6 +525,14 @@ function createWorker(options) {
|
|
|
425
525
|
this.pause();
|
|
426
526
|
}
|
|
427
527
|
return state;
|
|
528
|
+
},
|
|
529
|
+
async clear(options2 = {}) {
|
|
530
|
+
let query = db.deleteFrom("workmatic_jobs").where("queue", "=", queue);
|
|
531
|
+
if (options2.status) {
|
|
532
|
+
query = query.where("status", "=", options2.status);
|
|
533
|
+
}
|
|
534
|
+
const result = await query.execute();
|
|
535
|
+
return Number(result[0]?.numDeletedRows ?? 0);
|
|
428
536
|
}
|
|
429
537
|
};
|
|
430
538
|
if (persistState && autoRestore) {
|
|
@@ -528,7 +636,6 @@ function createRequestHandler(db, workerMap, basePath = "") {
|
|
|
528
636
|
ready: 0,
|
|
529
637
|
running: 0,
|
|
530
638
|
done: 0,
|
|
531
|
-
failed: 0,
|
|
532
639
|
dead: 0,
|
|
533
640
|
total: 0
|
|
534
641
|
};
|
|
@@ -734,12 +841,14 @@ function createDashboardMiddleware(options) {
|
|
|
734
841
|
};
|
|
735
842
|
}
|
|
736
843
|
export {
|
|
844
|
+
DEFAULT_WORKER_TIMEOUT_MS,
|
|
737
845
|
createClient,
|
|
738
846
|
createDashboard,
|
|
739
847
|
createDashboardMiddleware,
|
|
740
848
|
createDatabase,
|
|
741
849
|
createWorker,
|
|
742
850
|
defaultBackoff,
|
|
851
|
+
getUnderlyingDb,
|
|
743
852
|
validatePayload
|
|
744
853
|
};
|
|
745
854
|
//# sourceMappingURL=index.js.map
|