velocious 1.0.598 → 1.0.600
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 +12 -1
- package/build/background-jobs/adapter-client.js +45 -0
- package/build/background-jobs/adapter.js +139 -0
- package/build/background-jobs/job-registry.js +1 -1
- package/build/background-jobs/job.js +20 -129
- package/build/background-jobs/main.js +71 -28
- package/build/background-jobs/perform-job.js +23 -0
- package/build/background-jobs/platform-job.js +156 -0
- package/build/background-jobs/runtime.js +156 -0
- package/build/background-jobs/sql-adapter.js +20 -0
- package/build/background-jobs/store.js +3 -1
- package/build/background-jobs/types.js +10 -0
- package/build/background-jobs/web/controller.js +5 -2
- package/build/background-jobs/worker.js +6 -8
- package/build/configuration-types.js +5 -0
- package/build/configuration.js +217 -40
- package/build/environment-handlers/base.js +20 -0
- package/build/environment-handlers/node.js +27 -20
- package/build/jobs/prune-terminal-background-jobs.js +2 -3
- package/build/src/background-jobs/adapter-client.d.ts +41 -0
- package/build/src/background-jobs/adapter-client.d.ts.map +1 -0
- package/build/src/background-jobs/adapter-client.js +39 -0
- package/build/src/background-jobs/adapter.d.ts +165 -0
- package/build/src/background-jobs/adapter.d.ts.map +1 -0
- package/build/src/background-jobs/adapter.js +121 -0
- package/build/src/background-jobs/job-registry.d.ts +1 -1
- package/build/src/background-jobs/job-registry.d.ts.map +1 -1
- package/build/src/background-jobs/job-registry.js +2 -2
- package/build/src/background-jobs/job.d.ts +6 -69
- package/build/src/background-jobs/job.d.ts.map +1 -1
- package/build/src/background-jobs/job.js +17 -116
- package/build/src/background-jobs/main.d.ts +13 -3
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +66 -30
- package/build/src/background-jobs/perform-job.d.ts +16 -0
- package/build/src/background-jobs/perform-job.d.ts.map +1 -0
- package/build/src/background-jobs/perform-job.js +22 -0
- package/build/src/background-jobs/platform-job.d.ts +110 -0
- package/build/src/background-jobs/platform-job.d.ts.map +1 -0
- package/build/src/background-jobs/platform-job.js +138 -0
- package/build/src/background-jobs/runtime.d.ts +78 -0
- package/build/src/background-jobs/runtime.d.ts.map +1 -0
- package/build/src/background-jobs/runtime.js +132 -0
- package/build/src/background-jobs/sql-adapter.d.ts +13 -0
- package/build/src/background-jobs/sql-adapter.d.ts.map +1 -0
- package/build/src/background-jobs/sql-adapter.js +18 -0
- package/build/src/background-jobs/store.d.ts +2 -1
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +4 -2
- package/build/src/background-jobs/types.d.ts +41 -0
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +11 -1
- package/build/src/background-jobs/web/controller.d.ts.map +1 -1
- package/build/src/background-jobs/web/controller.js +5 -3
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +7 -8
- package/build/src/configuration-types.d.ts +16 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +5 -1
- package/build/src/configuration.d.ts +51 -2
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +202 -39
- package/build/src/environment-handlers/base.d.ts +17 -0
- package/build/src/environment-handlers/base.d.ts.map +1 -1
- package/build/src/environment-handlers/base.js +19 -1
- package/build/src/environment-handlers/node.d.ts +18 -0
- package/build/src/environment-handlers/node.d.ts.map +1 -1
- package/build/src/environment-handlers/node.js +25 -20
- package/build/src/jobs/prune-terminal-background-jobs.d.ts.map +1 -1
- package/build/src/jobs/prune-terminal-background-jobs.js +3 -4
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/background-jobs/adapter-client.js +45 -0
- package/src/background-jobs/adapter.js +139 -0
- package/src/background-jobs/job-registry.js +1 -1
- package/src/background-jobs/job.js +20 -129
- package/src/background-jobs/main.js +71 -28
- package/src/background-jobs/perform-job.js +23 -0
- package/src/background-jobs/platform-job.js +156 -0
- package/src/background-jobs/runtime.js +156 -0
- package/src/background-jobs/sql-adapter.js +20 -0
- package/src/background-jobs/store.js +3 -1
- package/src/background-jobs/types.js +10 -0
- package/src/background-jobs/web/controller.js +5 -2
- package/src/background-jobs/worker.js +6 -8
- package/src/configuration-types.js +5 -0
- package/src/configuration.js +217 -40
- package/src/environment-handlers/base.js +20 -0
- package/src/environment-handlers/node.js +27 -20
- package/src/jobs/prune-terminal-background-jobs.js +2 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import BackgroundJobsStore from "./store.js"
|
|
4
|
+
|
|
5
|
+
/** Built-in SQL adapter preserving the existing durable store implementation. */
|
|
6
|
+
export default class SqlBackgroundJobsAdapter extends BackgroundJobsStore {
|
|
7
|
+
/**
|
|
8
|
+
* Ensures the built-in SQL schema during migration.
|
|
9
|
+
* @param {{dbs: Record<string, import("../database/drivers/base.js").default>}} args - Migrated databases.
|
|
10
|
+
* @returns {Promise<void>} - Resolves when the SQL schema is present.
|
|
11
|
+
*/
|
|
12
|
+
async ensureFrameworkSchema({dbs}) {
|
|
13
|
+
const databaseIdentifier = this.getDatabaseIdentifier() || "default"
|
|
14
|
+
const frameworkDb = dbs[databaseIdentifier]
|
|
15
|
+
|
|
16
|
+
if (!frameworkDb) return
|
|
17
|
+
|
|
18
|
+
await this.ensureSchema(frameworkDb)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import {createHash, randomUUID} from "crypto"
|
|
4
|
+
import BackgroundJobsAdapter from "./adapter.js"
|
|
4
5
|
import Logger from "../logger.js"
|
|
5
6
|
import TableData from "../database/table-data/index.js"
|
|
6
7
|
import VelociousError from "../velocious-error.js"
|
|
@@ -100,7 +101,7 @@ const schemaApplyChains = new Map()
|
|
|
100
101
|
/** @type {Map<string, Promise<void>>} */
|
|
101
102
|
const transactionMutationChains = new Map()
|
|
102
103
|
|
|
103
|
-
export default class BackgroundJobsStore {
|
|
104
|
+
export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
104
105
|
/**
|
|
105
106
|
* Runs constructor.
|
|
106
107
|
* @param {object} args - Options.
|
|
@@ -108,6 +109,7 @@ export default class BackgroundJobsStore {
|
|
|
108
109
|
* @param {string} [args.databaseIdentifier] - Database identifier.
|
|
109
110
|
*/
|
|
110
111
|
constructor({configuration, databaseIdentifier}) {
|
|
112
|
+
super()
|
|
111
113
|
this.configuration = configuration
|
|
112
114
|
this.databaseIdentifier = databaseIdentifier
|
|
113
115
|
this.logger = new Logger(this)
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {"inline" | "forked" | "pooled" | "spawned"} BackgroundJobExecutionMode
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {object} BackgroundJobsHealth
|
|
8
|
+
* @property {boolean} ready - Whether the adapter can accept and process work.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} BackgroundJobsProducer
|
|
12
|
+
* @property {(args: {jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: BackgroundJobOptions}) => Promise<string>} enqueue - Enqueues a job.
|
|
13
|
+
* @property {(args: {scheduleKey: string, jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: BackgroundJobOptions}) => Promise<BackgroundJobReplacementResult>} replaceScheduled - Replaces a stable schedule.
|
|
14
|
+
* @property {(args: {scheduleKey: string}) => Promise<BackgroundJobCancellationResult>} cancelScheduled - Cancels a stable schedule.
|
|
15
|
+
*/
|
|
6
16
|
/**
|
|
7
17
|
* @typedef {object} BackgroundJobHandoff
|
|
8
18
|
* @property {string} handoffId - Unique handoff lease id.
|
|
@@ -99,11 +99,14 @@ export default class VelociousBackgroundJobsWebController extends Controller {
|
|
|
99
99
|
*/
|
|
100
100
|
async health() {
|
|
101
101
|
await this._respond(async () => {
|
|
102
|
+
const health = await this.getConfiguration().backgroundJobsHealth()
|
|
103
|
+
|
|
102
104
|
await this.render({json: {
|
|
103
105
|
capabilities: {backgroundJobCountDeltas: 1},
|
|
104
|
-
ok:
|
|
106
|
+
ok: health.ready,
|
|
107
|
+
ready: health.ready,
|
|
105
108
|
service: "velocious-background-jobs"
|
|
106
|
-
}})
|
|
109
|
+
}, status: health.ready ? 200 : 503})
|
|
107
110
|
})
|
|
108
111
|
}
|
|
109
112
|
|
|
@@ -10,6 +10,7 @@ import {randomUUID} from "crypto"
|
|
|
10
10
|
import {fileURLToPath} from "node:url"
|
|
11
11
|
import shutdownLifecycle from "../utils/shutdown-lifecycle.js"
|
|
12
12
|
import BackgroundJobRescheduleSignal from "./reschedule-signal.js"
|
|
13
|
+
import performBackgroundJob from "./perform-job.js"
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Per-forked-child timeout bookkeeping.
|
|
@@ -958,14 +959,11 @@ export default class BackgroundJobsWorker {
|
|
|
958
959
|
const registry = new BackgroundJobRegistry({configuration})
|
|
959
960
|
await registry.load()
|
|
960
961
|
const JobClass = registry.getJobByName(payload.jobName)
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
await configuration.withConnections({databaseIdentifiers: JobClass.databaseIdentifiers, name: `Background job worker inline: ${payload.jobName}`}, async () => {
|
|
968
|
-
await perform.apply(jobInstance, payload.args || [])
|
|
962
|
+
await performBackgroundJob({
|
|
963
|
+
configuration,
|
|
964
|
+
JobClass,
|
|
965
|
+
jobArgs: payload.args || [],
|
|
966
|
+
name: `Background job worker inline: ${payload.jobName}`
|
|
969
967
|
})
|
|
970
968
|
}
|
|
971
969
|
|
|
@@ -156,8 +156,13 @@
|
|
|
156
156
|
* `background_jobs` table (see `pollIntervalMs`).
|
|
157
157
|
*/
|
|
158
158
|
|
|
159
|
+
/** @typedef {"background" | "inline"} BackgroundJobsMode */
|
|
160
|
+
/** @typedef {(args: {configuration: import("./configuration.js").default}) => import("./background-jobs/adapter.js").default} BackgroundJobsAdapterFactory */
|
|
161
|
+
|
|
159
162
|
/**
|
|
160
163
|
* @typedef {object} BackgroundJobsConfiguration
|
|
164
|
+
* @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.
|
|
165
|
+
* @property {BackgroundJobsMode} [mode] - `"background"` uses the configured adapter/transport and durable queue semantics; `"inline"` performs immediately without durable queue state. Defaults to `"background"`.
|
|
161
166
|
* @property {string} [host] - Hostname for the background jobs main process.
|
|
162
167
|
* @property {number} [port] - Port for the background jobs main process.
|
|
163
168
|
* @property {string} [databaseIdentifier] - Database identifier used to store background jobs.
|
package/src/configuration.js
CHANGED
|
@@ -11,11 +11,20 @@
|
|
|
11
11
|
* @property {string[]} [databaseIdentifiers] - Database identifiers to include in the connection scope.
|
|
12
12
|
* @property {string} [name] - Human-readable name for the checked-out database connections.
|
|
13
13
|
*/
|
|
14
|
+
/**
|
|
15
|
+
* One adapter instance and its serialized ready/close lifecycle.
|
|
16
|
+
* @typedef {object} BackgroundJobsAdapterGeneration
|
|
17
|
+
* @property {import("./background-jobs/adapter.js").default} adapter - Adapter owned by this generation.
|
|
18
|
+
* @property {boolean} closing - Whether close has claimed this generation.
|
|
19
|
+
* @property {Promise<void> | undefined} readyPromise - Shared readiness attempt.
|
|
20
|
+
* @property {Promise<void> | undefined} closePromise - Shared close operation.
|
|
21
|
+
*/
|
|
14
22
|
|
|
15
23
|
import {digg} from "diggerize"
|
|
16
24
|
import gettextConfig from "gettext-universal/build/src/config.js"
|
|
17
25
|
import translate from "gettext-universal/build/src/translate.js"
|
|
18
26
|
import Ability from "./authorization/ability.js"
|
|
27
|
+
import BackgroundJobsAdapter from "./background-jobs/adapter.js"
|
|
19
28
|
import DatabaseOperation from "./database/operation.js"
|
|
20
29
|
import {initializeAuditedModelRelationships} from "./database/record/auditing.js"
|
|
21
30
|
import EventEmitter from "./utils/event-emitter.js"
|
|
@@ -204,6 +213,9 @@ export default class VelociousConfiguration {
|
|
|
204
213
|
* @type {Promise<void> | null} */
|
|
205
214
|
_closeDatabaseConnectionsPromise = null
|
|
206
215
|
|
|
216
|
+
/** @type {BackgroundJobsAdapterGeneration | undefined} */
|
|
217
|
+
_backgroundJobsAdapterGeneration = undefined
|
|
218
|
+
|
|
207
219
|
/**
|
|
208
220
|
* Dedicated advisory-lock connections currently holding a lock. These are spawned
|
|
209
221
|
* outside the pools' tracked sets (so a hold-timeout lock survives pool checkouts),
|
|
@@ -268,7 +280,7 @@ export default class VelociousConfiguration {
|
|
|
268
280
|
this.debug = debug
|
|
269
281
|
this._debugEndpoint = this._normalizeDebugEndpoint(debugEndpoint)
|
|
270
282
|
this._apiManifest = this._normalizeApiManifest(apiManifest)
|
|
271
|
-
this._environment = environment || process
|
|
283
|
+
this._environment = environment || globalThis.process?.env.VELOCIOUS_ENV || globalThis.process?.env.NODE_ENV || "development"
|
|
272
284
|
this._environmentHandler = environmentHandler
|
|
273
285
|
this._enforceTenantDatabaseScopes = enforceTenantDatabaseScopes
|
|
274
286
|
this._exposeInternalErrorsToClients = exposeInternalErrorsToClients
|
|
@@ -1441,22 +1453,23 @@ export default class VelociousConfiguration {
|
|
|
1441
1453
|
|
|
1442
1454
|
/**
|
|
1443
1455
|
* Runs get background jobs config.
|
|
1444
|
-
* @returns {Required<import("./configuration-types.js").BackgroundJobsConfiguration> & {retention: import("./configuration-types.js").ResolvedBackgroundJobsRetentionConfiguration}} - Background jobs configuration.
|
|
1456
|
+
* @returns {Omit<Required<import("./configuration-types.js").BackgroundJobsConfiguration>, "adapter" | "retention"> & {retention: import("./configuration-types.js").ResolvedBackgroundJobsRetentionConfiguration}} - Background jobs configuration.
|
|
1445
1457
|
*/
|
|
1446
1458
|
getBackgroundJobsConfig() {
|
|
1447
|
-
const
|
|
1448
|
-
const
|
|
1449
|
-
const
|
|
1450
|
-
const
|
|
1451
|
-
const
|
|
1452
|
-
const
|
|
1453
|
-
const
|
|
1454
|
-
const
|
|
1455
|
-
const
|
|
1456
|
-
const
|
|
1457
|
-
const
|
|
1458
|
-
const
|
|
1459
|
-
const
|
|
1459
|
+
const processEnvironment = globalThis.process?.env
|
|
1460
|
+
const envHost = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_HOST
|
|
1461
|
+
const envPortRaw = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_PORT
|
|
1462
|
+
const envDatabaseIdentifier = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_DATABASE_IDENTIFIER
|
|
1463
|
+
const envMaxConcurrentForkedRaw = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_MAX_CONCURRENT_FORKED_JOBS
|
|
1464
|
+
const envMaxConcurrentRaw = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_MAX_CONCURRENT_INLINE_JOBS
|
|
1465
|
+
const envPooledRunnerCountRaw = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_COUNT
|
|
1466
|
+
const envPooledRunnerConcurrencyRaw = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_CONCURRENCY
|
|
1467
|
+
const envPooledRunnerMaxJobsRaw = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_JOBS
|
|
1468
|
+
const envPooledRunnerMaxRssBytesRaw = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_RSS_BYTES
|
|
1469
|
+
const envPooledRunnerMaxLifetimeMsRaw = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_LIFETIME_MS
|
|
1470
|
+
const envDispatchStrategy = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_DISPATCH_STRATEGY
|
|
1471
|
+
const envPollIntervalRaw = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_POLL_INTERVAL_MS
|
|
1472
|
+
const envJobTimeoutRaw = processEnvironment?.VELOCIOUS_BACKGROUND_JOBS_JOB_TIMEOUT_MS
|
|
1460
1473
|
const envPort = envPortRaw ? Number(envPortRaw) : undefined
|
|
1461
1474
|
const envMaxConcurrentForked = envMaxConcurrentForkedRaw ? Number(envMaxConcurrentForkedRaw) : undefined
|
|
1462
1475
|
const envMaxConcurrent = envMaxConcurrentRaw ? Number(envMaxConcurrentRaw) : undefined
|
|
@@ -1468,6 +1481,11 @@ export default class VelociousConfiguration {
|
|
|
1468
1481
|
const envPollInterval = envPollIntervalRaw ? Number(envPollIntervalRaw) : undefined
|
|
1469
1482
|
const envJobTimeout = envJobTimeoutRaw ? Number(envJobTimeoutRaw) : undefined
|
|
1470
1483
|
const configured = this._backgroundJobs || {}
|
|
1484
|
+
const mode = configured.mode === undefined ? "background" : configured.mode
|
|
1485
|
+
|
|
1486
|
+
if (mode !== "background" && mode !== "inline") {
|
|
1487
|
+
throw new TypeError(`backgroundJobs.mode must be "background" or "inline", got: ${String(mode)}`)
|
|
1488
|
+
}
|
|
1471
1489
|
const host = configured.host || envHost || "127.0.0.1"
|
|
1472
1490
|
const port = typeof configured.port === "number"
|
|
1473
1491
|
? configured.port
|
|
@@ -1522,7 +1540,146 @@ export default class VelociousConfiguration {
|
|
|
1522
1540
|
: 60 * 60 * 1000
|
|
1523
1541
|
}
|
|
1524
1542
|
|
|
1525
|
-
return {host, port, databaseIdentifier, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerConcurrency, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, dispatchStrategy, pollIntervalMs, queues, jobTimeoutMs, retention}
|
|
1543
|
+
return {host, port, databaseIdentifier, maxConcurrentForkedJobs, maxConcurrentInlineJobs, mode, pooledRunnerCount, pooledRunnerConcurrency, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, dispatchStrategy, pollIntervalMs, queues, jobTimeoutMs, retention}
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/**
|
|
1547
|
+
* Resolves and memoizes one background-jobs adapter for this configuration lifecycle.
|
|
1548
|
+
* @returns {BackgroundJobsAdapter} - Active adapter.
|
|
1549
|
+
*/
|
|
1550
|
+
getBackgroundJobsAdapter() {
|
|
1551
|
+
if (this._backgroundJobsAdapterGeneration) return this._backgroundJobsAdapterGeneration.adapter
|
|
1552
|
+
|
|
1553
|
+
const configuredAdapter = this._backgroundJobs?.adapter
|
|
1554
|
+
const adapter = typeof configuredAdapter === "function"
|
|
1555
|
+
? configuredAdapter({configuration: this})
|
|
1556
|
+
: (configuredAdapter || this.getEnvironmentHandler().createBackgroundJobsAdapter({configuration: this}))
|
|
1557
|
+
|
|
1558
|
+
if (!(adapter instanceof BackgroundJobsAdapter)) {
|
|
1559
|
+
throw new TypeError("backgroundJobs.adapter must be a BackgroundJobsAdapter instance or a synchronous factory returning one")
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
this._backgroundJobsAdapterGeneration = {
|
|
1563
|
+
adapter,
|
|
1564
|
+
closing: false,
|
|
1565
|
+
closePromise: undefined,
|
|
1566
|
+
readyPromise: undefined
|
|
1567
|
+
}
|
|
1568
|
+
return adapter
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
/**
|
|
1572
|
+
* Atomically acquires the exact ready adapter for the active lifecycle.
|
|
1573
|
+
* A close that claims the generation while readiness is pending wins: this
|
|
1574
|
+
* operation waits for that close, creates the next generation, readies it,
|
|
1575
|
+
* and returns only that live instance.
|
|
1576
|
+
* @returns {Promise<BackgroundJobsAdapter>} - Exact ready adapter generation.
|
|
1577
|
+
*/
|
|
1578
|
+
async acquireReadyBackgroundJobsAdapter() {
|
|
1579
|
+
while (true) {
|
|
1580
|
+
const databaseClosePromise = this._closeDatabaseConnectionsPromise
|
|
1581
|
+
|
|
1582
|
+
if (databaseClosePromise) {
|
|
1583
|
+
await databaseClosePromise
|
|
1584
|
+
continue
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
this.getBackgroundJobsAdapter()
|
|
1588
|
+
const generation = this._backgroundJobsAdapterGeneration
|
|
1589
|
+
|
|
1590
|
+
if (!generation) throw new Error("Background jobs adapter generation was not created")
|
|
1591
|
+
|
|
1592
|
+
if (generation.closing) {
|
|
1593
|
+
if (generation.closePromise) await generation.closePromise
|
|
1594
|
+
continue
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
const readyPromise = generation.readyPromise || Promise.resolve().then(async () => {
|
|
1598
|
+
await generation.adapter.ensureReady()
|
|
1599
|
+
})
|
|
1600
|
+
|
|
1601
|
+
generation.readyPromise = readyPromise
|
|
1602
|
+
|
|
1603
|
+
try {
|
|
1604
|
+
await readyPromise
|
|
1605
|
+
} catch (error) {
|
|
1606
|
+
if (generation.readyPromise === readyPromise) generation.readyPromise = undefined
|
|
1607
|
+
throw error
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
if (generation.closing) {
|
|
1611
|
+
if (generation.closePromise) await generation.closePromise
|
|
1612
|
+
continue
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
if (this._backgroundJobsAdapterGeneration !== generation) continue
|
|
1616
|
+
|
|
1617
|
+
return generation.adapter
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
/**
|
|
1622
|
+
* Readies the active adapter once per lifecycle. A failed attempt remains retryable.
|
|
1623
|
+
* @returns {Promise<void>} - Resolves when ready.
|
|
1624
|
+
*/
|
|
1625
|
+
async ensureBackgroundJobsAdapterReady() {
|
|
1626
|
+
await this.acquireReadyBackgroundJobsAdapter()
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
/**
|
|
1630
|
+
* Returns health without resolving persistence in non-durable inline mode.
|
|
1631
|
+
* @returns {Promise<import("./background-jobs/types.js").BackgroundJobsHealth>} - Current health.
|
|
1632
|
+
*/
|
|
1633
|
+
async backgroundJobsHealth() {
|
|
1634
|
+
if (this.getBackgroundJobsConfig().mode === "inline") return {ready: true}
|
|
1635
|
+
|
|
1636
|
+
const adapter = await this.acquireReadyBackgroundJobsAdapter()
|
|
1637
|
+
|
|
1638
|
+
return await adapter.health()
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/**
|
|
1642
|
+
* Closes the resolved adapter once and clears lifecycle caches.
|
|
1643
|
+
* @returns {Promise<void>} - Resolves after close.
|
|
1644
|
+
*/
|
|
1645
|
+
async closeBackgroundJobsAdapter() {
|
|
1646
|
+
const generation = this._backgroundJobsAdapterGeneration
|
|
1647
|
+
|
|
1648
|
+
if (!generation) return
|
|
1649
|
+
if (generation.closePromise) return await generation.closePromise
|
|
1650
|
+
|
|
1651
|
+
generation.closing = true
|
|
1652
|
+
const closePromise = (async () => {
|
|
1653
|
+
/** @type {Error[]} */
|
|
1654
|
+
const closeErrors = []
|
|
1655
|
+
|
|
1656
|
+
if (generation.readyPromise) {
|
|
1657
|
+
try {
|
|
1658
|
+
await generation.readyPromise
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
closeErrors.push(error instanceof Error ? error : new Error(String(error)))
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
try {
|
|
1665
|
+
await generation.adapter.close()
|
|
1666
|
+
} catch (error) {
|
|
1667
|
+
closeErrors.push(error instanceof Error ? error : new Error(String(error)))
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
if (closeErrors.length === 1) throw closeErrors[0]
|
|
1671
|
+
if (closeErrors.length > 1) throw new AggregateError(closeErrors, "Failed to ready and close the background-jobs adapter")
|
|
1672
|
+
})()
|
|
1673
|
+
|
|
1674
|
+
generation.closePromise = closePromise
|
|
1675
|
+
|
|
1676
|
+
try {
|
|
1677
|
+
await closePromise
|
|
1678
|
+
} finally {
|
|
1679
|
+
if (this._backgroundJobsAdapterGeneration === generation) {
|
|
1680
|
+
this._backgroundJobsAdapterGeneration = undefined
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1526
1683
|
}
|
|
1527
1684
|
|
|
1528
1685
|
/**
|
|
@@ -1531,6 +1688,10 @@ export default class VelociousConfiguration {
|
|
|
1531
1688
|
* @returns {void}
|
|
1532
1689
|
*/
|
|
1533
1690
|
setBackgroundJobsConfig(backgroundJobs) {
|
|
1691
|
+
if (this._backgroundJobsAdapterGeneration && backgroundJobs.adapter !== undefined) {
|
|
1692
|
+
throw new Error("Cannot replace backgroundJobs.adapter during an active adapter lifecycle; close it first")
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1534
1695
|
this._backgroundJobs = Object.assign({}, this._backgroundJobs, backgroundJobs)
|
|
1535
1696
|
}
|
|
1536
1697
|
|
|
@@ -2099,8 +2260,8 @@ export default class VelociousConfiguration {
|
|
|
2099
2260
|
}
|
|
2100
2261
|
|
|
2101
2262
|
const initializeModelsPromise = (async () => {
|
|
2102
|
-
const shouldSkipDummyModelInitialization = process
|
|
2103
|
-
&& process
|
|
2263
|
+
const shouldSkipDummyModelInitialization = globalThis.process?.env.VELOCIOUS_SKIP_DUMMY_MODEL_INITIALIZATION === "1"
|
|
2264
|
+
&& globalThis.process?.env.VELOCIOUS_BROWSER_TESTS === "true"
|
|
2104
2265
|
&& this.getEnvironment() === "test"
|
|
2105
2266
|
|
|
2106
2267
|
if (!shouldSkipDummyModelInitialization) {
|
|
@@ -3425,34 +3586,50 @@ export default class VelociousConfiguration {
|
|
|
3425
3586
|
const constructors = new Set()
|
|
3426
3587
|
|
|
3427
3588
|
this._closeDatabaseConnectionsPromise = (async () => {
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
// pools' tracked sets, so `pool.closeAll()` would not reach them and a lock held
|
|
3431
|
-
// by a runner torn down mid-pass would leak until the DB server's `wait_timeout`.
|
|
3432
|
-
// Still close the pools even if this throws, so a stuck lock connection does not
|
|
3433
|
-
// leave the rest of the connections open.
|
|
3434
|
-
await this._closeAdvisoryLockConnections()
|
|
3435
|
-
} finally {
|
|
3436
|
-
for (const pool of Object.values(this.databasePools)) {
|
|
3437
|
-
if (!pool) continue
|
|
3589
|
+
/** @type {Error[]} */
|
|
3590
|
+
const closeErrors = []
|
|
3438
3591
|
|
|
3439
|
-
|
|
3592
|
+
try {
|
|
3593
|
+
await this.closeBackgroundJobsAdapter()
|
|
3594
|
+
} catch (error) {
|
|
3595
|
+
closeErrors.push(error instanceof Error ? error : new Error(String(error)))
|
|
3596
|
+
}
|
|
3440
3597
|
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3598
|
+
try {
|
|
3599
|
+
try {
|
|
3600
|
+
// Close dedicated advisory-lock connections first: they are spawned outside the
|
|
3601
|
+
// pools' tracked sets, so `pool.closeAll()` would not reach them and a lock held
|
|
3602
|
+
// by a runner torn down mid-pass would leak until the DB server's `wait_timeout`.
|
|
3603
|
+
// Still close the pools if this throws, so a stuck lock connection does not
|
|
3604
|
+
// leave the rest of the connections open.
|
|
3605
|
+
await this._closeAdvisoryLockConnections()
|
|
3606
|
+
} finally {
|
|
3607
|
+
for (const pool of Object.values(this.databasePools)) {
|
|
3608
|
+
if (!pool) continue
|
|
3609
|
+
|
|
3610
|
+
await pool.closeAll()
|
|
3611
|
+
|
|
3612
|
+
const PoolClass = /** @type {typeof import("./database/pool/base.js").default} */ (pool.constructor)
|
|
3613
|
+
constructors.add(PoolClass)
|
|
3614
|
+
}
|
|
3444
3615
|
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3616
|
+
for (const PoolClass of constructors) {
|
|
3617
|
+
PoolClass.clearGlobalConnections(this)
|
|
3618
|
+
}
|
|
3448
3619
|
|
|
3449
|
-
|
|
3620
|
+
this._frontendTenantSqliteLifecycle.reset()
|
|
3450
3621
|
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3622
|
+
// Allow full re-initialization after connections are closed.
|
|
3623
|
+
this._modelInitializationGeneration += 1
|
|
3624
|
+
this._modelsInitialized = false
|
|
3625
|
+
this._isInitialized = false
|
|
3626
|
+
}
|
|
3627
|
+
} catch (error) {
|
|
3628
|
+
closeErrors.push(error instanceof Error ? error : new Error(String(error)))
|
|
3455
3629
|
}
|
|
3630
|
+
|
|
3631
|
+
if (closeErrors.length === 1) throw closeErrors[0]
|
|
3632
|
+
if (closeErrors.length > 1) throw new AggregateError(closeErrors, "Failed to close background-jobs and database resources")
|
|
3456
3633
|
})()
|
|
3457
3634
|
|
|
3458
3635
|
try {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
import BackgroundJobsAdapterClient from "../background-jobs/adapter-client.js"
|
|
3
4
|
import {validateTimeZone} from "../time-zone.js"
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -575,6 +576,25 @@ export default class VelociousEnvironmentHandlerBase {
|
|
|
575
576
|
return
|
|
576
577
|
}
|
|
577
578
|
|
|
579
|
+
/**
|
|
580
|
+
* Creates the environment's default persistence adapter.
|
|
581
|
+
* @abstract
|
|
582
|
+
* @param {{configuration: import("../configuration.js").default}} _args - Adapter options.
|
|
583
|
+
* @returns {import("../background-jobs/adapter.js").default} - Default adapter.
|
|
584
|
+
*/
|
|
585
|
+
createBackgroundJobsAdapter(_args) {
|
|
586
|
+
throw new Error("This environment requires an explicit backgroundJobs.adapter")
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Creates the platform-neutral producer path for an explicit adapter.
|
|
591
|
+
* @param {{configuration: import("../configuration.js").default}} args - Client options.
|
|
592
|
+
* @returns {import("../background-jobs/types.js").BackgroundJobsProducer} - Adapter-backed producer.
|
|
593
|
+
*/
|
|
594
|
+
backgroundJobsClient(args) {
|
|
595
|
+
return new BackgroundJobsAdapterClient(args)
|
|
596
|
+
}
|
|
597
|
+
|
|
578
598
|
/**
|
|
579
599
|
* Runs require command.
|
|
580
600
|
* @abstract
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import "../database/annotations-async-hooks.js"
|
|
4
4
|
import Base from "./base.js"
|
|
5
|
+
import BackgroundJobsClient from "../background-jobs/client.js"
|
|
6
|
+
import SqlBackgroundJobsAdapter from "../background-jobs/sql-adapter.js"
|
|
5
7
|
import CliCommandsDestroyMigration from "./node/cli/commands/destroy/migration.js"
|
|
6
8
|
import CliCommandsInit from "./node/cli/commands/init.js"
|
|
7
9
|
import CliCommandsGenerateBaseModels from "./node/cli/commands/generate/base-models.js"
|
|
@@ -65,6 +67,25 @@ function pathWithinAllowedPrefixes(filePath, allowedPathPrefixes) {
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
export default class VelociousEnvironmentHandlerNode extends Base{
|
|
70
|
+
/**
|
|
71
|
+
* Creates the built-in SQL persistence adapter.
|
|
72
|
+
* @param {{configuration: import("../configuration.js").default}} args - Adapter options.
|
|
73
|
+
* @returns {SqlBackgroundJobsAdapter} - SQL adapter.
|
|
74
|
+
*/
|
|
75
|
+
createBackgroundJobsAdapter({configuration}) {
|
|
76
|
+
return new SqlBackgroundJobsAdapter({configuration})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Preserves the Node TCP producer and main-process wake-up path. The main
|
|
81
|
+
* owns the configured persistence adapter; Node producers never bypass it.
|
|
82
|
+
* @param {{configuration: import("../configuration.js").default}} args - Client options.
|
|
83
|
+
* @returns {import("../background-jobs/types.js").BackgroundJobsProducer} - Producer client.
|
|
84
|
+
*/
|
|
85
|
+
backgroundJobsClient({configuration}) {
|
|
86
|
+
return new BackgroundJobsClient({configuration})
|
|
87
|
+
}
|
|
88
|
+
|
|
68
89
|
/**
|
|
69
90
|
* Gives concurrent shared-transaction child jobs independent proxy sessions.
|
|
70
91
|
* A configured single-connection pool shares mutable transaction state between
|
|
@@ -1100,26 +1121,12 @@ export default class VelociousEnvironmentHandlerNode extends Base{
|
|
|
1100
1121
|
* @returns {Promise<void>} - Resolves when complete.
|
|
1101
1122
|
*/
|
|
1102
1123
|
async ensureFrameworkSchema({dbs}) {
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
// part of this migrate operation. When it isn't among the migrated set — e.g.
|
|
1110
|
-
// `db:tenants:migrate <tenant>`, which migrates only tenant databases — the
|
|
1111
|
-
// framework store lives elsewhere (typically the default DB) and was already
|
|
1112
|
-
// ensured by the plain `db:migrate` that precedes it. Reaching into it here would
|
|
1113
|
-
// open a fresh connection to that shared database once per tenant worker for
|
|
1114
|
-
// schema work that is already applied. So skip when the framework DB isn't in
|
|
1115
|
-
// this set; the runtime store still creates it lazily if a plain migrate never
|
|
1116
|
-
// ran. Queue-cap reconciliation never runs on this path at all — it belongs to
|
|
1117
|
-
// main-process startup (`BackgroundJobsStore#reconcileQueueConcurrency`).
|
|
1118
|
-
if (!frameworkDb) return
|
|
1119
|
-
|
|
1120
|
-
// Reuse the connection db:migrate already holds for this database; opening a
|
|
1121
|
-
// nested checkout would deadlock a database whose pool is capped at one connection.
|
|
1122
|
-
await store.ensureSchema(frameworkDb)
|
|
1124
|
+
// Migration passes its already checked-out DB into the adapter hook. Do not
|
|
1125
|
+
// run runtime readiness here: SQL readiness would open a nested checkout and
|
|
1126
|
+
// can deadlock a single-connection migration pool.
|
|
1127
|
+
const adapter = this.getConfiguration().getBackgroundJobsAdapter()
|
|
1128
|
+
|
|
1129
|
+
await adapter.ensureFrameworkSchema({dbs})
|
|
1123
1130
|
}
|
|
1124
1131
|
|
|
1125
1132
|
/**
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import BackgroundJobsStore from "../background-jobs/store.js"
|
|
4
3
|
import Configuration from "../configuration.js"
|
|
5
4
|
import VelociousJob from "../background-jobs/job.js"
|
|
6
5
|
|
|
@@ -59,9 +58,9 @@ export default class PruneTerminalBackgroundJobsJob extends VelociousJob {
|
|
|
59
58
|
async perform() {
|
|
60
59
|
const configuration = Configuration.current()
|
|
61
60
|
const config = configuration.getBackgroundJobsConfig()
|
|
62
|
-
const
|
|
61
|
+
const adapter = await configuration.acquireReadyBackgroundJobsAdapter()
|
|
63
62
|
|
|
64
|
-
await
|
|
63
|
+
await adapter.pruneTerminalJobs({
|
|
65
64
|
completedTtlMs: config.retention.completedTtlMs,
|
|
66
65
|
failedTtlMs: config.retention.failedTtlMs,
|
|
67
66
|
batchSize: config.retention.batchSize
|