@iterativeflow/mysql 2.1.0 → 2.2.0
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/index.d.mts +5 -0
- package/dist/index.mjs +43 -24
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -53,6 +53,11 @@ declare const createMysqlBackend: (sql: Sql, opts?: MysqlBackendOpts) => Backend
|
|
|
53
53
|
* MySQL has no implicit rowid). Indexed string columns are `VARCHAR(191)` to stay under the utf8mb4
|
|
54
54
|
* index-key limit. The idempotency/signal-dedup UNIQUE KEYs need no partial `WHERE`: MySQL treats
|
|
55
55
|
* NULLs as distinct, so unkeyed rows never collide.
|
|
56
|
+
*
|
|
57
|
+
* Also creates the `pending_work` function, so applying this needs `CREATE ROUTINE` as well as
|
|
58
|
+
* `CREATE TABLE`. MySQL has no `CREATE OR REPLACE FUNCTION`, so that one statement is a `DROP` +
|
|
59
|
+
* `CREATE` rather than `IF NOT EXISTS` — a scaler polling the function across a boot can see one
|
|
60
|
+
* failed read.
|
|
56
61
|
*/
|
|
57
62
|
declare const ddl: (prefix?: string) => string[];
|
|
58
63
|
/** Apply the schema DDL (idempotent). Run once before use. */
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NON_SUCCESS_TERMINAL_STATUSES, RECONCILABLE_STATUSES, TERMINAL_STATUSES, assertSqlIdentifier, createLocalWakeup, newId, orphanedRunsSql, queueDepthOf, statusList, zeroRunStats } from "@iterativeflow/core/backend";
|
|
1
|
+
import { NON_SUCCESS_TERMINAL_STATUSES, RECONCILABLE_STATUSES, TERMINAL_STATUSES, assertSqlIdentifier, createLocalWakeup, isTerminal, newId, orphanedRunsSql, purgeWhereSql, queueDepthOf, statusList, zeroRunStats } from "@iterativeflow/core/backend";
|
|
2
2
|
//#region src/queue.ts
|
|
3
3
|
/** @internal */
|
|
4
4
|
const createMysqlQueue = (sql, t, id) => {
|
|
@@ -17,7 +17,7 @@ const createMysqlQueue = (sql, t, id) => {
|
|
|
17
17
|
const at = ms(now);
|
|
18
18
|
const expiresAt = new Date(at + leaseMs);
|
|
19
19
|
return sql.tx(async (tx) => {
|
|
20
|
-
const namePredicate = names ? ` AND r.name IN (${names.map(() => "?").join(",")})` : "";
|
|
20
|
+
const namePredicate = names ? ` AND (r.name IS NULL OR r.name IN (${names.map(() => "?").join(",")}))` : "";
|
|
21
21
|
const params = names ? [
|
|
22
22
|
at,
|
|
23
23
|
at,
|
|
@@ -80,7 +80,7 @@ const createMysqlQueue = (sql, t, id) => {
|
|
|
80
80
|
async depth(now, names) {
|
|
81
81
|
if (names && names.length === 0) return queueDepthOf([], now.getTime());
|
|
82
82
|
const nowMs = now.getTime();
|
|
83
|
-
const namePredicate = names ? ` WHERE r.name IN (${names.map(() => "?").join(",")})` : "";
|
|
83
|
+
const namePredicate = names ? ` WHERE r.name IS NULL OR r.name IN (${names.map(() => "?").join(",")})` : "";
|
|
84
84
|
const r = (await sql.query(`SELECT SUM(claimable) AS claimable, SUM(leased) AS leased,
|
|
85
85
|
MIN(CASE WHEN claimable = 1 THEN run_at END) AS oldest
|
|
86
86
|
FROM (SELECT j.run_at AS run_at,
|
|
@@ -121,6 +121,11 @@ const tables = (prefix) => {
|
|
|
121
121
|
* MySQL has no implicit rowid). Indexed string columns are `VARCHAR(191)` to stay under the utf8mb4
|
|
122
122
|
* index-key limit. The idempotency/signal-dedup UNIQUE KEYs need no partial `WHERE`: MySQL treats
|
|
123
123
|
* NULLs as distinct, so unkeyed rows never collide.
|
|
124
|
+
*
|
|
125
|
+
* Also creates the `pending_work` function, so applying this needs `CREATE ROUTINE` as well as
|
|
126
|
+
* `CREATE TABLE`. MySQL has no `CREATE OR REPLACE FUNCTION`, so that one statement is a `DROP` +
|
|
127
|
+
* `CREATE` rather than `IF NOT EXISTS` — a scaler polling the function across a boot can see one
|
|
128
|
+
* failed read.
|
|
124
129
|
*/
|
|
125
130
|
const ddl = (prefix = "") => {
|
|
126
131
|
const t = tables(prefix);
|
|
@@ -196,11 +201,16 @@ const ddl = (prefix = "") => {
|
|
|
196
201
|
`CREATE FUNCTION \`${prefix}pending_work\`(flow_names JSON, as_of BIGINT)
|
|
197
202
|
RETURNS BIGINT READS SQL DATA
|
|
198
203
|
RETURN (
|
|
199
|
-
(SELECT COUNT(*) FROM ${t.job} j
|
|
204
|
+
(SELECT COUNT(*) FROM ${t.job} j
|
|
200
205
|
WHERE j.run_at <= as_of AND (j.lease_expires IS NULL OR j.lease_expires <= as_of)
|
|
201
|
-
AND (flow_names IS NULL OR
|
|
202
|
-
|
|
203
|
-
|
|
206
|
+
AND (flow_names IS NULL OR (JSON_LENGTH(flow_names) > 0 AND NOT EXISTS (
|
|
207
|
+
SELECT 1 FROM ${t.run} r
|
|
208
|
+
WHERE r.id = j.run_id AND NOT JSON_CONTAINS(flow_names, JSON_QUOTE(r.name))))))
|
|
209
|
+
+ (SELECT COUNT(*) FROM ${t.timer} tm
|
|
210
|
+
WHERE tm.fire_at <= as_of
|
|
211
|
+
AND (flow_names IS NULL OR (JSON_LENGTH(flow_names) > 0 AND NOT EXISTS (
|
|
212
|
+
SELECT 1 FROM ${t.run} r
|
|
213
|
+
WHERE r.id = tm.run_id AND NOT JSON_CONTAINS(flow_names, JSON_QUOTE(r.name))))))
|
|
204
214
|
+ (SELECT COUNT(*) FROM ${t.cron} c
|
|
205
215
|
WHERE c.next_run_at <= as_of AND (flow_names IS NULL OR JSON_CONTAINS(flow_names, JSON_QUOTE(c.flow_name))))
|
|
206
216
|
)`
|
|
@@ -299,6 +309,10 @@ const sqlTuple = (statuses) => `(${statuses.map((s) => `'${s}'`).join(",")})`;
|
|
|
299
309
|
const TERMINAL = sqlTuple(TERMINAL_STATUSES);
|
|
300
310
|
const RECONCILABLE = sqlTuple(RECONCILABLE_STATUSES);
|
|
301
311
|
const NON_SUCCESS_TERMINAL = sqlTuple(NON_SUCCESS_TERMINAL_STATUSES);
|
|
312
|
+
const MS_PURGE = {
|
|
313
|
+
placeholder: () => "?",
|
|
314
|
+
time: (at) => at.getTime()
|
|
315
|
+
};
|
|
302
316
|
const inList = (n) => `(${Array.from({ length: n }, () => "?").join(",")})`;
|
|
303
317
|
/** @internal */
|
|
304
318
|
const createMysqlStore = (sql, t, id) => {
|
|
@@ -358,6 +372,24 @@ const createMysqlStore = (sql, t, id) => {
|
|
|
358
372
|
status: "pending"
|
|
359
373
|
};
|
|
360
374
|
};
|
|
375
|
+
const deleteRuns = async (filter, limit) => {
|
|
376
|
+
const q = purgeWhereSql(filter, MS_PURGE);
|
|
377
|
+
if (!q) return 0;
|
|
378
|
+
const params = [...q.params, limit];
|
|
379
|
+
return sql.tx(async (tx) => {
|
|
380
|
+
const ids = (await tx.query(`SELECT id FROM ${t.run}
|
|
381
|
+
WHERE ${q.where}
|
|
382
|
+
ORDER BY seq LIMIT ?`, params)).map((r) => r.id);
|
|
383
|
+
if (ids.length === 0) return 0;
|
|
384
|
+
const inIds = inList(ids.length);
|
|
385
|
+
await tx.exec(`DELETE FROM ${t.step} WHERE run_id IN ${inIds}`, ids);
|
|
386
|
+
await tx.exec(`DELETE FROM ${t.signal} WHERE run_id IN ${inIds}`, ids);
|
|
387
|
+
await tx.exec(`DELETE FROM ${t.job} WHERE run_id IN ${inIds}`, ids);
|
|
388
|
+
await tx.exec(`DELETE FROM ${t.timer} WHERE run_id IN ${inIds}`, ids);
|
|
389
|
+
await tx.exec(`DELETE FROM ${t.run} WHERE id IN ${inIds}`, ids);
|
|
390
|
+
return ids.length;
|
|
391
|
+
});
|
|
392
|
+
};
|
|
361
393
|
return {
|
|
362
394
|
startRun(spec) {
|
|
363
395
|
return startOne(sql, spec);
|
|
@@ -418,7 +450,7 @@ const createMysqlStore = (sql, t, id) => {
|
|
|
418
450
|
const cur = await tx.query(`SELECT status, attempts FROM ${t.run} WHERE id = ? FOR UPDATE`, [runId]);
|
|
419
451
|
if (!cur[0]) throw new Error(`markRunning: run ${runId} not found`);
|
|
420
452
|
const attempts = Number(cur[0].attempts);
|
|
421
|
-
if (
|
|
453
|
+
if (isTerminal(cur[0].status)) return attempts;
|
|
422
454
|
await tx.exec(`UPDATE ${t.run} SET status = 'running', attempts = attempts + 1 WHERE id = ?`, [runId]);
|
|
423
455
|
return attempts + 1;
|
|
424
456
|
});
|
|
@@ -519,21 +551,8 @@ const createMysqlStore = (sql, t, id) => {
|
|
|
519
551
|
limit: "?"
|
|
520
552
|
}), [limit])).map((r) => r.id);
|
|
521
553
|
},
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const ids = (await tx.query(`SELECT id FROM ${t.run}
|
|
525
|
-
WHERE status IN ${TERMINAL} AND created_at < ?
|
|
526
|
-
ORDER BY seq LIMIT ?`, [before.getTime(), limit])).map((r) => r.id);
|
|
527
|
-
if (ids.length === 0) return 0;
|
|
528
|
-
const inIds = inList(ids.length);
|
|
529
|
-
await tx.exec(`DELETE FROM ${t.step} WHERE run_id IN ${inIds}`, ids);
|
|
530
|
-
await tx.exec(`DELETE FROM ${t.signal} WHERE run_id IN ${inIds}`, ids);
|
|
531
|
-
await tx.exec(`DELETE FROM ${t.job} WHERE run_id IN ${inIds}`, ids);
|
|
532
|
-
await tx.exec(`DELETE FROM ${t.timer} WHERE run_id IN ${inIds}`, ids);
|
|
533
|
-
await tx.exec(`DELETE FROM ${t.run} WHERE id IN ${inIds}`, ids);
|
|
534
|
-
return ids.length;
|
|
535
|
-
});
|
|
536
|
-
},
|
|
554
|
+
deleteRuns,
|
|
555
|
+
deleteRunsOlderThan: (before, limit) => deleteRuns({ before }, limit),
|
|
537
556
|
retryRun(runId) {
|
|
538
557
|
return sql.tx(async (tx) => {
|
|
539
558
|
if ((await tx.exec(`UPDATE ${t.run} SET status = 'pending', error = NULL WHERE id = ? AND status = 'failed'`, [runId])).affectedRows !== 1) return { retried: false };
|
|
@@ -603,7 +622,7 @@ const createMysqlTimer = (sql, t) => {
|
|
|
603
622
|
},
|
|
604
623
|
async dueCount(now, names) {
|
|
605
624
|
if (names && names.length === 0) return 0;
|
|
606
|
-
const namePredicate = names ? ` AND r.name IN (${names.map(() => "?").join(",")})` : "";
|
|
625
|
+
const namePredicate = names ? ` AND (r.name IS NULL OR r.name IN (${names.map(() => "?").join(",")}))` : "";
|
|
607
626
|
const params = names ? [now.getTime(), ...names] : [now.getTime()];
|
|
608
627
|
const rows = await sql.query(`SELECT count(*) AS n FROM ${t.timer} tm LEFT JOIN ${t.run} r ON r.id = tm.run_id
|
|
609
628
|
WHERE tm.fire_at <= ?${namePredicate}`, params);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iterativeflow/mysql",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@iterativeflow/core": "2.
|
|
28
|
+
"@iterativeflow/core": "2.2.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"mysql2": ">=3.0.0"
|