velocious 1.0.606 → 1.0.608
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 +45 -2
- 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 +1220 -0
- package/build/background-jobs/main.js +2 -1
- package/build/background-jobs/store.js +97 -100
- package/build/background-jobs/types.js +28 -1
- package/build/background-jobs/worker.js +19 -14
- 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 +1079 -0
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +3 -2
- package/build/src/background-jobs/store.d.ts +17 -9
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +75 -91
- package/build/src/background-jobs/types.d.ts +107 -2
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +29 -2
- package/build/src/background-jobs/worker.d.ts +15 -7
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +20 -15
- 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 +1220 -0
- package/src/background-jobs/main.js +2 -1
- package/src/background-jobs/store.js +97 -100
- package/src/background-jobs/types.js +28 -1
- package/src/background-jobs/worker.js +19 -14
- 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
|
@@ -1354,7 +1354,8 @@ export default class BackgroundJobsMain {
|
|
|
1354
1354
|
workerId: worker.workerId,
|
|
1355
1355
|
handedOffAtMs: handoff.handedOffAtMs,
|
|
1356
1356
|
options: {
|
|
1357
|
-
executionMode: job.executionMode
|
|
1357
|
+
executionMode: job.executionMode,
|
|
1358
|
+
...(job.timeoutMs === null ? {} : {timeoutMs: job.timeoutMs})
|
|
1358
1359
|
}
|
|
1359
1360
|
}
|
|
1360
1361
|
})
|
|
@@ -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,
|
|
@@ -27,6 +40,7 @@ import {
|
|
|
27
40
|
* @property {number} maxRetries - Retry cap.
|
|
28
41
|
* @property {string} queue - Queue name.
|
|
29
42
|
* @property {number} scheduledAtMs - Eligibility timestamp.
|
|
43
|
+
* @property {number | null} timeoutMs - Per-job timeout override, or null when omitted.
|
|
30
44
|
*/
|
|
31
45
|
|
|
32
46
|
const MIGRATIONS_TABLE = "velocious_internal_migrations"
|
|
@@ -52,22 +66,9 @@ const COUNTS_REVISION_KEY = "counts"
|
|
|
52
66
|
export const BACKGROUND_JOB_COUNTS_CHANNEL = "velocious-background-job-counts"
|
|
53
67
|
export const BACKGROUND_JOB_COUNT_BUCKETS = ["all", "queued", "handed_off", "completed", "failed", "orphaned"]
|
|
54
68
|
const COUNTED_JOB_STATUSES = BACKGROUND_JOB_COUNT_BUCKETS.slice(1)
|
|
55
|
-
const
|
|
69
|
+
const MAX_JOB_TIMEOUT_MS = 2_147_483_647
|
|
70
|
+
const JOB_TIMEOUT_VALIDATION_MESSAGE = `background job timeoutMs must be a finite non-positive number or an integer between 1 and ${MAX_JOB_TIMEOUT_MS}`
|
|
56
71
|
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
72
|
|
|
72
73
|
/**
|
|
73
74
|
* Columns the dashboard is allowed to sort job listings by, mapped to their
|
|
@@ -498,7 +499,8 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
498
499
|
maxRetries: preparedJob.maxRetries,
|
|
499
500
|
queue: preparedJob.queue,
|
|
500
501
|
scheduledAtMs: options.scheduledAtMs === undefined ? null : preparedJob.scheduledAtMs,
|
|
501
|
-
scheduling: options.scheduledAtMs === undefined ? "immediate" : "scheduled"
|
|
502
|
+
scheduling: options.scheduledAtMs === undefined ? "immediate" : "scheduled",
|
|
503
|
+
...(preparedJob.timeoutMs === null ? {} : {timeoutMs: preparedJob.timeoutMs})
|
|
502
504
|
})
|
|
503
505
|
|
|
504
506
|
return createHash("sha256").update(serialized).digest("hex")
|
|
@@ -783,7 +785,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
783
785
|
.map(([queue, priority]) => `WHEN ${db.quote(queue)} THEN ${priority}`)
|
|
784
786
|
.join(" ")
|
|
785
787
|
|
|
786
|
-
return `CASE COALESCE(${queueColumn}, ${db.quote(
|
|
788
|
+
return `CASE COALESCE(${queueColumn}, ${db.quote(DEFAULT_BACKGROUND_JOB_QUEUE)}) ${whens} ELSE 0 END`
|
|
787
789
|
}
|
|
788
790
|
|
|
789
791
|
/**
|
|
@@ -1302,13 +1304,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1302
1304
|
* @returns {number} - Delay in milliseconds.
|
|
1303
1305
|
*/
|
|
1304
1306
|
getRetryDelayMs(retryCount) {
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
if (retryCount <= scheduleSeconds.length) {
|
|
1308
|
-
return scheduleSeconds[retryCount - 1] * 1000
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
return (retryCount - 3) * 60 * 60 * 1000
|
|
1307
|
+
return retryDelayMs(retryCount)
|
|
1312
1308
|
}
|
|
1313
1309
|
|
|
1314
1310
|
/**
|
|
@@ -1332,8 +1328,33 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1332
1328
|
jobName,
|
|
1333
1329
|
maxRetries: this._normalizeMaxRetries(options?.maxRetries),
|
|
1334
1330
|
queue,
|
|
1335
|
-
scheduledAtMs: this._normalizeScheduledAtMs(options?.scheduledAtMs, createdAtMs)
|
|
1331
|
+
scheduledAtMs: this._normalizeScheduledAtMs(options?.scheduledAtMs, createdAtMs),
|
|
1332
|
+
timeoutMs: this._normalizeJobTimeoutMs(options)
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
/**
|
|
1337
|
+
* Normalizes a per-job timeout while preserving omitted (worker fallback)
|
|
1338
|
+
* separately from explicitly disabled.
|
|
1339
|
+
* @param {import("./types.js").BackgroundJobOptions | undefined} options - Job options.
|
|
1340
|
+
* @returns {number | null} - Positive timeout, zero for disabled, or null when omitted.
|
|
1341
|
+
*/
|
|
1342
|
+
_normalizeJobTimeoutMs(options) {
|
|
1343
|
+
if (options?.timeoutMs === undefined) return null
|
|
1344
|
+
|
|
1345
|
+
const timeoutMs = options.timeoutMs
|
|
1346
|
+
|
|
1347
|
+
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) {
|
|
1348
|
+
throw VelociousError.safe(JOB_TIMEOUT_VALIDATION_MESSAGE)
|
|
1336
1349
|
}
|
|
1350
|
+
|
|
1351
|
+
if (timeoutMs <= 0) return 0
|
|
1352
|
+
|
|
1353
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs > MAX_JOB_TIMEOUT_MS) {
|
|
1354
|
+
throw VelociousError.safe(JOB_TIMEOUT_VALIDATION_MESSAGE)
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
return timeoutMs
|
|
1337
1358
|
}
|
|
1338
1359
|
|
|
1339
1360
|
/**
|
|
@@ -1371,6 +1392,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1371
1392
|
schedule_key: scheduleKey,
|
|
1372
1393
|
concurrency_key: concurrency?.concurrencyKey || null,
|
|
1373
1394
|
max_concurrency: concurrency?.maxConcurrency || null,
|
|
1395
|
+
timeout_ms: preparedJob.timeoutMs,
|
|
1374
1396
|
handoff_id: null
|
|
1375
1397
|
}
|
|
1376
1398
|
})
|
|
@@ -1382,11 +1404,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1382
1404
|
* @returns {number} - Normalized max retries.
|
|
1383
1405
|
*/
|
|
1384
1406
|
_normalizeMaxRetries(maxRetries) {
|
|
1385
|
-
|
|
1386
|
-
return Math.floor(maxRetries)
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
return DEFAULT_MAX_RETRIES
|
|
1407
|
+
return normalizeBackgroundJobMaxRetries(maxRetries)
|
|
1390
1408
|
}
|
|
1391
1409
|
|
|
1392
1410
|
/**
|
|
@@ -1396,10 +1414,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1396
1414
|
* @returns {number} - Dispatch timestamp.
|
|
1397
1415
|
*/
|
|
1398
1416
|
_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")
|
|
1417
|
+
return normalizeBackgroundJobScheduledAtMs(scheduledAtMs, defaultScheduledAtMs)
|
|
1403
1418
|
}
|
|
1404
1419
|
|
|
1405
1420
|
/**
|
|
@@ -1408,14 +1423,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1408
1423
|
* @returns {number} - Future eligibility timestamp.
|
|
1409
1424
|
*/
|
|
1410
1425
|
_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
|
|
1426
|
+
return rescheduledBackgroundJobAtMs(delayMs, Date.now())
|
|
1419
1427
|
}
|
|
1420
1428
|
|
|
1421
1429
|
/**
|
|
@@ -1424,9 +1432,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1424
1432
|
* @returns {void}
|
|
1425
1433
|
*/
|
|
1426
1434
|
_validateRescheduleDelayMs(delayMs) {
|
|
1427
|
-
|
|
1428
|
-
throw VelociousError.safe("background job reschedule delayMs must be a non-negative safe integer")
|
|
1429
|
-
}
|
|
1435
|
+
rescheduledBackgroundJobAtMs(delayMs, 0)
|
|
1430
1436
|
}
|
|
1431
1437
|
|
|
1432
1438
|
/**
|
|
@@ -1612,6 +1618,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1612
1618
|
table.text("last_error", {null: true})
|
|
1613
1619
|
table.string("concurrency_key", {null: true, index: true})
|
|
1614
1620
|
table.integer("max_concurrency", {null: true})
|
|
1621
|
+
table.bigint("timeout_ms", {null: true})
|
|
1615
1622
|
|
|
1616
1623
|
await db.createTable(table)
|
|
1617
1624
|
}
|
|
@@ -1704,6 +1711,35 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1704
1711
|
|
|
1705
1712
|
await this._ensureQueueColumn(db)
|
|
1706
1713
|
await this._ensureScheduleKeyColumn(db)
|
|
1714
|
+
await this._ensureJobTimeoutColumn(db)
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* Idempotently adds the per-job wall-clock timeout to existing job tables.
|
|
1719
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
1720
|
+
* @returns {Promise<void>} - Resolves when ensured.
|
|
1721
|
+
*/
|
|
1722
|
+
async _ensureJobTimeoutColumn(db) {
|
|
1723
|
+
const lockName = `${MIGRATION_SCOPE}:timeout_ms_column`
|
|
1724
|
+
const acquired = await db.acquireAdvisoryLock(lockName)
|
|
1725
|
+
|
|
1726
|
+
if (!acquired) throw new Error("Failed to acquire background jobs timeout schema lock")
|
|
1727
|
+
|
|
1728
|
+
try {
|
|
1729
|
+
db.clearSchemaCache()
|
|
1730
|
+
const table = await db.getTableByNameOrFail(JOBS_TABLE)
|
|
1731
|
+
|
|
1732
|
+
if (!(await table.getColumnByName("timeout_ms"))) {
|
|
1733
|
+
const tableData = new TableData(JOBS_TABLE)
|
|
1734
|
+
tableData.bigint("timeout_ms", {null: true})
|
|
1735
|
+
|
|
1736
|
+
for (const sql of await db.alterTableSQLs(tableData)) await db.query(sql)
|
|
1737
|
+
|
|
1738
|
+
db.clearSchemaCache()
|
|
1739
|
+
}
|
|
1740
|
+
} finally {
|
|
1741
|
+
await db.releaseAdvisoryLock(lockName)
|
|
1742
|
+
}
|
|
1707
1743
|
}
|
|
1708
1744
|
|
|
1709
1745
|
/**
|
|
@@ -2083,14 +2119,14 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2083
2119
|
// `execution_mode` is the single source of truth for a job's runtime and is
|
|
2084
2120
|
// written on every enqueue; the drop-forked migration backfills any pre-existing
|
|
2085
2121
|
// rows before the legacy `forked` column is removed.
|
|
2086
|
-
const executionMode = row.execution_mode ? this._normalizeExecutionModeName(String(row.execution_mode)) :
|
|
2122
|
+
const executionMode = row.execution_mode ? this._normalizeExecutionModeName(String(row.execution_mode)) : DEFAULT_BACKGROUND_JOB_EXECUTION_MODE
|
|
2087
2123
|
|
|
2088
2124
|
return {
|
|
2089
2125
|
id: String(row.id),
|
|
2090
2126
|
jobName: String(row.job_name),
|
|
2091
2127
|
args: this._parseArgs(row.args_json),
|
|
2092
2128
|
executionMode,
|
|
2093
|
-
queue: row.queue ? String(row.queue) :
|
|
2129
|
+
queue: row.queue ? String(row.queue) : DEFAULT_BACKGROUND_JOB_QUEUE,
|
|
2094
2130
|
scheduleKey: row.schedule_key ? String(row.schedule_key) : null,
|
|
2095
2131
|
status: row.status ? String(row.status) : "queued",
|
|
2096
2132
|
attempts: this._normalizeNumber(row.attempts),
|
|
@@ -2105,26 +2141,9 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2105
2141
|
workerId: row.worker_id ? String(row.worker_id) : null,
|
|
2106
2142
|
lastError: row.last_error ? String(row.last_error) : null,
|
|
2107
2143
|
concurrencyKey: row.concurrency_key ? String(row.concurrency_key) : null,
|
|
2108
|
-
maxConcurrency: this._normalizeNumber(row.max_concurrency)
|
|
2109
|
-
|
|
2110
|
-
}
|
|
2111
|
-
|
|
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")
|
|
2144
|
+
maxConcurrency: this._normalizeNumber(row.max_concurrency),
|
|
2145
|
+
timeoutMs: this._normalizeNumber(row.timeout_ms)
|
|
2123
2146
|
}
|
|
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
2147
|
}
|
|
2129
2148
|
|
|
2130
2149
|
/**
|
|
@@ -2133,11 +2152,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2133
2152
|
* @returns {string} - Queue name.
|
|
2134
2153
|
*/
|
|
2135
2154
|
_normalizeQueue(options) {
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
if (typeof queue === "string" && queue.trim().length > 0) return queue.trim()
|
|
2139
|
-
|
|
2140
|
-
return DEFAULT_QUEUE
|
|
2155
|
+
return normalizeBackgroundJobQueue(options)
|
|
2141
2156
|
}
|
|
2142
2157
|
|
|
2143
2158
|
/**
|
|
@@ -2151,15 +2166,11 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2151
2166
|
* @returns {{concurrencyKey: string, maxConcurrency: number, queueDerived: boolean} | null} - Resolved concurrency.
|
|
2152
2167
|
*/
|
|
2153
2168
|
_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}
|
|
2169
|
+
return normalizeBackgroundJobConcurrency({
|
|
2170
|
+
options: options || {},
|
|
2171
|
+
queue,
|
|
2172
|
+
queues: this.configuration.getBackgroundJobsConfig().queues
|
|
2173
|
+
})
|
|
2163
2174
|
}
|
|
2164
2175
|
|
|
2165
2176
|
/**
|
|
@@ -2667,21 +2678,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2667
2678
|
* @returns {import("./types.js").BackgroundJobExecutionMode} - Normalized execution mode.
|
|
2668
2679
|
*/
|
|
2669
2680
|
_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
|
|
2681
|
+
return normalizeBackgroundJobExecutionMode(options || {}, DEFAULT_BACKGROUND_JOB_EXECUTION_MODE)
|
|
2685
2682
|
}
|
|
2686
2683
|
|
|
2687
2684
|
/**
|
|
@@ -2690,11 +2687,11 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2690
2687
|
* @returns {import("./types.js").BackgroundJobExecutionMode} - Normalized execution mode.
|
|
2691
2688
|
*/
|
|
2692
2689
|
_normalizeExecutionModeName(executionMode) {
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2690
|
+
return normalizeBackgroundJobExecutionMode(
|
|
2691
|
+
{executionMode: /** @type {import("./types.js").BackgroundJobExecutionMode} */ (executionMode)},
|
|
2692
|
+
DEFAULT_BACKGROUND_JOB_EXECUTION_MODE,
|
|
2693
|
+
BACKGROUND_JOB_EXECUTION_MODES
|
|
2694
|
+
)
|
|
2698
2695
|
}
|
|
2699
2696
|
|
|
2700
2697
|
/**
|
|
@@ -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.
|
|
@@ -28,6 +53,7 @@
|
|
|
28
53
|
* @property {boolean} [deduplicateWhileQueued] - When true, skip the enqueue if an identical still-queued job (same job name, args and queue) is scheduled no later than this enqueue, returning the earliest matching job's id. A future retry does not suppress earlier work. Deduplication is independent of `concurrencyKey`, so the job keeps its normal (e.g. queue-derived) concurrency cap. Keeps an interval-scheduled recurring job (e.g. retention pruning) from piling up redundant queued rows when it runs slower than its interval or no worker is free.
|
|
29
54
|
* @property {string} [idempotencyKey] - Durable enqueue identity scoped to the resolved job class name and queue. Exact replay returns the original job id across every state and after job pruning; reuse with different canonical arguments or behavior-affecting options fails. Ownership is independent of `deduplicateWhileQueued` and is retained until an explicit future retention policy removes it.
|
|
30
55
|
* @property {number} [scheduledAtMs] - Epoch timestamp in milliseconds when the job becomes eligible for dispatch. Defaults to enqueue time.
|
|
56
|
+
* @property {number} [timeoutMs] - Per-job wall-clock timeout for forked and pooled execution. A positive integer up to 2,147,483,647 overrides the worker-level `jobTimeoutMs`; a non-positive finite value disables the timeout for this job.
|
|
31
57
|
*/
|
|
32
58
|
/**
|
|
33
59
|
* @typedef {object} BackgroundJobPayload
|
|
@@ -61,6 +87,7 @@
|
|
|
61
87
|
* @property {string | null} lastError - Last failure message.
|
|
62
88
|
* @property {string | null} concurrencyKey - Durable concurrency key.
|
|
63
89
|
* @property {number | null} maxConcurrency - Durable per-key cap.
|
|
90
|
+
* @property {number | null} timeoutMs - Per-job wall-clock timeout override, or null when omitted.
|
|
64
91
|
*/
|
|
65
92
|
/**
|
|
66
93
|
* @typedef {"queued" | "handed_off" | null} BackgroundJobReplacementPreviousStatus
|
|
@@ -690,7 +690,7 @@ export default class BackgroundJobsWorker {
|
|
|
690
690
|
state.lastDispatchSeq = ++this._pooledDispatchSeq
|
|
691
691
|
|
|
692
692
|
return new Promise((resolve) => {
|
|
693
|
-
const timeoutTimer = this._armPooledJobTimeout({child,
|
|
693
|
+
const timeoutTimer = this._armPooledJobTimeout({child, payload})
|
|
694
694
|
|
|
695
695
|
state.inflight.set(payload.id, {payload, resolve, timeoutTimer})
|
|
696
696
|
try {
|
|
@@ -753,15 +753,15 @@ export default class BackgroundJobsWorker {
|
|
|
753
753
|
* the timer, or null when no timeout is configured.
|
|
754
754
|
* @param {object} args - Options.
|
|
755
755
|
* @param {import("node:child_process").ChildProcess} args.child - Pooled child.
|
|
756
|
-
* @param {string} args.
|
|
756
|
+
* @param {import("./types.js").BackgroundJobPayload & {id: string}} args.payload - Job payload whose overrun is guarded.
|
|
757
757
|
* @returns {ReturnType<typeof setTimeout> | null} - The armed timer, or null.
|
|
758
758
|
*/
|
|
759
|
-
_armPooledJobTimeout({child,
|
|
760
|
-
const timeoutMs = this._resolveJobTimeoutMs()
|
|
759
|
+
_armPooledJobTimeout({child, payload}) {
|
|
760
|
+
const timeoutMs = this._resolveJobTimeoutMs(payload.options)
|
|
761
761
|
|
|
762
762
|
if (!(typeof timeoutMs === "number" && timeoutMs > 0)) return null
|
|
763
763
|
|
|
764
|
-
return setTimeout(() => this._onPooledJobTimeout({child, jobId}), timeoutMs)
|
|
764
|
+
return setTimeout(() => this._onPooledJobTimeout({child, jobId: payload.id}), timeoutMs)
|
|
765
765
|
}
|
|
766
766
|
|
|
767
767
|
/**
|
|
@@ -1016,7 +1016,7 @@ export default class BackgroundJobsWorker {
|
|
|
1016
1016
|
* @returns {Promise<void>} - Resolves when the child exits.
|
|
1017
1017
|
*/
|
|
1018
1018
|
_waitForForkedChild({child, payload}) {
|
|
1019
|
-
const timeoutState = this._armForkedJobTimeout({child})
|
|
1019
|
+
const timeoutState = this._armForkedJobTimeout({child, payload})
|
|
1020
1020
|
|
|
1021
1021
|
return new Promise((resolve) => {
|
|
1022
1022
|
child.once("exit", (code, signal) => {
|
|
@@ -1040,10 +1040,11 @@ export default class BackgroundJobsWorker {
|
|
|
1040
1040
|
* and behavior is unchanged.
|
|
1041
1041
|
* @param {object} args - Options.
|
|
1042
1042
|
* @param {import("node:child_process").ChildProcess} args.child - Forked child process.
|
|
1043
|
+
* @param {import("./types.js").BackgroundJobPayload & {id: string}} args.payload - Job payload.
|
|
1043
1044
|
* @returns {ForkedJobTimeoutState} - Timeout state.
|
|
1044
1045
|
*/
|
|
1045
|
-
_armForkedJobTimeout({child}) {
|
|
1046
|
-
const timeoutMs = this._resolveJobTimeoutMs()
|
|
1046
|
+
_armForkedJobTimeout({child, payload}) {
|
|
1047
|
+
const timeoutMs = this._resolveJobTimeoutMs(payload.options)
|
|
1047
1048
|
/** @type {ForkedJobTimeoutState} */
|
|
1048
1049
|
const state = {timedOut: false, timeoutMs, timer: null, sigkillTimer: null}
|
|
1049
1050
|
|
|
@@ -1056,14 +1057,18 @@ export default class BackgroundJobsWorker {
|
|
|
1056
1057
|
|
|
1057
1058
|
/**
|
|
1058
1059
|
* Resolves the effective wall-clock job timeout in ms (shared by forked and pooled jobs), or null when disabled. The
|
|
1059
|
-
*
|
|
1060
|
-
* configuration. A non-positive value disables the
|
|
1060
|
+
* per-job override wins, followed by the constructor override, then the value
|
|
1061
|
+
* from the background-jobs configuration. A non-positive value disables the
|
|
1062
|
+
* backstop at whichever level supplied it.
|
|
1063
|
+
* @param {import("./types.js").BackgroundJobOptions} [jobOptions] - Per-job options.
|
|
1061
1064
|
* @returns {number | null} - Timeout in ms, or null when disabled.
|
|
1062
1065
|
*/
|
|
1063
|
-
_resolveJobTimeoutMs() {
|
|
1064
|
-
const raw = typeof
|
|
1065
|
-
?
|
|
1066
|
-
: (
|
|
1066
|
+
_resolveJobTimeoutMs(jobOptions) {
|
|
1067
|
+
const raw = typeof jobOptions?.timeoutMs === "number"
|
|
1068
|
+
? jobOptions.timeoutMs
|
|
1069
|
+
: (typeof this.jobTimeoutMsOverride === "number"
|
|
1070
|
+
? this.jobTimeoutMsOverride
|
|
1071
|
+
: (this.configuration ? this.configuration.getBackgroundJobsConfig().jobTimeoutMs : null))
|
|
1067
1072
|
|
|
1068
1073
|
// A non-finite (e.g. Infinity) or non-positive value disables the backstop;
|
|
1069
1074
|
// a finite value beyond Node's timer range is clamped to the max rather than
|
|
@@ -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} */
|