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
package/build/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
|
|
@@ -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==
|