cairnq 0.1.0 → 0.3.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/README.md +28 -0
- package/dist/_protocol/migrations/postgres/0001_init.sql +3 -1
- package/dist/_protocol/migrations/postgres/0002_purge_index.sql +6 -0
- package/dist/_protocol/migrations/postgres/0003_notify.sql +38 -0
- package/dist/_protocol/migrations/postgres/0004_lease_index.sql +16 -0
- package/dist/_protocol/migrations/postgres/0005_clear_terminal_lease.sql +17 -0
- package/dist/_protocol/migrations/sqlite/0001_init.sql +3 -1
- package/dist/_protocol/migrations/sqlite/0002_purge_index.sql +6 -0
- package/dist/_protocol/migrations/sqlite/0004_lease_index.sql +22 -0
- package/dist/_protocol/migrations/sqlite/0005_clear_terminal_lease.sql +17 -0
- package/dist/_protocol/sql/postgres/claim.sql +18 -5
- package/dist/_protocol/sql/postgres/claim_one_queue.sql +35 -0
- package/dist/_protocol/sql/postgres/complete.sql +3 -0
- package/dist/_protocol/sql/postgres/fail.sql +30 -8
- package/dist/_protocol/sql/postgres/insert_task.sql +6 -3
- package/dist/_protocol/sql/postgres/list.sql +3 -1
- package/dist/_protocol/sql/postgres/lock_key.sql +9 -0
- package/dist/_protocol/sql/postgres/progress.sql +4 -3
- package/dist/_protocol/sql/postgres/protocol_version.sql +4 -0
- package/dist/_protocol/sql/postgres/purge.sql +25 -0
- package/dist/_protocol/sql/postgres/recover_leases.sql +49 -14
- package/dist/_protocol/sql/postgres/retry.sql +3 -0
- package/dist/_protocol/sql/postgres/stats.sql +8 -0
- package/dist/_protocol/sql/postgres/succeed.sql +4 -0
- package/dist/_protocol/sql/sqlite/claim.sql +13 -2
- package/dist/_protocol/sql/sqlite/claim_one_queue.sql +36 -0
- package/dist/_protocol/sql/sqlite/claimable_probe.sql +6 -2
- package/dist/_protocol/sql/sqlite/complete.sql +3 -0
- package/dist/_protocol/sql/sqlite/fail.sql +32 -8
- package/dist/_protocol/sql/sqlite/list.sql +3 -1
- package/dist/_protocol/sql/sqlite/lock_key.sql +5 -0
- package/dist/_protocol/sql/sqlite/progress.sql +6 -2
- package/dist/_protocol/sql/sqlite/protocol_version.sql +4 -0
- package/dist/_protocol/sql/sqlite/purge.sql +18 -0
- package/dist/_protocol/sql/sqlite/recover_leases.sql +25 -7
- package/dist/_protocol/sql/sqlite/retry.sql +3 -0
- package/dist/_protocol/sql/sqlite/stats.sql +8 -0
- package/dist/_protocol/sql/sqlite/succeed.sql +4 -0
- package/dist/client.d.ts +10 -2
- package/dist/client.js +12 -0
- package/dist/context.d.ts +17 -1
- package/dist/context.js +60 -6
- package/dist/errors.d.ts +18 -2
- package/dist/errors.js +49 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/sql.js +16 -9
- package/dist/store/base.d.ts +114 -9
- package/dist/store/base.js +376 -1
- package/dist/store/postgres.d.ts +62 -63
- package/dist/store/postgres.js +245 -222
- package/dist/store/sqlite.d.ts +83 -60
- package/dist/store/sqlite.js +370 -234
- package/dist/wait.d.ts +15 -2
- package/dist/wait.js +23 -5
- package/dist/worker.d.ts +53 -1
- package/dist/worker.js +202 -42
- package/package.json +9 -2
- package/src/client.ts +16 -2
- package/src/context.ts +70 -13
- package/src/errors.ts +59 -4
- package/src/index.ts +3 -1
- package/src/sql.ts +15 -8
- package/src/store/base.ts +443 -27
- package/src/store/postgres.ts +243 -267
- package/src/store/sqlite.ts +378 -263
- package/src/wait.ts +28 -5
- package/src/worker.ts +242 -42
package/dist/store/sqlite.d.ts
CHANGED
|
@@ -1,20 +1,40 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import type { ListInput, SubmitInput, TaskStore } from "./base.js";
|
|
1
|
+
import { type Fetch, type Params, TaskStore } from "./base.js";
|
|
3
2
|
/**
|
|
4
|
-
* SQLiteStore —
|
|
3
|
+
* SQLiteStore — the SQLite dialect of the shared cairnq-protocol SQL.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* Everything protocol-shaped lives in TaskStore; this file is only what SQLite
|
|
6
|
+
* does differently: better-sqlite3's synchronous driver, BEGIN IMMEDIATE
|
|
7
|
+
* transactions, a read-only probe in front of the write lock, and time supplied
|
|
8
|
+
* by the SDK (`:now_ms`) rather than by the database.
|
|
9
|
+
*
|
|
10
|
+
* The driver being synchronous suits SQLite's single writer: claim is one short
|
|
11
|
+
* transaction, the handler runs outside any transaction, and
|
|
12
|
+
* progress/heartbeat/succeed/fail are each their own short write.
|
|
13
|
+
*
|
|
14
|
+
* Cross-process contention is absorbed by retrying in JavaScript, not by
|
|
15
|
+
* busy_timeout. The two cost the same wait but not the same blocking: a nonzero
|
|
16
|
+
* busy_timeout waits *inside* the synchronous driver, so a caller that loses the
|
|
17
|
+
* write lock stalls this process's event loop for up to the whole timeout — the
|
|
18
|
+
* P99 of an HTTP server that submits tasks. Executing a statement takes
|
|
19
|
+
* microseconds; waiting for a lock takes milliseconds to seconds, and only the
|
|
20
|
+
* second part needs to happen off the thread. So busy_timeout goes to 0 (fail
|
|
21
|
+
* immediately) and the wait becomes an awaited backoff, which the event loop runs
|
|
22
|
+
* through. The budget is the same either way — `busyTimeoutMs`.
|
|
23
|
+
*
|
|
24
|
+
* The open path keeps a real busy_timeout: it is synchronous by nature (WAL
|
|
25
|
+
* switch, migrations) and happens once, under the caller's `connect()`.
|
|
11
26
|
*/
|
|
12
|
-
export declare class SQLiteStore
|
|
27
|
+
export declare class SQLiteStore extends TaskStore {
|
|
13
28
|
private readonly path;
|
|
14
|
-
private readonly opts;
|
|
15
29
|
private db;
|
|
16
30
|
private stmts;
|
|
17
31
|
private readonly statements;
|
|
32
|
+
/** This store's entry in `fileLocks` — see there for why it is per-database. */
|
|
33
|
+
private readonly lockKey;
|
|
34
|
+
/** How long a single operation may keep retrying a lost write lock. */
|
|
35
|
+
private readonly busyBudgetMs;
|
|
36
|
+
/** When this connection may next revisit its planner statistics. */
|
|
37
|
+
private nextStatsRefreshAt;
|
|
18
38
|
constructor(path: string, opts?: {
|
|
19
39
|
busyTimeoutMs?: number;
|
|
20
40
|
});
|
|
@@ -22,56 +42,59 @@ export declare class SQLiteStore implements TaskStore {
|
|
|
22
42
|
close(): Promise<void>;
|
|
23
43
|
private ensure;
|
|
24
44
|
private applyMigrations;
|
|
25
|
-
private checkVersion;
|
|
26
45
|
private readProtocolVersion;
|
|
27
46
|
protocolVersion(): Promise<number>;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Adapt the dialect-neutral parameters to what this statement binds.
|
|
49
|
+
*
|
|
50
|
+
* SQLite statements carry no DB clock, so every absolute `*_ms` is derived here
|
|
51
|
+
* from one `now`, and booleans cross as 0/1. The result is narrowed to the
|
|
52
|
+
* names the SQL actually uses, which is what makes it safe for a caller to pass
|
|
53
|
+
* one superset of parameters for both dialects.
|
|
54
|
+
*
|
|
55
|
+
* Each derivation writes a name Postgres does not use (`lease_until_ms` from
|
|
56
|
+
* `lease_ms`, and so on), so a statement binds one or the other, never both —
|
|
57
|
+
* which is why the derived values can be computed unconditionally and left for
|
|
58
|
+
* the narrowing step to discard.
|
|
59
|
+
*/
|
|
60
|
+
private bind;
|
|
61
|
+
private runNow;
|
|
62
|
+
/** Queue an operation behind every other operation on this database. */
|
|
63
|
+
private enqueue;
|
|
64
|
+
/**
|
|
65
|
+
* Serialize an operation against this database, waiting out a lost write lock on
|
|
66
|
+
* a jittered backoff. Replaces busy_timeout's synchronous wait (see the class
|
|
67
|
+
* comment); on exhausting the budget the original SQLITE_BUSY surfaces, which is
|
|
68
|
+
* what a nonzero busy_timeout would have thrown too.
|
|
69
|
+
*
|
|
70
|
+
* Each attempt re-queues rather than backing off while holding its turn: the
|
|
71
|
+
* contention left to retry is cross-process, and under WAL a *reader* never sees
|
|
72
|
+
* SQLITE_BUSY at all — so sleeping in place would stall this process's reads
|
|
73
|
+
* (including the worker's own poll) on a lock they were never waiting for.
|
|
74
|
+
*
|
|
75
|
+
* Retrying is safe because an attempt is one statement, or one transaction that
|
|
76
|
+
* has already rolled back: nothing partially applied survives it. `fn` may
|
|
77
|
+
* therefore run more than once and must not carry effects of its own — the
|
|
78
|
+
* callers in TaskStore build their ids and payloads before opening one.
|
|
79
|
+
*/
|
|
80
|
+
private withLock;
|
|
81
|
+
/**
|
|
82
|
+
* Revisit this connection's planner statistics, at most once per
|
|
83
|
+
* STATS_REFRESH_INTERVAL_MS.
|
|
84
|
+
*
|
|
85
|
+
* A connection lives for days, and the statements were prepared against whatever
|
|
86
|
+
* the table looked like when it opened — a worker started against an empty
|
|
87
|
+
* database plans as if it were still empty however large the backlog grows. The
|
|
88
|
+
* prepared statements do pick the refreshed plans up: ANALYZE bumps the schema
|
|
89
|
+
* cookie, so SQLite silently re-prepares them on next use. That is what makes
|
|
90
|
+
* this worth doing rather than a restart-only concern.
|
|
91
|
+
*
|
|
92
|
+
* Queued rather than run under `withLock`: statistics are best-effort, so losing
|
|
93
|
+
* the write lock to another process should cost nothing — skip and let the next
|
|
94
|
+
* interval try, instead of spending an operation's whole retry budget on them.
|
|
95
|
+
*/
|
|
96
|
+
private maybeRefreshStatistics;
|
|
97
|
+
protected fetch(name: string, params: Params): Promise<any[]>;
|
|
98
|
+
protected tx<T>(fn: (fetch: Fetch) => Promise<T>): Promise<T>;
|
|
99
|
+
protected hasClaimableWork(params: Params): Promise<boolean>;
|
|
77
100
|
}
|