velocious 1.0.606 → 1.0.607
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 +44 -1
- package/build/background-jobs/job-semantics.js +145 -0
- package/build/background-jobs/local-adapter.js +161 -0
- package/build/background-jobs/local-dispatcher.js +418 -0
- package/build/background-jobs/local-job-registry.js +59 -0
- package/build/background-jobs/local-store.js +1219 -0
- package/build/background-jobs/store.js +33 -97
- package/build/background-jobs/types.js +26 -1
- package/build/configuration-types.js +3 -1
- package/build/configuration.js +16 -1
- package/build/database/drivers/sqlite/index.js +46 -41
- package/build/environment-handlers/browser.js +10 -0
- package/build/src/background-jobs/job-semantics.d.ts +65 -0
- package/build/src/background-jobs/job-semantics.d.ts.map +1 -0
- package/build/src/background-jobs/job-semantics.js +125 -0
- package/build/src/background-jobs/local-adapter.d.ts +161 -0
- package/build/src/background-jobs/local-adapter.d.ts.map +1 -0
- package/build/src/background-jobs/local-adapter.js +141 -0
- package/build/src/background-jobs/local-dispatcher.d.ts +185 -0
- package/build/src/background-jobs/local-dispatcher.d.ts.map +1 -0
- package/build/src/background-jobs/local-dispatcher.js +383 -0
- package/build/src/background-jobs/local-job-registry.d.ts +26 -0
- package/build/src/background-jobs/local-job-registry.d.ts.map +1 -0
- package/build/src/background-jobs/local-job-registry.js +51 -0
- package/build/src/background-jobs/local-store.d.ts +364 -0
- package/build/src/background-jobs/local-store.d.ts.map +1 -0
- package/build/src/background-jobs/local-store.js +1078 -0
- package/build/src/background-jobs/store.d.ts +0 -9
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +18 -88
- package/build/src/background-jobs/types.d.ts +97 -2
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +27 -2
- package/build/src/configuration-types.d.ts +9 -2
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +4 -2
- package/build/src/configuration.d.ts +5 -0
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +15 -2
- package/build/src/database/drivers/sqlite/index.d.ts +8 -0
- package/build/src/database/drivers/sqlite/index.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/index.js +47 -41
- package/build/src/environment-handlers/browser.d.ts +9 -0
- package/build/src/environment-handlers/browser.d.ts.map +1 -1
- package/build/src/environment-handlers/browser.js +10 -1
- package/build/src/testing/test-runner.d.ts +42 -10
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +135 -35
- package/build/testing/test-runner.js +141 -33
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/scripts/test-browser.js +5 -1
- package/src/background-jobs/job-semantics.js +145 -0
- package/src/background-jobs/local-adapter.js +161 -0
- package/src/background-jobs/local-dispatcher.js +418 -0
- package/src/background-jobs/local-job-registry.js +59 -0
- package/src/background-jobs/local-store.js +1219 -0
- package/src/background-jobs/store.js +33 -97
- package/src/background-jobs/types.js +26 -1
- package/src/configuration-types.js +3 -1
- package/src/configuration.js +16 -1
- package/src/database/drivers/sqlite/index.js +46 -41
- package/src/environment-handlers/browser.js +10 -0
- package/src/testing/test-runner.js +141 -33
|
@@ -9,6 +9,19 @@ import BackgroundJobRecord from "./job-record.js"
|
|
|
9
9
|
import normalizeBackgroundJobError from "./normalize-error.js"
|
|
10
10
|
import {coordinateSharedTransactionConnection} from "../testing/shared-transaction-connection-coordinator.js"
|
|
11
11
|
import stableJsonStringify from "../utils/stable-json.js"
|
|
12
|
+
import {
|
|
13
|
+
BACKGROUND_JOB_EXECUTION_MODES,
|
|
14
|
+
DEFAULT_BACKGROUND_JOB_EXECUTION_MODE,
|
|
15
|
+
DEFAULT_BACKGROUND_JOB_QUEUE,
|
|
16
|
+
QUEUE_CONCURRENCY_KEY_PREFIX,
|
|
17
|
+
normalizeBackgroundJobConcurrency,
|
|
18
|
+
normalizeBackgroundJobExecutionMode,
|
|
19
|
+
normalizeBackgroundJobMaxRetries,
|
|
20
|
+
normalizeBackgroundJobQueue,
|
|
21
|
+
normalizeBackgroundJobScheduledAtMs,
|
|
22
|
+
rescheduledBackgroundJobAtMs,
|
|
23
|
+
retryDelayMs
|
|
24
|
+
} from "./job-semantics.js"
|
|
12
25
|
import {
|
|
13
26
|
MAIL_DELIVERY_OPERATIONS_TABLE,
|
|
14
27
|
mailDeliveryOperationForJob,
|
|
@@ -52,22 +65,7 @@ const COUNTS_REVISION_KEY = "counts"
|
|
|
52
65
|
export const BACKGROUND_JOB_COUNTS_CHANNEL = "velocious-background-job-counts"
|
|
53
66
|
export const BACKGROUND_JOB_COUNT_BUCKETS = ["all", "queued", "handed_off", "completed", "failed", "orphaned"]
|
|
54
67
|
const COUNTED_JOB_STATUSES = BACKGROUND_JOB_COUNT_BUCKETS.slice(1)
|
|
55
|
-
const DEFAULT_MAX_RETRIES = 10
|
|
56
68
|
const ORPHANED_AFTER_MS = 2 * 60 * 60 * 1000
|
|
57
|
-
/**
|
|
58
|
-
* Execution modes.
|
|
59
|
-
* @type {import("./types.js").BackgroundJobExecutionMode[]} */
|
|
60
|
-
const EXECUTION_MODES = ["inline", "forked", "pooled", "spawned"]
|
|
61
|
-
/**
|
|
62
|
-
* Execution mode for a new enqueue that names neither `executionMode` nor the
|
|
63
|
-
* legacy `forked` flag. Pooled routes the job to a warm, reused local runner
|
|
64
|
-
* process — the same isolation as forked without paying a fresh process per job.
|
|
65
|
-
* @type {import("./types.js").BackgroundJobExecutionMode} */
|
|
66
|
-
const DEFAULT_EXECUTION_MODE = "pooled"
|
|
67
|
-
const DEFAULT_QUEUE = "default"
|
|
68
|
-
// Queue-derived durable concurrency keys are namespaced so they can't collide
|
|
69
|
-
// with explicit caller-supplied concurrencyKeys.
|
|
70
|
-
const QUEUE_CONCURRENCY_KEY_PREFIX = "queue:"
|
|
71
69
|
|
|
72
70
|
/**
|
|
73
71
|
* Columns the dashboard is allowed to sort job listings by, mapped to their
|
|
@@ -783,7 +781,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
783
781
|
.map(([queue, priority]) => `WHEN ${db.quote(queue)} THEN ${priority}`)
|
|
784
782
|
.join(" ")
|
|
785
783
|
|
|
786
|
-
return `CASE COALESCE(${queueColumn}, ${db.quote(
|
|
784
|
+
return `CASE COALESCE(${queueColumn}, ${db.quote(DEFAULT_BACKGROUND_JOB_QUEUE)}) ${whens} ELSE 0 END`
|
|
787
785
|
}
|
|
788
786
|
|
|
789
787
|
/**
|
|
@@ -1302,13 +1300,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1302
1300
|
* @returns {number} - Delay in milliseconds.
|
|
1303
1301
|
*/
|
|
1304
1302
|
getRetryDelayMs(retryCount) {
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
if (retryCount <= scheduleSeconds.length) {
|
|
1308
|
-
return scheduleSeconds[retryCount - 1] * 1000
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
return (retryCount - 3) * 60 * 60 * 1000
|
|
1303
|
+
return retryDelayMs(retryCount)
|
|
1312
1304
|
}
|
|
1313
1305
|
|
|
1314
1306
|
/**
|
|
@@ -1382,11 +1374,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1382
1374
|
* @returns {number} - Normalized max retries.
|
|
1383
1375
|
*/
|
|
1384
1376
|
_normalizeMaxRetries(maxRetries) {
|
|
1385
|
-
|
|
1386
|
-
return Math.floor(maxRetries)
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
return DEFAULT_MAX_RETRIES
|
|
1377
|
+
return normalizeBackgroundJobMaxRetries(maxRetries)
|
|
1390
1378
|
}
|
|
1391
1379
|
|
|
1392
1380
|
/**
|
|
@@ -1396,10 +1384,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1396
1384
|
* @returns {number} - Dispatch timestamp.
|
|
1397
1385
|
*/
|
|
1398
1386
|
_normalizeScheduledAtMs(scheduledAtMs, defaultScheduledAtMs) {
|
|
1399
|
-
|
|
1400
|
-
if (Number.isSafeInteger(scheduledAtMs) && scheduledAtMs >= 0) return scheduledAtMs
|
|
1401
|
-
|
|
1402
|
-
throw VelociousError.safe("background job scheduledAtMs must be a non-negative safe integer")
|
|
1387
|
+
return normalizeBackgroundJobScheduledAtMs(scheduledAtMs, defaultScheduledAtMs)
|
|
1403
1388
|
}
|
|
1404
1389
|
|
|
1405
1390
|
/**
|
|
@@ -1408,14 +1393,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1408
1393
|
* @returns {number} - Future eligibility timestamp.
|
|
1409
1394
|
*/
|
|
1410
1395
|
_rescheduledAtMs(delayMs) {
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
const scheduledAtMs = Date.now() + delayMs
|
|
1414
|
-
if (!Number.isSafeInteger(scheduledAtMs)) {
|
|
1415
|
-
throw VelociousError.safe("background job reschedule scheduledAtMs must be a safe integer")
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
return scheduledAtMs
|
|
1396
|
+
return rescheduledBackgroundJobAtMs(delayMs, Date.now())
|
|
1419
1397
|
}
|
|
1420
1398
|
|
|
1421
1399
|
/**
|
|
@@ -1424,9 +1402,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1424
1402
|
* @returns {void}
|
|
1425
1403
|
*/
|
|
1426
1404
|
_validateRescheduleDelayMs(delayMs) {
|
|
1427
|
-
|
|
1428
|
-
throw VelociousError.safe("background job reschedule delayMs must be a non-negative safe integer")
|
|
1429
|
-
}
|
|
1405
|
+
rescheduledBackgroundJobAtMs(delayMs, 0)
|
|
1430
1406
|
}
|
|
1431
1407
|
|
|
1432
1408
|
/**
|
|
@@ -2083,14 +2059,14 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2083
2059
|
// `execution_mode` is the single source of truth for a job's runtime and is
|
|
2084
2060
|
// written on every enqueue; the drop-forked migration backfills any pre-existing
|
|
2085
2061
|
// rows before the legacy `forked` column is removed.
|
|
2086
|
-
const executionMode = row.execution_mode ? this._normalizeExecutionModeName(String(row.execution_mode)) :
|
|
2062
|
+
const executionMode = row.execution_mode ? this._normalizeExecutionModeName(String(row.execution_mode)) : DEFAULT_BACKGROUND_JOB_EXECUTION_MODE
|
|
2087
2063
|
|
|
2088
2064
|
return {
|
|
2089
2065
|
id: String(row.id),
|
|
2090
2066
|
jobName: String(row.job_name),
|
|
2091
2067
|
args: this._parseArgs(row.args_json),
|
|
2092
2068
|
executionMode,
|
|
2093
|
-
queue: row.queue ? String(row.queue) :
|
|
2069
|
+
queue: row.queue ? String(row.queue) : DEFAULT_BACKGROUND_JOB_QUEUE,
|
|
2094
2070
|
scheduleKey: row.schedule_key ? String(row.schedule_key) : null,
|
|
2095
2071
|
status: row.status ? String(row.status) : "queued",
|
|
2096
2072
|
attempts: this._normalizeNumber(row.attempts),
|
|
@@ -2109,35 +2085,13 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2109
2085
|
}
|
|
2110
2086
|
}
|
|
2111
2087
|
|
|
2112
|
-
/**
|
|
2113
|
-
* Validates concurrency options.
|
|
2114
|
-
* @param {import("./types.js").BackgroundJobOptions | undefined} options - Job options.
|
|
2115
|
-
* @returns {{concurrencyKey: string, maxConcurrency: number} | null} - Normalized configuration.
|
|
2116
|
-
*/
|
|
2117
|
-
_normalizeConcurrencyOptions(options) {
|
|
2118
|
-
const key = options?.concurrencyKey
|
|
2119
|
-
const cap = options?.maxConcurrency
|
|
2120
|
-
if (key === undefined && cap === undefined) return null
|
|
2121
|
-
if (typeof key !== "string" || key.length === 0 || !Number.isInteger(cap) || Number(cap) <= 0) {
|
|
2122
|
-
throw new Error("background job concurrencyKey and maxConcurrency must be paired; concurrencyKey must be non-empty and maxConcurrency must be a positive integer")
|
|
2123
|
-
}
|
|
2124
|
-
if (key.startsWith(QUEUE_CONCURRENCY_KEY_PREFIX)) {
|
|
2125
|
-
throw new Error(`background job concurrencyKey must not start with the reserved "${QUEUE_CONCURRENCY_KEY_PREFIX}" prefix, which is reserved for queue-derived concurrency caps`)
|
|
2126
|
-
}
|
|
2127
|
-
return {concurrencyKey: key, maxConcurrency: Number(cap)}
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
2088
|
/**
|
|
2131
2089
|
* Normalizes a job's queue name, defaulting to "default".
|
|
2132
2090
|
* @param {import("./types.js").BackgroundJobOptions | undefined} options - Job options.
|
|
2133
2091
|
* @returns {string} - Queue name.
|
|
2134
2092
|
*/
|
|
2135
2093
|
_normalizeQueue(options) {
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
if (typeof queue === "string" && queue.trim().length > 0) return queue.trim()
|
|
2139
|
-
|
|
2140
|
-
return DEFAULT_QUEUE
|
|
2094
|
+
return normalizeBackgroundJobQueue(options)
|
|
2141
2095
|
}
|
|
2142
2096
|
|
|
2143
2097
|
/**
|
|
@@ -2151,15 +2105,11 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2151
2105
|
* @returns {{concurrencyKey: string, maxConcurrency: number, queueDerived: boolean} | null} - Resolved concurrency.
|
|
2152
2106
|
*/
|
|
2153
2107
|
_resolveConcurrency(options, queue) {
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
if (cap === null) return null
|
|
2161
|
-
|
|
2162
|
-
return {concurrencyKey: `${QUEUE_CONCURRENCY_KEY_PREFIX}${queue}`, maxConcurrency: cap, queueDerived: true}
|
|
2108
|
+
return normalizeBackgroundJobConcurrency({
|
|
2109
|
+
options: options || {},
|
|
2110
|
+
queue,
|
|
2111
|
+
queues: this.configuration.getBackgroundJobsConfig().queues
|
|
2112
|
+
})
|
|
2163
2113
|
}
|
|
2164
2114
|
|
|
2165
2115
|
/**
|
|
@@ -2667,21 +2617,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2667
2617
|
* @returns {import("./types.js").BackgroundJobExecutionMode} - Normalized execution mode.
|
|
2668
2618
|
*/
|
|
2669
2619
|
_normalizeExecutionMode(options) {
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
if (executionMode) {
|
|
2673
|
-
return this._normalizeExecutionModeName(executionMode)
|
|
2674
|
-
}
|
|
2675
|
-
|
|
2676
|
-
// The `forked` option alias was removed. Reject it loudly instead of silently
|
|
2677
|
-
// defaulting to pooled, which would turn an explicitly inline (`forked: false`)
|
|
2678
|
-
// or one-shot forked (`forked: true`) job into a pooled child-runner job — a
|
|
2679
|
-
// silent semantic change for any not-yet-migrated caller.
|
|
2680
|
-
if (options && "forked" in options) {
|
|
2681
|
-
throw new Error("The background job `forked` option was removed; pass `executionMode` (\"inline\", \"forked\", \"pooled\", or \"spawned\") instead")
|
|
2682
|
-
}
|
|
2683
|
-
|
|
2684
|
-
return DEFAULT_EXECUTION_MODE
|
|
2620
|
+
return normalizeBackgroundJobExecutionMode(options || {}, DEFAULT_BACKGROUND_JOB_EXECUTION_MODE)
|
|
2685
2621
|
}
|
|
2686
2622
|
|
|
2687
2623
|
/**
|
|
@@ -2690,11 +2626,11 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2690
2626
|
* @returns {import("./types.js").BackgroundJobExecutionMode} - Normalized execution mode.
|
|
2691
2627
|
*/
|
|
2692
2628
|
_normalizeExecutionModeName(executionMode) {
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2629
|
+
return normalizeBackgroundJobExecutionMode(
|
|
2630
|
+
{executionMode: /** @type {import("./types.js").BackgroundJobExecutionMode} */ (executionMode)},
|
|
2631
|
+
DEFAULT_BACKGROUND_JOB_EXECUTION_MODE,
|
|
2632
|
+
BACKGROUND_JOB_EXECUTION_MODES
|
|
2633
|
+
)
|
|
2698
2634
|
}
|
|
2699
2635
|
|
|
2700
2636
|
/**
|
|
@@ -3,6 +3,31 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {"inline" | "forked" | "pooled" | "spawned"} BackgroundJobExecutionMode
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {object} LocalBackgroundJobsClock
|
|
8
|
+
* @property {() => number} now - Current epoch milliseconds.
|
|
9
|
+
* @property {(callback: () => void, delayMs: number) => ReturnType<typeof setTimeout> | number} setTimeout - Arms a timer.
|
|
10
|
+
* @property {(timerId: ReturnType<typeof setTimeout> | number) => void} clearTimeout - Clears a timer.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} ResolvedBackgroundJobConcurrency
|
|
14
|
+
* @property {string} concurrencyKey - Durable cap identity.
|
|
15
|
+
* @property {number} maxConcurrency - Positive cap.
|
|
16
|
+
* @property {boolean} queueDerived - Whether queue configuration owns the cap.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} PreparedLocalBackgroundJob
|
|
20
|
+
* @property {string} argsDigest - Fixed-width digest of the serialized arguments.
|
|
21
|
+
* @property {string} argsJson - Serialized arguments.
|
|
22
|
+
* @property {ResolvedBackgroundJobConcurrency | null} concurrency - Resolved concurrency.
|
|
23
|
+
* @property {number} createdAtMs - Creation timestamp.
|
|
24
|
+
* @property {"inline"} executionMode - Local in-process execution mode.
|
|
25
|
+
* @property {string} jobId - Durable id.
|
|
26
|
+
* @property {string} jobName - Registered name.
|
|
27
|
+
* @property {number} maxRetries - Retry cap.
|
|
28
|
+
* @property {string} queue - Queue name.
|
|
29
|
+
* @property {number} scheduledAtMs - Eligibility timestamp.
|
|
30
|
+
*/
|
|
6
31
|
/**
|
|
7
32
|
* @typedef {object} BackgroundJobsHealth
|
|
8
33
|
* @property {boolean} ready - Whether the adapter can accept and process work.
|
|
@@ -20,7 +45,7 @@
|
|
|
20
45
|
*/
|
|
21
46
|
/**
|
|
22
47
|
* @typedef {object} BackgroundJobOptions
|
|
23
|
-
* @property {BackgroundJobExecutionMode} [executionMode] - How the job should run.
|
|
48
|
+
* @property {BackgroundJobExecutionMode} [executionMode] - How the job should run. Node defaults to `"pooled"` (a warm, reused local runner process). Browser/Expo local dispatch defaults to and only accepts `"inline"`. `"forked"` runs a Node job in a fresh `child_process.fork()` child, and `"spawned"` in a detached CLI runner.
|
|
24
49
|
* @property {number} [maxRetries] - Max retries for a failed job before it is marked failed.
|
|
25
50
|
* @property {string} [queue] - Queue name. Defaults to `"default"`. When the queue has a configured cap in `backgroundJobs.queues`, that cap is enforced cluster-wide.
|
|
26
51
|
* @property {string} [concurrencyKey] - Opaque non-empty key used to share a concurrency cap. Overrides any queue-derived cap.
|
|
@@ -158,14 +158,16 @@
|
|
|
158
158
|
|
|
159
159
|
/** @typedef {"background" | "inline"} BackgroundJobsMode */
|
|
160
160
|
/** @typedef {(args: {configuration: import("./configuration.js").default}) => import("./background-jobs/adapter.js").default} BackgroundJobsAdapterFactory */
|
|
161
|
+
/** @typedef {typeof import("./background-jobs/platform-job.js").default} BackgroundJobClass */
|
|
161
162
|
|
|
162
163
|
/**
|
|
163
164
|
* @typedef {object} BackgroundJobsConfiguration
|
|
164
165
|
* @property {import("./background-jobs/adapter.js").default | BackgroundJobsAdapterFactory} [adapter] - Adapter instance or synchronous factory. A factory creates one adapter per configuration lifecycle; the framework closes adapters it resolves.
|
|
166
|
+
* @property {BackgroundJobClass[]} [jobClasses] - Static portable job classes available to Browser/Expo local dispatch. Defaults to `[]`; Node keeps its filesystem registry.
|
|
165
167
|
* @property {BackgroundJobsMode} [mode] - `"background"` uses the configured adapter/transport and durable queue semantics; `"inline"` performs immediately without durable queue state. Defaults to `"background"`.
|
|
166
168
|
* @property {string} [host] - Hostname for the background jobs main process.
|
|
167
169
|
* @property {number} [port] - Port for the background jobs main process.
|
|
168
|
-
* @property {string} [databaseIdentifier] - Database identifier used to store background jobs.
|
|
170
|
+
* @property {string} [databaseIdentifier] - Database identifier used to store background jobs. Browser/Expo local dispatch uses this existing SQLite database and defaults to `"default"`.
|
|
169
171
|
* @property {number} [maxConcurrentInlineJobs] - How many `forked: false` jobs a single
|
|
170
172
|
* `background-jobs-worker` process is allowed to run in parallel. Concurrency
|
|
171
173
|
* is at the JS event-loop level: every concurrent job shares the worker's
|
package/build/configuration.js
CHANGED
|
@@ -1543,7 +1543,22 @@ export default class VelociousConfiguration {
|
|
|
1543
1543
|
: 60 * 60 * 1000
|
|
1544
1544
|
}
|
|
1545
1545
|
|
|
1546
|
-
|
|
1546
|
+
const jobClasses = this.getBackgroundJobClasses()
|
|
1547
|
+
|
|
1548
|
+
return {host, port, databaseIdentifier, maxConcurrentForkedJobs, maxConcurrentInlineJobs, mode, pooledRunnerCount, pooledRunnerConcurrency, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, dispatchStrategy, pollIntervalMs, queues, jobClasses, jobTimeoutMs, retention}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/**
|
|
1552
|
+
* Returns statically registered portable background jobs.
|
|
1553
|
+
* @returns {import("./configuration-types.js").BackgroundJobClass[]} - Configured job classes.
|
|
1554
|
+
*/
|
|
1555
|
+
getBackgroundJobClasses() {
|
|
1556
|
+
const jobClasses = this._backgroundJobs?.jobClasses
|
|
1557
|
+
|
|
1558
|
+
if (jobClasses === undefined) return []
|
|
1559
|
+
if (!Array.isArray(jobClasses)) throw new TypeError("backgroundJobs.jobClasses must be an array")
|
|
1560
|
+
|
|
1561
|
+
return [...jobClasses]
|
|
1547
1562
|
}
|
|
1548
1563
|
|
|
1549
1564
|
/**
|
|
@@ -255,6 +255,35 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
|
|
|
255
255
|
await fs.writeFile(ownerPath, payload)
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Publishes a fully initialized lock directory with one atomic rename.
|
|
260
|
+
* A losing candidate is removed in the same call, so concurrent acquisition
|
|
261
|
+
* cannot observe or delete another process's half-written owner metadata.
|
|
262
|
+
* @param {string} lockPath - Stable advisory-lock path.
|
|
263
|
+
* @returns {Promise<boolean>} - Whether this candidate became the lock owner.
|
|
264
|
+
*/
|
|
265
|
+
async _publishAdvisoryLockDirectory(lockPath) {
|
|
266
|
+
const candidatePath = await fs.mkdtemp(`${lockPath}.pending-`)
|
|
267
|
+
let published = false
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
await this._writeAdvisoryLockMetadata(candidatePath)
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
await fs.rename(candidatePath, lockPath)
|
|
274
|
+
published = true
|
|
275
|
+
return true
|
|
276
|
+
} catch (error) {
|
|
277
|
+
const code = /** @type {Error & {code?: string}} */ (error)?.code
|
|
278
|
+
|
|
279
|
+
if (code === "EEXIST" || code === "ENOTEMPTY") return false
|
|
280
|
+
throw error
|
|
281
|
+
}
|
|
282
|
+
} finally {
|
|
283
|
+
if (!published) await fs.rm(candidatePath, {force: true, recursive: true})
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
258
287
|
/**
|
|
259
288
|
* Runs acquire advisory lock file.
|
|
260
289
|
* @param {string} name - Lock name.
|
|
@@ -272,28 +301,21 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
|
|
|
272
301
|
// mkdir succeeds, the deadline elapses, or an unexpected error is
|
|
273
302
|
// re-thrown.
|
|
274
303
|
while (true) {
|
|
275
|
-
|
|
276
|
-
await fs.mkdir(lockPath)
|
|
277
|
-
await this._writeAdvisoryLockMetadata(lockPath)
|
|
304
|
+
if (await this._publishAdvisoryLockDirectory(lockPath)) return true
|
|
278
305
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (await this._isAdvisoryLockStale(lockPath)) {
|
|
284
|
-
await fs.rm(lockPath, {recursive: true, force: true})
|
|
285
|
-
continue
|
|
286
|
-
}
|
|
306
|
+
if (await this._isAdvisoryLockStale(lockPath)) {
|
|
307
|
+
await fs.rm(lockPath, {recursive: true, force: true})
|
|
308
|
+
continue
|
|
309
|
+
}
|
|
287
310
|
|
|
288
|
-
|
|
289
|
-
|
|
311
|
+
if (deadline !== null) {
|
|
312
|
+
const remaining = deadline - Date.now()
|
|
290
313
|
|
|
291
|
-
|
|
314
|
+
if (remaining <= 0) return false
|
|
292
315
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
316
|
+
await wait(Math.min(pollIntervalMs, remaining))
|
|
317
|
+
} else {
|
|
318
|
+
await wait(pollIntervalMs)
|
|
297
319
|
}
|
|
298
320
|
}
|
|
299
321
|
}
|
|
@@ -308,31 +330,14 @@ export default class VelociousDatabaseDriversSqliteNode extends Base {
|
|
|
308
330
|
|
|
309
331
|
const lockPath = this._advisoryLockPath(name)
|
|
310
332
|
|
|
311
|
-
|
|
312
|
-
await fs.mkdir(lockPath)
|
|
313
|
-
await this._writeAdvisoryLockMetadata(lockPath)
|
|
333
|
+
if (await this._publishAdvisoryLockDirectory(lockPath)) return true
|
|
314
334
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (await this._isAdvisoryLockStale(lockPath)) {
|
|
320
|
-
await fs.rm(lockPath, {recursive: true, force: true})
|
|
321
|
-
|
|
322
|
-
try {
|
|
323
|
-
await fs.mkdir(lockPath)
|
|
324
|
-
await this._writeAdvisoryLockMetadata(lockPath)
|
|
325
|
-
|
|
326
|
-
return true
|
|
327
|
-
} catch (retryError) {
|
|
328
|
-
if (/** @type {Error & {code?: string}} */ (retryError)?.code === "EEXIST") return false
|
|
329
|
-
|
|
330
|
-
throw retryError
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return false
|
|
335
|
+
if (await this._isAdvisoryLockStale(lockPath)) {
|
|
336
|
+
await fs.rm(lockPath, {recursive: true, force: true})
|
|
337
|
+
return await this._publishAdvisoryLockDirectory(lockPath)
|
|
335
338
|
}
|
|
339
|
+
|
|
340
|
+
return false
|
|
336
341
|
}
|
|
337
342
|
|
|
338
343
|
/**
|
|
@@ -2,6 +2,7 @@ import Base from "./base.js"
|
|
|
2
2
|
import * as inflection from "inflection"
|
|
3
3
|
import restArgsError from "../utils/rest-args-error.js"
|
|
4
4
|
import Logger from "../logger.js"
|
|
5
|
+
import LocalBackgroundJobsAdapter from "../background-jobs/local-adapter.js"
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Defines this typedef.
|
|
@@ -40,6 +41,15 @@ function isMigrationObject(migration) {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export default class VelociousEnvironmentsHandlerBrowser extends Base {
|
|
44
|
+
/**
|
|
45
|
+
* Creates the Browser/Expo local SQLite adapter and in-process dispatcher.
|
|
46
|
+
* @param {{configuration: import("../configuration.js").default}} args - Adapter options.
|
|
47
|
+
* @returns {LocalBackgroundJobsAdapter} - Local background-jobs adapter.
|
|
48
|
+
*/
|
|
49
|
+
createBackgroundJobsAdapter({configuration}) {
|
|
50
|
+
return new LocalBackgroundJobsAdapter({configuration})
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
/**
|
|
44
54
|
* Find commands require context result.
|
|
45
55
|
* @type {CommandsRequireContextType | undefined} */
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export declare const DEFAULT_BACKGROUND_JOB_EXECUTION_MODE = "pooled";
|
|
2
|
+
export declare const DEFAULT_BACKGROUND_JOB_MAX_RETRIES = 10;
|
|
3
|
+
export declare const DEFAULT_BACKGROUND_JOB_QUEUE = "default";
|
|
4
|
+
export declare const QUEUE_CONCURRENCY_KEY_PREFIX = "queue:";
|
|
5
|
+
/** @type {import("./types.js").BackgroundJobExecutionMode[]} */
|
|
6
|
+
export declare const BACKGROUND_JOB_EXECUTION_MODES: import("./types.js").BackgroundJobExecutionMode[];
|
|
7
|
+
/**
|
|
8
|
+
* Normalizes a job queue.
|
|
9
|
+
* @param {import("./types.js").BackgroundJobOptions} [options] - Job options.
|
|
10
|
+
* @returns {string} - Queue name.
|
|
11
|
+
*/
|
|
12
|
+
export declare function normalizeBackgroundJobQueue(options?: import("./types.js").BackgroundJobOptions): string;
|
|
13
|
+
/**
|
|
14
|
+
* Normalizes an explicit execution mode while retaining the Node default.
|
|
15
|
+
* @param {Omit<import("./types.js").BackgroundJobOptions, "executionMode"> & {executionMode?: string}} options - Job options.
|
|
16
|
+
* @param {import("./types.js").BackgroundJobExecutionMode} [defaultExecutionMode] - Default mode.
|
|
17
|
+
* @param {import("./types.js").BackgroundJobExecutionMode[]} [supportedExecutionModes] - Modes accepted by the caller.
|
|
18
|
+
* @returns {import("./types.js").BackgroundJobExecutionMode} - Execution mode.
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizeBackgroundJobExecutionMode(options?: Omit<import("./types.js").BackgroundJobOptions, "executionMode"> & {
|
|
21
|
+
executionMode?: string;
|
|
22
|
+
}, defaultExecutionMode?: import("./types.js").BackgroundJobExecutionMode, supportedExecutionModes?: import("./types.js").BackgroundJobExecutionMode[]): import("./types.js").BackgroundJobExecutionMode;
|
|
23
|
+
/**
|
|
24
|
+
* Validates and normalizes a retry cap.
|
|
25
|
+
* @param {number | null | undefined} maxRetries - Requested retry cap.
|
|
26
|
+
* @returns {number} - Retry cap.
|
|
27
|
+
*/
|
|
28
|
+
export declare function normalizeBackgroundJobMaxRetries(maxRetries: number | null | undefined): number;
|
|
29
|
+
/**
|
|
30
|
+
* Validates an enqueue eligibility timestamp.
|
|
31
|
+
* @param {number | undefined} scheduledAtMs - Requested timestamp.
|
|
32
|
+
* @param {number} defaultScheduledAtMs - Default timestamp.
|
|
33
|
+
* @returns {number} - Eligibility timestamp.
|
|
34
|
+
*/
|
|
35
|
+
export declare function normalizeBackgroundJobScheduledAtMs(scheduledAtMs: number | undefined, defaultScheduledAtMs: number): number;
|
|
36
|
+
/**
|
|
37
|
+
* Validates a reschedule delay and resolves it at persistence time.
|
|
38
|
+
* @param {number} delayMs - Requested delay.
|
|
39
|
+
* @param {number} nowMs - Persistence timestamp.
|
|
40
|
+
* @returns {number} - New eligibility timestamp.
|
|
41
|
+
*/
|
|
42
|
+
export declare function rescheduledBackgroundJobAtMs(delayMs: number, nowMs: number): number;
|
|
43
|
+
/**
|
|
44
|
+
* Returns the shared failure backoff.
|
|
45
|
+
* @param {number} retryCount - One-based failed attempt count.
|
|
46
|
+
* @returns {number} - Backoff in milliseconds.
|
|
47
|
+
*/
|
|
48
|
+
export declare function retryDelayMs(retryCount: number): number;
|
|
49
|
+
/**
|
|
50
|
+
* Resolves explicit or queue-derived concurrency.
|
|
51
|
+
* @param {object} args - Resolution arguments.
|
|
52
|
+
* @param {import("./types.js").BackgroundJobOptions} args.options - Job options.
|
|
53
|
+
* @param {string} args.queue - Normalized queue.
|
|
54
|
+
* @param {Record<string, {maxConcurrent?: number, priority?: number}>} args.queues - Queue configuration.
|
|
55
|
+
* @returns {import("./types.js").ResolvedBackgroundJobConcurrency | null} - Concurrency contract.
|
|
56
|
+
*/
|
|
57
|
+
export declare function normalizeBackgroundJobConcurrency({ options, queue, queues }: {
|
|
58
|
+
options: import("./types.js").BackgroundJobOptions;
|
|
59
|
+
queue: string;
|
|
60
|
+
queues: Record<string, {
|
|
61
|
+
maxConcurrent?: number;
|
|
62
|
+
priority?: number;
|
|
63
|
+
}>;
|
|
64
|
+
}): import("./types.js").ResolvedBackgroundJobConcurrency | null;
|
|
65
|
+
//# sourceMappingURL=job-semantics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-semantics.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/job-semantics.js"],"names":[],"mappings":"AAIA,eAAO,MAAM,qCAAqC,WAAW,CAAA;AAC7D,eAAO,MAAM,kCAAkC,KAAK,CAAA;AACpD,eAAO,MAAM,4BAA4B,YAAY,CAAA;AACrD,eAAO,MAAM,4BAA4B,WAAW,CAAA;AACpD,gEAAgE;AAChE,eAAO,MAAM,8BAA8B,EADhC,OAAO,YAAY,EAAE,0BAA0B,EAC6B,CAAA;AAEvF;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,AAHhD,CACA,EADQ,OAAO,YAAY,EAAE,oBAGmB,GAFtC,MAAM,CAQlB;AAED;;;;;;GAMG;AACH,wBAAgB,mCAAmC,CAAC,OAAO,GALhD,IAAI,CAAC,OAAO,YAAY,EAAE,oBAAoB,EAAE,eAAe,CAAC,GAAG;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAKrC,EAAE,oBAAoB,AAJnF,CACA,EADQ,OAAO,YAAY,EAAE,0BAI8F,EAAE,uBAAuB,AAHpJ,CACA,EADQ,OAAO,YAAY,EAAE,0BAA0B,EAG8H,GAF3K,OAAO,YAAY,EAAE,0BAA0B,CAsB3D;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAAC,UAAU,EAHhD,MAAM,GAAG,IAAI,GAAG,SAGgC,GAF9C,MAAM,CAQlB;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,aAAa,EAJtD,MAAM,GAAG,SAI6C,EAAE,oBAAoB,EAH5E,MAG4E,GAF1E,MAAM,CAOlB;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAJzC,MAIyC,EAAE,KAAK,EAHhD,MAGgD,GAF9C,MAAM,CAclB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAH5B,MAG4B,GAF1B,MAAM,CAQlB;AAED;;;;;;;GAOG;AACH,wBAAgB,iCAAiC,CAAC,EAAC,OAAY,EAAE,KAAK,EAAE,MAAM,EAAC,EAL5E;IAAwD,OAAO,EAAvD,OAAO,YAAY,EAAE,oBAAoB,CACjD;IAAqB,KAAK,EAAlB,MAAM,CACd;IAA0E,MAAM,EAAxE,MAAM,CAAC,MAAM,EAAE;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CACnE;CAE4E,GAFlE,OAAO,YAAY,EAAE,gCAAgC,GAAG,IAAI,CA0BxE"}
|