@sqlane/core 0.1.0 → 0.1.2

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.
@@ -0,0 +1,51 @@
1
+ export type Executor = import('../types.js').Executor;
2
+ export type ProbeRow = import('./store.js').ProbeRow;
3
+ export type Candidate = import('./store.js').Candidate;
4
+ export type ClaimParams = import('./store.js').ClaimParams;
5
+ export type ClaimedJob = import('./store.js').ClaimedJob;
6
+ /** @typedef {import('../types.js').Executor} Executor */
7
+ /** @typedef {import('./store.js').ProbeRow} ProbeRow */
8
+ /** @typedef {import('./store.js').Candidate} Candidate */
9
+ /** @typedef {import('./store.js').ClaimParams} ClaimParams */
10
+ /** @typedef {import('./store.js').ClaimedJob} ClaimedJob */
11
+ /** The due jobs of each keyed name that the FIFO of the mutex keys reads */
12
+ export declare const MUTEX_WINDOW_SIZE = 500;
13
+ /**
14
+ * The SQL of the probe. `GROUP BY status, job_name`: the whole index prefix, else MySQL does not use a loose index
15
+ * scan.
16
+ * @param {string} p
17
+ * @returns {string}
18
+ */
19
+ export declare function probeSql(p: string): string;
20
+ /**
21
+ * The next due time of each job name with pending jobs: one index dive per pending name (loose index scan).
22
+ * @param {Executor} executor
23
+ * @param {string} p
24
+ * @returns {Promise<ProbeRow[]>}
25
+ */
26
+ export declare function probe(executor: Executor, p: string): Promise<ProbeRow[]>;
27
+ /**
28
+ * The due jobs that can start now, oldest first: at most `limit` of each name. A job of a keyed name waits if one of
29
+ * its keys is held, or an older due job of a keyed name has it (FIFO per key over the window of the keyed names).
30
+ * @param {Executor} executor
31
+ * @param {string} p
32
+ * @param {Map<string, number>} limitsByName
33
+ * @param {string[]} keyedNames The handled names whose definition has mutex keys.
34
+ * @returns {Promise<Candidate[]>}
35
+ */
36
+ export declare function findCandidates(executor: Executor, p: string, limitsByName: Map<string, number>, keyedNames: string[]): Promise<Candidate[]>;
37
+ /**
38
+ * Claims due jobs among the candidates. Call it in a queue transaction.
39
+ * @param {Executor} executor
40
+ * @param {string} p
41
+ * @param {ClaimParams} params
42
+ * @returns {Promise<ClaimedJob[]>} In the order of run_at.
43
+ */
44
+ export declare function claim(executor: Executor, p: string, { candidateIds, lockToken, lockedBy, leaseMs, pick }: ClaimParams): Promise<ClaimedJob[]>;
45
+ /**
46
+ * @param {Executor} executor
47
+ * @param {string} p
48
+ * @param {string[]} ids
49
+ * @returns {Promise<Map<string, unknown>>} The payloads by job id, as JSON gives them back.
50
+ */
51
+ export declare function readPayloads(executor: Executor, p: string, ids: string[]): Promise<Map<string, unknown>>;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Inserts the jobs and their payloads.
3
+ * @param {import('../types.js').Executor} executor
4
+ * @param {string} p The table prefix.
5
+ * @param {import('../new_job.js').NewJob[]} jobs Their ids are distinct.
6
+ * @param {string} enqueueToken
7
+ * @returns {Promise<Set<string>>} The ids that this call created.
8
+ */
9
+ export declare function insertJobsPostgres(executor: import('../types.js').Executor, p: string, jobs: import('../new_job.js').NewJob[], enqueueToken: string): Promise<Set<string>>;
@@ -0,0 +1,135 @@
1
+ export type Executor = import('../types.js').Executor;
2
+ export type OwnedJob = import('./store.js').OwnedJob;
3
+ export type FailureJobBuilder = import('./store.js').FailureJobBuilder;
4
+ export type FailureJobResult = import('./store.js').FailureJobResult;
5
+ /**
6
+ * Renews the leases of the jobs that the process runs. A row locked by another transaction is skipped: it does not
7
+ * block the others.
8
+ * @param {Executor} executor
9
+ * @param {string} p
10
+ * @param {OwnedJob[]} jobs
11
+ * @param {number} leaseMs
12
+ * @returns {Promise<Set<string>>} The ids whose lease was renewed.
13
+ */
14
+ export declare function renewLeases(executor: Executor, p: string, jobs: OwnedJob[], leaseMs: number): Promise<Set<string>>;
15
+ /**
16
+ * Marks a timeout: the job stays running until its handler settles (`stuck` in the stats).
17
+ * @param {Executor} executor
18
+ * @param {string} p
19
+ * @param {OwnedJob} job
20
+ * @returns {Promise<boolean>} False: the lease was lost.
21
+ */
22
+ export declare function markTimedOut(executor: Executor, p: string, job: OwnedJob): Promise<boolean>;
23
+ /**
24
+ * The jobs are done: completed until their retention ends, without their payloads; their keys are freed. One
25
+ * statement for many jobs.
26
+ * @param {Executor} executor
27
+ * @param {string} p
28
+ * @param {import('./store.js').FinishedJob[]} jobs
29
+ * @returns {Promise<number>} The jobs that this call completed: the others lost their lease, or an earlier try
30
+ * committed.
31
+ */
32
+ export declare function completeJobs(executor: Executor, p: string, jobs: import('./store.js').FinishedJob[]): Promise<number>;
33
+ /**
34
+ * The attempt failed: pending again after the delay, one more attempt used; its keys are freed.
35
+ * @param {Executor} executor
36
+ * @param {string} p
37
+ * @param {OwnedJob} job
38
+ * @param {{ delayMs: number, error: import('../error_json.js').ErrorJson }} params
39
+ * @returns {Promise<boolean>} False: no change.
40
+ */
41
+ export declare function retryJob(executor: Executor, p: string, job: OwnedJob, { delayMs, error }: {
42
+ delayMs: number;
43
+ error: import('../error_json.js').ErrorJson;
44
+ }): Promise<boolean>;
45
+ /**
46
+ * The job fails for good: failed until its retention ends, with its payload; its keys are freed; its failure job is
47
+ * added in the same transaction.
48
+ * @param {Executor} executor
49
+ * @param {string} p
50
+ * @param {OwnedJob} job
51
+ * @param {{ error: import('../error_json.js').ErrorJson, buildFailureJob: FailureJobBuilder }} params
52
+ * @returns {Promise<{ changed: boolean, failureJob: FailureJobResult | null }>}
53
+ */
54
+ export declare function failJob(executor: Executor, p: string, job: OwnedJob, { error, buildFailureJob }: {
55
+ error: import('../error_json.js').ErrorJson;
56
+ buildFailureJob: FailureJobBuilder;
57
+ }): Promise<{
58
+ changed: boolean;
59
+ failureJob: FailureJobResult | null;
60
+ }>;
61
+ /**
62
+ * The job is released, without an attempt used: pending again after the delay.
63
+ * @param {Executor} executor
64
+ * @param {string} p
65
+ * @param {OwnedJob} job
66
+ * @param {{ delayMs: number, lingerMs: number }} params lingerMs: its keys stay held this long (its handler still
67
+ * runs); 0: they are freed.
68
+ * @returns {Promise<boolean>} False: no change.
69
+ */
70
+ export declare function releaseJob(executor: Executor, p: string, job: OwnedJob, { delayMs, lingerMs }: {
71
+ delayMs: number;
72
+ lingerMs: number;
73
+ }): Promise<boolean>;
74
+ /**
75
+ * @param {Executor} executor
76
+ * @param {string} p
77
+ * @param {number} limit
78
+ * @returns {Promise<{ id: string, name: string }[]>} The running jobs whose lease ended, the oldest first.
79
+ */
80
+ export declare function findStaleJobs(executor: Executor, p: string, limit: number): Promise<{
81
+ id: string;
82
+ name: string;
83
+ }[]>;
84
+ /**
85
+ * A running job whose lease ended: one more stall, pending again after 30 s × 2^stalls (at most 1 h); after maxStalls,
86
+ * it fails for good. Its keys linger for one lease: its old handler may still run.
87
+ * @param {Executor} executor
88
+ * @param {string} p
89
+ * @param {string} id
90
+ * @param {{ leaseMs: number, buildFailureJob: FailureJobBuilder }} params
91
+ * @returns {Promise<{ result: 'released' | 'failed' | null, failureJob: FailureJobResult | null }>} null: not stale
92
+ * (any more).
93
+ */
94
+ export declare function recoverStaleJob(executor: Executor, p: string, id: string, { leaseMs, buildFailureJob }: {
95
+ leaseMs: number;
96
+ buildFailureJob: FailureJobBuilder;
97
+ }): Promise<{
98
+ result: 'released' | 'failed' | null;
99
+ failureJob: FailureJobResult | null;
100
+ }>;
101
+ /**
102
+ * Deletes the ended lingers, and the holds without their running job (from a manual change, or old code).
103
+ * @param {Executor} executor
104
+ * @param {string} p
105
+ * @param {number} limit
106
+ * @returns {Promise<{ lingers: number, orphans: number }>}
107
+ */
108
+ export declare function sweepMutexes(executor: Executor, p: string, limit: number): Promise<{
109
+ lingers: number;
110
+ orphans: number;
111
+ }>;
112
+ /**
113
+ * Deletes a batch of completed and failed jobs whose retention ended, with their payloads. SKIP LOCKED: processes
114
+ * share the work.
115
+ * @param {Executor} executor
116
+ * @param {string} p
117
+ * @param {number} limit
118
+ * @returns {Promise<number>} The deleted jobs.
119
+ */
120
+ export declare function deleteExpiredJobs(executor: Executor, p: string, limit: number): Promise<number>;
121
+ /**
122
+ * For drain({ includeDelayed }): the pending jobs of the names are due now.
123
+ * @param {Executor} executor
124
+ * @param {string} p
125
+ * @param {string[]} names
126
+ * @returns {Promise<void>}
127
+ */
128
+ export declare function makeDue(executor: Executor, p: string, names: string[]): Promise<void>;
129
+ /**
130
+ * The counts per job name, for alerts such as "queue stuck".
131
+ * @param {Executor} executor
132
+ * @param {string} p
133
+ * @returns {Promise<import('../types.js').JobStats[]>}
134
+ */
135
+ export declare function getStats(executor: Executor, p: string): Promise<import('../types.js').JobStats[]>;
@@ -0,0 +1,45 @@
1
+ export type Executor = import('../types.js').Executor;
2
+ export type ProbeRow = import('./store.js').ProbeRow;
3
+ export type Candidate = import('./store.js').Candidate;
4
+ export type ClaimParams = import('./store.js').ClaimParams;
5
+ export type ClaimedJob = import('./store.js').ClaimedJob;
6
+ /** @typedef {import('../types.js').Executor} Executor */
7
+ /** @typedef {import('./store.js').ProbeRow} ProbeRow */
8
+ /** @typedef {import('./store.js').Candidate} Candidate */
9
+ /** @typedef {import('./store.js').ClaimParams} ClaimParams */
10
+ /** @typedef {import('./store.js').ClaimedJob} ClaimedJob */
11
+ /** The due jobs of each keyed name that the FIFO of the mutex keys reads */
12
+ export declare const MUTEX_WINDOW_SIZE = 500;
13
+ /**
14
+ * The next due time of each job name with pending jobs. PostgreSQL has no loose index scan: a recursive CTE skips from
15
+ * one pending name to the next on the partial index, two index probes per pending name.
16
+ * @param {Executor} executor
17
+ * @param {string} p
18
+ * @returns {Promise<ProbeRow[]>}
19
+ */
20
+ export declare function probe(executor: Executor, p: string): Promise<ProbeRow[]>;
21
+ /**
22
+ * The due jobs that can start now, oldest first: at most `limit` of each name. A job of a keyed name waits if one of
23
+ * its keys is held, or an older due job of a keyed name has it (FIFO per key over the window of the keyed names).
24
+ * @param {Executor} executor
25
+ * @param {string} p
26
+ * @param {Map<string, number>} limitsByName
27
+ * @param {string[]} keyedNames The handled names whose definition has mutex keys.
28
+ * @returns {Promise<Candidate[]>}
29
+ */
30
+ export declare function findCandidates(executor: Executor, p: string, limitsByName: Map<string, number>, keyedNames: string[]): Promise<Candidate[]>;
31
+ /**
32
+ * Claims due jobs among the candidates. Call it in a queue transaction.
33
+ * @param {Executor} executor
34
+ * @param {string} p
35
+ * @param {ClaimParams} params
36
+ * @returns {Promise<ClaimedJob[]>} In the order of run_at.
37
+ */
38
+ export declare function claim(executor: Executor, p: string, { candidateIds, lockToken, lockedBy, leaseMs, pick }: ClaimParams): Promise<ClaimedJob[]>;
39
+ /**
40
+ * @param {Executor} executor
41
+ * @param {string} p
42
+ * @param {string[]} ids
43
+ * @returns {Promise<Map<string, unknown>>} The payloads by job id, as JSON gives them back.
44
+ */
45
+ export declare function readPayloads(executor: Executor, p: string, ids: string[]): Promise<Map<string, unknown>>;
@@ -0,0 +1,221 @@
1
+ export type ProbeRow = {
2
+ /**
3
+ * A job name with pending jobs.
4
+ */
5
+ name: string;
6
+ /**
7
+ * Until its oldest pending job is due: 0 or less means due now.
8
+ */
9
+ delayMs: number;
10
+ };
11
+ export type Candidate = {
12
+ id: string;
13
+ name: string;
14
+ runAtMs: number;
15
+ };
16
+ export type ClaimedJob = {
17
+ id: string;
18
+ name: string;
19
+ mutexKeys: string[];
20
+ /**
21
+ * The failed attempts so far.
22
+ */
23
+ attempts: number;
24
+ maxAttempts: number;
25
+ stalls: number;
26
+ maxStalls: number;
27
+ failureJob: string | null;
28
+ enqueuedAt: Date;
29
+ /**
30
+ * Since it was due.
31
+ */
32
+ waitedMs: number;
33
+ lockToken: string;
34
+ };
35
+ export type ClaimParams = {
36
+ candidateIds: string[];
37
+ /**
38
+ * Of this claim.
39
+ */
40
+ lockToken: string;
41
+ /**
42
+ * The process.
43
+ */
44
+ lockedBy: string;
45
+ leaseMs: number;
46
+ /**
47
+ * The jobs to take among the locked
48
+ * ones (in the order of run_at): the free slots, the free keys. The keys of the picked jobs must be distinct.
49
+ */
50
+ pick: (locked: ClaimedJob[], busyKeys: Set<string>) => ClaimedJob[];
51
+ };
52
+ export type OwnedJob = {
53
+ id: string;
54
+ lockToken: string;
55
+ };
56
+ export type FinishedJob = OwnedJob & {
57
+ mutexKeys: readonly string[];
58
+ };
59
+ export type FailedJob = {
60
+ id: string;
61
+ name: string;
62
+ /**
63
+ * Null: its payload is missing.
64
+ */
65
+ payload: unknown;
66
+ attempt: number;
67
+ maxAttempts: number;
68
+ /**
69
+ * The name of its failure job.
70
+ */
71
+ failureJob: string;
72
+ error: import('../error_json.js').ErrorJson;
73
+ };
74
+ export type FailureJobBuilder = (failed: FailedJob) => import('../new_job.js').NewJob;
75
+ export type FailureJobResult = {
76
+ added: import('../new_job.js').NewJob;
77
+ } | {
78
+ notAdded: unknown;
79
+ };
80
+ export type Store = {
81
+ probe: () => Promise<ProbeRow[]>;
82
+ findCandidates: (limitsByName: Map<string, number>, keyedNames: string[]) => Promise<Candidate[]>;
83
+ /**
84
+ * In a queue transaction of its own.
85
+ */
86
+ claim: (params: ClaimParams) => Promise<ClaimedJob[]>;
87
+ readPayloads: (ids: string[]) => Promise<Map<string, unknown>>;
88
+ renewLeases: (jobs: OwnedJob[], leaseMs: number) => Promise<Set<string>>;
89
+ markTimedOut: (job: OwnedJob) => Promise<boolean>;
90
+ /**
91
+ * In one transaction: the jobs that it completed.
92
+ */
93
+ completeJobs: (jobs: FinishedJob[]) => Promise<number>;
94
+ retryJob: (job: OwnedJob, params: {
95
+ delayMs: number;
96
+ error: import('../error_json.js').ErrorJson;
97
+ }) => Promise<boolean>;
98
+ failJob: (job: OwnedJob, params: {
99
+ error: import('../error_json.js').ErrorJson;
100
+ buildFailureJob: FailureJobBuilder;
101
+ }) => Promise<{
102
+ changed: boolean;
103
+ failureJob: FailureJobResult | null;
104
+ }>;
105
+ releaseJob: (job: OwnedJob, params: {
106
+ delayMs: number;
107
+ lingerMs: number;
108
+ }) => Promise<boolean>;
109
+ findStaleJobs: (limit: number) => Promise<{
110
+ id: string;
111
+ name: string;
112
+ }[]>;
113
+ recoverStaleJob: (id: string, params: {
114
+ leaseMs: number;
115
+ buildFailureJob: FailureJobBuilder;
116
+ }) => Promise<{
117
+ result: 'released' | 'failed' | null;
118
+ failureJob: FailureJobResult | null;
119
+ }>;
120
+ sweepMutexes: (limit: number) => Promise<{
121
+ lingers: number;
122
+ orphans: number;
123
+ }>;
124
+ deleteExpiredJobs: (limit: number) => Promise<number>;
125
+ /**
126
+ * For drain({ includeDelayed }).
127
+ */
128
+ makeDue: (names: string[]) => Promise<void>;
129
+ getStats: () => Promise<import('../types.js').JobStats[]>;
130
+ };
131
+ /**
132
+ * @typedef {object} ProbeRow
133
+ * @property {string} name A job name with pending jobs.
134
+ * @property {number} delayMs Until its oldest pending job is due: 0 or less means due now.
135
+ */
136
+ /**
137
+ * @typedef {object} Candidate
138
+ * @property {string} id
139
+ * @property {string} name
140
+ * @property {number} runAtMs
141
+ */
142
+ /**
143
+ * A job that a claim locked, and then claimed: it runs.
144
+ * @typedef {object} ClaimedJob
145
+ * @property {string} id
146
+ * @property {string} name
147
+ * @property {string[]} mutexKeys
148
+ * @property {number} attempts The failed attempts so far.
149
+ * @property {number} maxAttempts
150
+ * @property {number} stalls
151
+ * @property {number} maxStalls
152
+ * @property {string | null} failureJob
153
+ * @property {Date} enqueuedAt
154
+ * @property {number} waitedMs Since it was due.
155
+ * @property {string} lockToken
156
+ */
157
+ /**
158
+ * @typedef {object} ClaimParams
159
+ * @property {string[]} candidateIds
160
+ * @property {string} lockToken Of this claim.
161
+ * @property {string} lockedBy The process.
162
+ * @property {number} leaseMs
163
+ * @property {(locked: ClaimedJob[], busyKeys: Set<string>) => ClaimedJob[]} pick The jobs to take among the locked
164
+ * ones (in the order of run_at): the free slots, the free keys. The keys of the picked jobs must be distinct.
165
+ */
166
+ /**
167
+ * A job that this process claimed: every write of it checks the lock token.
168
+ * @typedef {object} OwnedJob
169
+ * @property {string} id
170
+ * @property {string} lockToken
171
+ */
172
+ /**
173
+ * A job that this process ran, with its keys.
174
+ * @typedef {OwnedJob & { mutexKeys: readonly string[] }} FinishedJob
175
+ */
176
+ /**
177
+ * The job that fails for good, as the failure job gets it.
178
+ * @typedef {object} FailedJob
179
+ * @property {string} id
180
+ * @property {string} name
181
+ * @property {unknown} payload Null: its payload is missing.
182
+ * @property {number} attempt
183
+ * @property {number} maxAttempts
184
+ * @property {string} failureJob The name of its failure job.
185
+ * @property {import('../error_json.js').ErrorJson} error
186
+ */
187
+ /**
188
+ * Builds the failure job: its options from its definition, else the defaults; no failure job of its own.
189
+ * @typedef {(failed: FailedJob) => import('../new_job.js').NewJob} FailureJobBuilder
190
+ */
191
+ /** @typedef {{ added: import('../new_job.js').NewJob } | { notAdded: unknown }} FailureJobResult */
192
+ /**
193
+ * @typedef {object} Store
194
+ * @property {() => Promise<ProbeRow[]>} probe
195
+ * @property {(limitsByName: Map<string, number>, keyedNames: string[]) => Promise<Candidate[]>} findCandidates
196
+ * @property {(params: ClaimParams) => Promise<ClaimedJob[]>} claim In a queue transaction of its own.
197
+ * @property {(ids: string[]) => Promise<Map<string, unknown>>} readPayloads
198
+ * @property {(jobs: OwnedJob[], leaseMs: number) => Promise<Set<string>>} renewLeases
199
+ * @property {(job: OwnedJob) => Promise<boolean>} markTimedOut
200
+ * @property {(jobs: FinishedJob[]) => Promise<number>} completeJobs In one transaction: the jobs that it completed.
201
+ * @property {(job: OwnedJob, params: { delayMs: number, error: import('../error_json.js').ErrorJson })
202
+ * => Promise<boolean>} retryJob
203
+ * @property {(
204
+ * job: OwnedJob,
205
+ * params: { error: import('../error_json.js').ErrorJson, buildFailureJob: FailureJobBuilder },
206
+ * ) => Promise<{ changed: boolean, failureJob: FailureJobResult | null }>} failJob
207
+ * @property {(job: OwnedJob, params: { delayMs: number, lingerMs: number }) => Promise<boolean>} releaseJob
208
+ * @property {(limit: number) => Promise<{ id: string, name: string }[]>} findStaleJobs
209
+ * @property {(id: string, params: { leaseMs: number, buildFailureJob: FailureJobBuilder })
210
+ * => Promise<{ result: 'released' | 'failed' | null, failureJob: FailureJobResult | null }>} recoverStaleJob
211
+ * @property {(limit: number) => Promise<{ lingers: number, orphans: number }>} sweepMutexes
212
+ * @property {(limit: number) => Promise<number>} deleteExpiredJobs
213
+ * @property {(names: string[]) => Promise<void>} makeDue For drain({ includeDelayed }).
214
+ * @property {() => Promise<import('../types.js').JobStats[]>} getStats
215
+ */
216
+ /**
217
+ * @param {import('../types.js').Adapter<any>} adapter
218
+ * @param {string} tablePrefix Checked.
219
+ * @returns {Store}
220
+ */
221
+ export declare function createStore(adapter: import('../types.js').Adapter<any>, tablePrefix: string): Store;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * A database error that a retry of the same work can pass: the transaction lost a lock race.
3
+ * @param {unknown} error
4
+ * @returns {boolean}
5
+ */
6
+ export declare function isTransientDbError(error: unknown): boolean;