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.
Files changed (90) hide show
  1. package/README.md +12 -1
  2. package/build/background-jobs/adapter-client.js +45 -0
  3. package/build/background-jobs/adapter.js +139 -0
  4. package/build/background-jobs/job-registry.js +1 -1
  5. package/build/background-jobs/job.js +20 -129
  6. package/build/background-jobs/main.js +71 -28
  7. package/build/background-jobs/perform-job.js +23 -0
  8. package/build/background-jobs/platform-job.js +156 -0
  9. package/build/background-jobs/runtime.js +156 -0
  10. package/build/background-jobs/sql-adapter.js +20 -0
  11. package/build/background-jobs/store.js +3 -1
  12. package/build/background-jobs/types.js +10 -0
  13. package/build/background-jobs/web/controller.js +5 -2
  14. package/build/background-jobs/worker.js +6 -8
  15. package/build/configuration-types.js +5 -0
  16. package/build/configuration.js +217 -40
  17. package/build/environment-handlers/base.js +20 -0
  18. package/build/environment-handlers/node.js +27 -20
  19. package/build/jobs/prune-terminal-background-jobs.js +2 -3
  20. package/build/src/background-jobs/adapter-client.d.ts +41 -0
  21. package/build/src/background-jobs/adapter-client.d.ts.map +1 -0
  22. package/build/src/background-jobs/adapter-client.js +39 -0
  23. package/build/src/background-jobs/adapter.d.ts +165 -0
  24. package/build/src/background-jobs/adapter.d.ts.map +1 -0
  25. package/build/src/background-jobs/adapter.js +121 -0
  26. package/build/src/background-jobs/job-registry.d.ts +1 -1
  27. package/build/src/background-jobs/job-registry.d.ts.map +1 -1
  28. package/build/src/background-jobs/job-registry.js +2 -2
  29. package/build/src/background-jobs/job.d.ts +6 -69
  30. package/build/src/background-jobs/job.d.ts.map +1 -1
  31. package/build/src/background-jobs/job.js +17 -116
  32. package/build/src/background-jobs/main.d.ts +13 -3
  33. package/build/src/background-jobs/main.d.ts.map +1 -1
  34. package/build/src/background-jobs/main.js +66 -30
  35. package/build/src/background-jobs/perform-job.d.ts +16 -0
  36. package/build/src/background-jobs/perform-job.d.ts.map +1 -0
  37. package/build/src/background-jobs/perform-job.js +22 -0
  38. package/build/src/background-jobs/platform-job.d.ts +110 -0
  39. package/build/src/background-jobs/platform-job.d.ts.map +1 -0
  40. package/build/src/background-jobs/platform-job.js +138 -0
  41. package/build/src/background-jobs/runtime.d.ts +78 -0
  42. package/build/src/background-jobs/runtime.d.ts.map +1 -0
  43. package/build/src/background-jobs/runtime.js +132 -0
  44. package/build/src/background-jobs/sql-adapter.d.ts +13 -0
  45. package/build/src/background-jobs/sql-adapter.d.ts.map +1 -0
  46. package/build/src/background-jobs/sql-adapter.js +18 -0
  47. package/build/src/background-jobs/store.d.ts +2 -1
  48. package/build/src/background-jobs/store.d.ts.map +1 -1
  49. package/build/src/background-jobs/store.js +4 -2
  50. package/build/src/background-jobs/types.d.ts +41 -0
  51. package/build/src/background-jobs/types.d.ts.map +1 -1
  52. package/build/src/background-jobs/types.js +11 -1
  53. package/build/src/background-jobs/web/controller.d.ts.map +1 -1
  54. package/build/src/background-jobs/web/controller.js +5 -3
  55. package/build/src/background-jobs/worker.d.ts.map +1 -1
  56. package/build/src/background-jobs/worker.js +7 -8
  57. package/build/src/configuration-types.d.ts +16 -0
  58. package/build/src/configuration-types.d.ts.map +1 -1
  59. package/build/src/configuration-types.js +5 -1
  60. package/build/src/configuration.d.ts +51 -2
  61. package/build/src/configuration.d.ts.map +1 -1
  62. package/build/src/configuration.js +202 -39
  63. package/build/src/environment-handlers/base.d.ts +17 -0
  64. package/build/src/environment-handlers/base.d.ts.map +1 -1
  65. package/build/src/environment-handlers/base.js +19 -1
  66. package/build/src/environment-handlers/node.d.ts +18 -0
  67. package/build/src/environment-handlers/node.d.ts.map +1 -1
  68. package/build/src/environment-handlers/node.js +25 -20
  69. package/build/src/jobs/prune-terminal-background-jobs.d.ts.map +1 -1
  70. package/build/src/jobs/prune-terminal-background-jobs.js +3 -4
  71. package/build/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +1 -1
  73. package/src/background-jobs/adapter-client.js +45 -0
  74. package/src/background-jobs/adapter.js +139 -0
  75. package/src/background-jobs/job-registry.js +1 -1
  76. package/src/background-jobs/job.js +20 -129
  77. package/src/background-jobs/main.js +71 -28
  78. package/src/background-jobs/perform-job.js +23 -0
  79. package/src/background-jobs/platform-job.js +156 -0
  80. package/src/background-jobs/runtime.js +156 -0
  81. package/src/background-jobs/sql-adapter.js +20 -0
  82. package/src/background-jobs/store.js +3 -1
  83. package/src/background-jobs/types.js +10 -0
  84. package/src/background-jobs/web/controller.js +5 -2
  85. package/src/background-jobs/worker.js +6 -8
  86. package/src/configuration-types.js +5 -0
  87. package/src/configuration.js +217 -40
  88. package/src/environment-handlers/base.js +20 -0
  89. package/src/environment-handlers/node.js +27 -20
  90. package/src/jobs/prune-terminal-background-jobs.js +2 -3
@@ -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.env.VELOCIOUS_ENV || process.env.NODE_ENV || "development"
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 envHost = process.env.VELOCIOUS_BACKGROUND_JOBS_HOST
1448
- const envPortRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_PORT
1449
- const envDatabaseIdentifier = process.env.VELOCIOUS_BACKGROUND_JOBS_DATABASE_IDENTIFIER
1450
- const envMaxConcurrentForkedRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_MAX_CONCURRENT_FORKED_JOBS
1451
- const envMaxConcurrentRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_MAX_CONCURRENT_INLINE_JOBS
1452
- const envPooledRunnerCountRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_COUNT
1453
- const envPooledRunnerConcurrencyRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_CONCURRENCY
1454
- const envPooledRunnerMaxJobsRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_JOBS
1455
- const envPooledRunnerMaxRssBytesRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_RSS_BYTES
1456
- const envPooledRunnerMaxLifetimeMsRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POOLED_RUNNER_MAX_LIFETIME_MS
1457
- const envDispatchStrategy = process.env.VELOCIOUS_BACKGROUND_JOBS_DISPATCH_STRATEGY
1458
- const envPollIntervalRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POLL_INTERVAL_MS
1459
- const envJobTimeoutRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_JOB_TIMEOUT_MS
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.env.VELOCIOUS_SKIP_DUMMY_MODEL_INITIALIZATION === "1"
2103
- && process.env.VELOCIOUS_BROWSER_TESTS === "true"
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
- try {
3429
- // Close dedicated advisory-lock connections first: they are spawned outside the
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
- await pool.closeAll()
3592
+ try {
3593
+ await this.closeBackgroundJobsAdapter()
3594
+ } catch (error) {
3595
+ closeErrors.push(error instanceof Error ? error : new Error(String(error)))
3596
+ }
3440
3597
 
3441
- const PoolClass = /** @type {typeof import("./database/pool/base.js").default} */ (pool.constructor)
3442
- constructors.add(PoolClass)
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
- for (const PoolClass of constructors) {
3446
- PoolClass.clearGlobalConnections(this)
3447
- }
3616
+ for (const PoolClass of constructors) {
3617
+ PoolClass.clearGlobalConnections(this)
3618
+ }
3448
3619
 
3449
- this._frontendTenantSqliteLifecycle.reset()
3620
+ this._frontendTenantSqliteLifecycle.reset()
3450
3621
 
3451
- // Allow full re-initialization after connections are closed.
3452
- this._modelInitializationGeneration += 1
3453
- this._modelsInitialized = false
3454
- this._isInitialized = false
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
- const {default: BackgroundJobsStore} = await import("../background-jobs/store.js")
1104
- const store = new BackgroundJobsStore({configuration: this.getConfiguration()})
1105
- const databaseIdentifier = store.getDatabaseIdentifier() ?? "default"
1106
- const frameworkDb = dbs[databaseIdentifier]
1107
-
1108
- // Only ensure the framework schema when the background-jobs database is actually
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 store = new BackgroundJobsStore({configuration, databaseIdentifier: config.databaseIdentifier})
61
+ const adapter = await configuration.acquireReadyBackgroundJobsAdapter()
63
62
 
64
- await store.pruneTerminalJobs({
63
+ await adapter.pruneTerminalJobs({
65
64
  completedTtlMs: config.retention.completedTtlMs,
66
65
  failedTtlMs: config.retention.failedTtlMs,
67
66
  batchSize: config.retention.batchSize
@@ -0,0 +1,41 @@
1
+ /** Platform-neutral producer client for a configured adapter. */
2
+ export default class BackgroundJobsAdapterClient {
3
+ configuration: import("../configuration.js").default;
4
+ /**
5
+ * Creates an adapter-backed producer.
6
+ * @param {{configuration: import("../configuration.js").default}} args - Client options.
7
+ */
8
+ constructor({ configuration }: {
9
+ configuration: import("../configuration.js").default;
10
+ });
11
+ /**
12
+ * Enqueues a job through the configured adapter.
13
+ * @param {{jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: import("./types.js").BackgroundJobOptions}} args - Job request.
14
+ * @returns {Promise<string>} - Job id.
15
+ */
16
+ enqueue(args: {
17
+ jobName: string;
18
+ args: Array<ReturnType<typeof JSON.parse>>;
19
+ options?: import("./types.js").BackgroundJobOptions;
20
+ }): Promise<string>;
21
+ /**
22
+ * Replaces a stable schedule through the configured adapter.
23
+ * @param {{scheduleKey: string, jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: import("./types.js").BackgroundJobOptions}} args - Replacement request.
24
+ * @returns {Promise<import("./types.js").BackgroundJobReplacementResult>} - Replacement result.
25
+ */
26
+ replaceScheduled(args: {
27
+ scheduleKey: string;
28
+ jobName: string;
29
+ args: Array<ReturnType<typeof JSON.parse>>;
30
+ options?: import("./types.js").BackgroundJobOptions;
31
+ }): Promise<import("./types.js").BackgroundJobReplacementResult>;
32
+ /**
33
+ * Cancels a stable schedule through the configured adapter.
34
+ * @param {{scheduleKey: string}} args - Cancellation request.
35
+ * @returns {Promise<import("./types.js").BackgroundJobCancellationResult>} - Cancellation result.
36
+ */
37
+ cancelScheduled({ scheduleKey }: {
38
+ scheduleKey: string;
39
+ }): Promise<import("./types.js").BackgroundJobCancellationResult>;
40
+ }
41
+ //# sourceMappingURL=adapter-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-client.d.ts","sourceRoot":"","sources":["../../../src/background-jobs/adapter-client.js"],"names":[],"mappings":"AAEA,iEAAiE;AACjE,MAAM,CAAC,OAAO,OAAO,2BAA2B;IAMvC,aAAa;IALpB;;;OAGG;IACH,YAAY,EAAC,aAAa,EAAC,EAFhB;QAAC,aAAa,EAAE,OAAO,qBAAqB,EAAE,OAAO,CAAA;KAErC,EAE1B;IAED;;;;OAIG;IACG,OAAO,CAAC,IAAI,EAHP;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,oBAAoB,CAAA;KAG1G,GAFL,OAAO,CAAC,MAAM,CAAC,CAM3B;IAED;;;;OAIG;IACG,gBAAgB,CAAC,IAAI,EAHhB;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,oBAAoB,CAAA;KAGtH,GAFd,OAAO,CAAC,OAAO,YAAY,EAAE,8BAA8B,CAAC,CAMxE;IAED;;;;OAIG;IACG,eAAe,CAAC,EAAC,WAAW,EAAC,EAHxB;QAAC,WAAW,EAAE,MAAM,CAAA;KAGI,GAFtB,OAAO,CAAC,OAAO,YAAY,EAAE,+BAA+B,CAAC,CAMzE;CACF"}
@@ -0,0 +1,39 @@
1
+ // @ts-check
2
+ /** Platform-neutral producer client for a configured adapter. */
3
+ export default class BackgroundJobsAdapterClient {
4
+ /**
5
+ * Creates an adapter-backed producer.
6
+ * @param {{configuration: import("../configuration.js").default}} args - Client options.
7
+ */
8
+ constructor({ configuration }) {
9
+ this.configuration = configuration;
10
+ }
11
+ /**
12
+ * Enqueues a job through the configured adapter.
13
+ * @param {{jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: import("./types.js").BackgroundJobOptions}} args - Job request.
14
+ * @returns {Promise<string>} - Job id.
15
+ */
16
+ async enqueue(args) {
17
+ const adapter = await this.configuration.acquireReadyBackgroundJobsAdapter();
18
+ return await adapter.enqueue(args);
19
+ }
20
+ /**
21
+ * Replaces a stable schedule through the configured adapter.
22
+ * @param {{scheduleKey: string, jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: import("./types.js").BackgroundJobOptions}} args - Replacement request.
23
+ * @returns {Promise<import("./types.js").BackgroundJobReplacementResult>} - Replacement result.
24
+ */
25
+ async replaceScheduled(args) {
26
+ const adapter = await this.configuration.acquireReadyBackgroundJobsAdapter();
27
+ return await adapter.replaceScheduled(args);
28
+ }
29
+ /**
30
+ * Cancels a stable schedule through the configured adapter.
31
+ * @param {{scheduleKey: string}} args - Cancellation request.
32
+ * @returns {Promise<import("./types.js").BackgroundJobCancellationResult>} - Cancellation result.
33
+ */
34
+ async cancelScheduled({ scheduleKey }) {
35
+ const adapter = await this.configuration.acquireReadyBackgroundJobsAdapter();
36
+ return await adapter.cancelScheduled(scheduleKey);
37
+ }
38
+ }
39
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRhcHRlci1jbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYmFja2dyb3VuZC1qb2JzL2FkYXB0ZXItY2xpZW50LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWixpRUFBaUU7QUFDakUsTUFBTSxDQUFDLE9BQU8sT0FBTywyQkFBMkI7SUFDOUM7OztPQUdHO0lBQ0gsWUFBWSxFQUFDLGFBQWEsRUFBQztRQUN6QixJQUFJLENBQUMsYUFBYSxHQUFHLGFBQWEsQ0FBQTtJQUNwQyxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSTtRQUNoQixNQUFNLE9BQU8sR0FBRyxNQUFNLElBQUksQ0FBQyxhQUFhLENBQUMsaUNBQWlDLEVBQUUsQ0FBQTtRQUU1RSxPQUFPLE1BQU0sT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQTtJQUNwQyxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJO1FBQ3pCLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FBQyxpQ0FBaUMsRUFBRSxDQUFBO1FBRTVFLE9BQU8sTUFBTSxPQUFPLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLENBQUE7SUFDN0MsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsZUFBZSxDQUFDLEVBQUMsV0FBVyxFQUFDO1FBQ2pDLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FBQyxpQ0FBaUMsRUFBRSxDQUFBO1FBRTVFLE9BQU8sTUFBTSxPQUFPLENBQUMsZUFBZSxDQUFDLFdBQVcsQ0FBQyxDQUFBO0lBQ25ELENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG4vKiogUGxhdGZvcm0tbmV1dHJhbCBwcm9kdWNlciBjbGllbnQgZm9yIGEgY29uZmlndXJlZCBhZGFwdGVyLiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgQmFja2dyb3VuZEpvYnNBZGFwdGVyQ2xpZW50IHtcbiAgLyoqXG4gICAqIENyZWF0ZXMgYW4gYWRhcHRlci1iYWNrZWQgcHJvZHVjZXIuXG4gICAqIEBwYXJhbSB7e2NvbmZpZ3VyYXRpb246IGltcG9ydChcIi4uL2NvbmZpZ3VyYXRpb24uanNcIikuZGVmYXVsdH19IGFyZ3MgLSBDbGllbnQgb3B0aW9ucy5cbiAgICovXG4gIGNvbnN0cnVjdG9yKHtjb25maWd1cmF0aW9ufSkge1xuICAgIHRoaXMuY29uZmlndXJhdGlvbiA9IGNvbmZpZ3VyYXRpb25cbiAgfVxuXG4gIC8qKlxuICAgKiBFbnF1ZXVlcyBhIGpvYiB0aHJvdWdoIHRoZSBjb25maWd1cmVkIGFkYXB0ZXIuXG4gICAqIEBwYXJhbSB7e2pvYk5hbWU6IHN0cmluZywgYXJnczogQXJyYXk8UmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT4+LCBvcHRpb25zPzogaW1wb3J0KFwiLi90eXBlcy5qc1wiKS5CYWNrZ3JvdW5kSm9iT3B0aW9uc319IGFyZ3MgLSBKb2IgcmVxdWVzdC5cbiAgICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gLSBKb2IgaWQuXG4gICAqL1xuICBhc3luYyBlbnF1ZXVlKGFyZ3MpIHtcbiAgICBjb25zdCBhZGFwdGVyID0gYXdhaXQgdGhpcy5jb25maWd1cmF0aW9uLmFjcXVpcmVSZWFkeUJhY2tncm91bmRKb2JzQWRhcHRlcigpXG5cbiAgICByZXR1cm4gYXdhaXQgYWRhcHRlci5lbnF1ZXVlKGFyZ3MpXG4gIH1cblxuICAvKipcbiAgICogUmVwbGFjZXMgYSBzdGFibGUgc2NoZWR1bGUgdGhyb3VnaCB0aGUgY29uZmlndXJlZCBhZGFwdGVyLlxuICAgKiBAcGFyYW0ge3tzY2hlZHVsZUtleTogc3RyaW5nLCBqb2JOYW1lOiBzdHJpbmcsIGFyZ3M6IEFycmF5PFJldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+Piwgb3B0aW9ucz86IGltcG9ydChcIi4vdHlwZXMuanNcIikuQmFja2dyb3VuZEpvYk9wdGlvbnN9fSBhcmdzIC0gUmVwbGFjZW1lbnQgcmVxdWVzdC5cbiAgICogQHJldHVybnMge1Byb21pc2U8aW1wb3J0KFwiLi90eXBlcy5qc1wiKS5CYWNrZ3JvdW5kSm9iUmVwbGFjZW1lbnRSZXN1bHQ+fSAtIFJlcGxhY2VtZW50IHJlc3VsdC5cbiAgICovXG4gIGFzeW5jIHJlcGxhY2VTY2hlZHVsZWQoYXJncykge1xuICAgIGNvbnN0IGFkYXB0ZXIgPSBhd2FpdCB0aGlzLmNvbmZpZ3VyYXRpb24uYWNxdWlyZVJlYWR5QmFja2dyb3VuZEpvYnNBZGFwdGVyKClcblxuICAgIHJldHVybiBhd2FpdCBhZGFwdGVyLnJlcGxhY2VTY2hlZHVsZWQoYXJncylcbiAgfVxuXG4gIC8qKlxuICAgKiBDYW5jZWxzIGEgc3RhYmxlIHNjaGVkdWxlIHRocm91Z2ggdGhlIGNvbmZpZ3VyZWQgYWRhcHRlci5cbiAgICogQHBhcmFtIHt7c2NoZWR1bGVLZXk6IHN0cmluZ319IGFyZ3MgLSBDYW5jZWxsYXRpb24gcmVxdWVzdC5cbiAgICogQHJldHVybnMge1Byb21pc2U8aW1wb3J0KFwiLi90eXBlcy5qc1wiKS5CYWNrZ3JvdW5kSm9iQ2FuY2VsbGF0aW9uUmVzdWx0Pn0gLSBDYW5jZWxsYXRpb24gcmVzdWx0LlxuICAgKi9cbiAgYXN5bmMgY2FuY2VsU2NoZWR1bGVkKHtzY2hlZHVsZUtleX0pIHtcbiAgICBjb25zdCBhZGFwdGVyID0gYXdhaXQgdGhpcy5jb25maWd1cmF0aW9uLmFjcXVpcmVSZWFkeUJhY2tncm91bmRKb2JzQWRhcHRlcigpXG5cbiAgICByZXR1cm4gYXdhaXQgYWRhcHRlci5jYW5jZWxTY2hlZHVsZWQoc2NoZWR1bGVLZXkpXG4gIH1cbn1cbiJdfQ==