velocious 1.0.658 → 1.0.659
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 +10 -0
- package/build/background-jobs/adapter.js +15 -0
- package/build/background-jobs/client.js +6 -2
- package/build/background-jobs/execution-context.js +47 -0
- package/build/background-jobs/job-runner.js +5 -2
- package/build/background-jobs/job.js +21 -2
- package/build/background-jobs/main.js +24 -10
- package/build/background-jobs/runtime.js +6 -2
- package/build/background-jobs/sql-adapter.js +6 -0
- package/build/background-jobs/store.js +289 -48
- package/build/background-jobs/types.js +11 -2
- package/build/background-jobs/worker.js +10 -7
- package/build/src/background-jobs/adapter.d.ts +19 -0
- package/build/src/background-jobs/adapter.d.ts.map +1 -1
- package/build/src/background-jobs/adapter.js +14 -1
- package/build/src/background-jobs/client.d.ts +5 -1
- package/build/src/background-jobs/client.d.ts.map +1 -1
- package/build/src/background-jobs/client.js +7 -3
- package/build/src/background-jobs/execution-context.d.ts +23 -0
- package/build/src/background-jobs/execution-context.d.ts.map +1 -0
- package/build/src/background-jobs/execution-context.js +42 -0
- package/build/src/background-jobs/job-runner.d.ts.map +1 -1
- package/build/src/background-jobs/job-runner.js +6 -3
- package/build/src/background-jobs/job.d.ts.map +1 -1
- package/build/src/background-jobs/job.js +21 -3
- package/build/src/background-jobs/main.d.ts +2 -2
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +26 -11
- package/build/src/background-jobs/runtime.d.ts +5 -1
- package/build/src/background-jobs/runtime.d.ts.map +1 -1
- package/build/src/background-jobs/runtime.js +7 -3
- package/build/src/background-jobs/sql-adapter.d.ts +5 -0
- package/build/src/background-jobs/sql-adapter.d.ts.map +1 -1
- package/build/src/background-jobs/sql-adapter.js +6 -1
- package/build/src/background-jobs/store.d.ts +119 -1
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +268 -47
- package/build/src/background-jobs/types.d.ts +33 -2
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +12 -3
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +11 -8
- package/package.json +1 -1
- package/src/background-jobs/adapter.js +15 -0
- package/src/background-jobs/client.js +6 -2
- package/src/background-jobs/execution-context.js +47 -0
- package/src/background-jobs/job-runner.js +5 -2
- package/src/background-jobs/job.js +21 -2
- package/src/background-jobs/main.js +24 -10
- package/src/background-jobs/runtime.js +6 -2
- package/src/background-jobs/sql-adapter.js +6 -0
- package/src/background-jobs/store.js +289 -48
- package/src/background-jobs/types.js +11 -2
- package/src/background-jobs/worker.js +10 -7
package/README.md
CHANGED
|
@@ -2442,6 +2442,16 @@ the new main. Deploy and HTTP/WebSocket drain completion are independent of this
|
|
|
2442
2442
|
potentially hours-long lifecycle. See [release-generation
|
|
2443
2443
|
draining](docs/background-jobs.md#release-generation-draining).
|
|
2444
2444
|
|
|
2445
|
+
Jobs that remain owned by a retired generation may still use the unchanged
|
|
2446
|
+
`performLater()` / `performLaterWithOptions()` APIs to create follow-up work.
|
|
2447
|
+
Velocious carries the producer's exact handoff through its asynchronous
|
|
2448
|
+
execution context and atomically validates that lease with insertion or queued
|
|
2449
|
+
deduplication. Each call has its own internal replay identity, so two identical
|
|
2450
|
+
calls create distinct jobs unless the caller explicitly requests queued
|
|
2451
|
+
deduplication or durable idempotency. The retired main commits and wakes the
|
|
2452
|
+
queue but never dispatches the follow-up; the active generation owns that work.
|
|
2453
|
+
Ordinary retired enqueue, replace, and cancel requests remain rejected.
|
|
2454
|
+
|
|
2445
2455
|
Velocious provides the opt-in generation protocol; production still requires a
|
|
2446
2456
|
supervisor that preserves old generation units and release pins, and a deploy
|
|
2447
2457
|
coordinator that retires the old generation before activating the healthy
|
|
@@ -14,6 +14,14 @@ export default class BackgroundJobsAdapter {
|
|
|
14
14
|
*/
|
|
15
15
|
supportsReleaseScopedGenerations() { return false }
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Declares atomic producer-handoff validation plus enqueue support. A
|
|
19
|
+
* generation-capable adapter must override this together with
|
|
20
|
+
* `enqueueFromOwnedHandoff`.
|
|
21
|
+
* @returns {boolean} - Whether atomic owned enqueue is supported.
|
|
22
|
+
*/
|
|
23
|
+
supportsOwnedEnqueueFromHandoff() { return false }
|
|
24
|
+
|
|
17
25
|
/**
|
|
18
26
|
* Ensures the adapter can accept work.
|
|
19
27
|
* @returns {Promise<void>} - Resolves when ready.
|
|
@@ -64,6 +72,13 @@ export default class BackgroundJobsAdapter {
|
|
|
64
72
|
*/
|
|
65
73
|
async enqueue(_args) { throw new Error("BackgroundJobsAdapter#enqueue is not implemented") }
|
|
66
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Atomically validates an exact producing handoff and enqueues its follow-up.
|
|
77
|
+
* @param {{jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: import("./types.js").BackgroundJobOptions, producerInvocationId?: string, producerProof: import("./types.js").BackgroundJobProducerProof}} _args - Owned enqueue request.
|
|
78
|
+
* @returns {Promise<string>} - Job id.
|
|
79
|
+
*/
|
|
80
|
+
async enqueueFromOwnedHandoff(_args) { throw new Error("BackgroundJobsAdapter#enqueueFromOwnedHandoff is not implemented") }
|
|
81
|
+
|
|
67
82
|
/**
|
|
68
83
|
* Replaces the owner of a stable schedule key.
|
|
69
84
|
* @param {{scheduleKey: string, jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: import("./types.js").BackgroundJobOptions}} _args - Replacement request.
|
|
@@ -44,9 +44,11 @@ export default class BackgroundJobsClient {
|
|
|
44
44
|
* @param {string} args.jobName - Job name.
|
|
45
45
|
* @param {Array<ReturnType<typeof JSON.parse>>} args.args - Job args.
|
|
46
46
|
* @param {import("./types.js").BackgroundJobOptions} [args.options] - Job options.
|
|
47
|
+
* @param {string} [args.producerInvocationId] - Stable identity for one owned enqueue invocation.
|
|
48
|
+
* @param {import("./types.js").BackgroundJobProducerProof} [args.producerProof] - Exact internal producer handoff.
|
|
47
49
|
* @returns {Promise<string>} - Job id.
|
|
48
50
|
*/
|
|
49
|
-
async enqueue({jobName, args, options}) {
|
|
51
|
+
async enqueue({jobName, args, options, producerInvocationId, producerProof}) {
|
|
50
52
|
const request = await this._request()
|
|
51
53
|
|
|
52
54
|
return await timeout({
|
|
@@ -59,7 +61,9 @@ export default class BackgroundJobsClient {
|
|
|
59
61
|
type: "enqueue",
|
|
60
62
|
jobName,
|
|
61
63
|
args,
|
|
62
|
-
options
|
|
64
|
+
options,
|
|
65
|
+
...(producerInvocationId ? {producerInvocationId} : {}),
|
|
66
|
+
...(producerProof ? {producerProof} : {})
|
|
63
67
|
})
|
|
64
68
|
},
|
|
65
69
|
onMessage: ({message, resolve, reject}) => {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks"
|
|
4
|
+
|
|
5
|
+
/** @type {AsyncLocalStorage<import("./types.js").BackgroundJobProducerProof>} */
|
|
6
|
+
const producerProofStorage = new AsyncLocalStorage()
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns the exact producer handoff for the current asynchronous job chain.
|
|
10
|
+
* @returns {import("./types.js").BackgroundJobProducerProof | undefined} - Current producer proof.
|
|
11
|
+
*/
|
|
12
|
+
export function currentBackgroundJobProducerProof() {
|
|
13
|
+
return producerProofStorage.getStore()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Runs one job-owned asynchronous chain with an immutable producer proof.
|
|
18
|
+
* @template T
|
|
19
|
+
* @param {import("./types.js").BackgroundJobProducerProof} producerProof - Exact producer handoff.
|
|
20
|
+
* @param {() => T} callback - Job-owned work.
|
|
21
|
+
* @returns {T} - Callback result.
|
|
22
|
+
*/
|
|
23
|
+
export function runWithBackgroundJobProducerProof(producerProof, callback) {
|
|
24
|
+
return producerProofStorage.run(Object.freeze({...producerProof}), callback)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Runs under a payload's exact lease when all fencing fields are present.
|
|
29
|
+
* Legacy payloads without a complete lease retain their existing behavior.
|
|
30
|
+
* @template T
|
|
31
|
+
* @param {import("./types.js").BackgroundJobPayload} payload - Persisted runner payload.
|
|
32
|
+
* @param {() => T} callback - Job-owned work.
|
|
33
|
+
* @returns {T} - Callback result.
|
|
34
|
+
*/
|
|
35
|
+
export function runWithBackgroundJobPayload(payload, callback) {
|
|
36
|
+
const handedOffAtMs = payload.handedOffAtMs
|
|
37
|
+
if (!payload.id || !payload.handoffId || !payload.workerId || handedOffAtMs === undefined || !Number.isSafeInteger(handedOffAtMs)) {
|
|
38
|
+
return callback()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return runWithBackgroundJobProducerProof({
|
|
42
|
+
handedOffAtMs,
|
|
43
|
+
handoffId: payload.handoffId,
|
|
44
|
+
jobId: payload.id,
|
|
45
|
+
workerId: payload.workerId
|
|
46
|
+
}, callback)
|
|
47
|
+
}
|
|
@@ -4,6 +4,7 @@ import configurationResolver from "../configuration-resolver.js"
|
|
|
4
4
|
import BackgroundJobRegistry from "./job-registry.js"
|
|
5
5
|
import BackgroundJobsStatusReporter from "./status-reporter.js"
|
|
6
6
|
import BackgroundJobRescheduleSignal from "./reschedule-signal.js"
|
|
7
|
+
import { runWithBackgroundJobPayload } from "./execution-context.js"
|
|
7
8
|
import { closeRunnerConnections } from "./runner-graceful-shutdown.js"
|
|
8
9
|
|
|
9
10
|
const BEACON_READY_TIMEOUT_MS = 5000
|
|
@@ -116,8 +117,10 @@ export default async function runJobPayload(payload, {closeConnections = true, m
|
|
|
116
117
|
|
|
117
118
|
try {
|
|
118
119
|
try {
|
|
119
|
-
await
|
|
120
|
-
await
|
|
120
|
+
await runWithBackgroundJobPayload(payload, async () => {
|
|
121
|
+
await configuration.withConnections({databaseIdentifiers: JobClass.databaseIdentifiers, name: `Background job runner: ${payload.jobName}`}, async () => {
|
|
122
|
+
await perform.apply(jobInstance, jobArgs)
|
|
123
|
+
})
|
|
121
124
|
})
|
|
122
125
|
} catch (error) {
|
|
123
126
|
if (error instanceof BackgroundJobRescheduleSignal) {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
import { randomUUID } from "node:crypto"
|
|
4
|
+
|
|
3
5
|
import configurationResolver from "../configuration-resolver.js"
|
|
6
|
+
import { currentBackgroundJobProducerProof } from "./execution-context.js"
|
|
4
7
|
import PlatformVelociousJob from "./platform-job.js"
|
|
5
8
|
import {
|
|
6
9
|
cancelScheduledBackgroundJobForConfiguration,
|
|
@@ -24,8 +27,16 @@ export default class VelociousJob extends PlatformVelociousJob {
|
|
|
24
27
|
static async performLater(...args) {
|
|
25
28
|
const configuration = await configurationResolver()
|
|
26
29
|
const {jobArgs, jobOptions} = this._splitArgsAndOptions(args)
|
|
30
|
+
const producerProof = currentBackgroundJobProducerProof()
|
|
27
31
|
|
|
28
|
-
return await enqueueBackgroundJobForConfiguration({
|
|
32
|
+
return await enqueueBackgroundJobForConfiguration({
|
|
33
|
+
configuration,
|
|
34
|
+
JobClass: this,
|
|
35
|
+
jobArgs,
|
|
36
|
+
jobOptions,
|
|
37
|
+
producerProof,
|
|
38
|
+
producerInvocationId: producerProof ? randomUUID() : undefined
|
|
39
|
+
})
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
/**
|
|
@@ -37,8 +48,16 @@ export default class VelociousJob extends PlatformVelociousJob {
|
|
|
37
48
|
*/
|
|
38
49
|
static async performLaterWithOptions({args, options}) {
|
|
39
50
|
const configuration = await configurationResolver()
|
|
51
|
+
const producerProof = currentBackgroundJobProducerProof()
|
|
40
52
|
|
|
41
|
-
return await enqueueBackgroundJobForConfiguration({
|
|
53
|
+
return await enqueueBackgroundJobForConfiguration({
|
|
54
|
+
configuration,
|
|
55
|
+
JobClass: this,
|
|
56
|
+
jobArgs: args,
|
|
57
|
+
jobOptions: options,
|
|
58
|
+
producerProof,
|
|
59
|
+
producerInvocationId: producerProof ? randomUUID() : undefined
|
|
60
|
+
})
|
|
42
61
|
}
|
|
43
62
|
|
|
44
63
|
/**
|
|
@@ -201,7 +201,7 @@ export default class BackgroundJobsMain {
|
|
|
201
201
|
this._pollTimer = undefined
|
|
202
202
|
/**
|
|
203
203
|
* Narrows the runtime value to the documented type.
|
|
204
|
-
* @type {ReturnType<typeof setTimeout> | undefined} */
|
|
204
|
+
* @type {ReturnType<typeof setTimeout> | number | undefined} */
|
|
205
205
|
this._scheduledTimer = undefined
|
|
206
206
|
/**
|
|
207
207
|
* Narrows the runtime value to the documented type.
|
|
@@ -291,6 +291,9 @@ export default class BackgroundJobsMain {
|
|
|
291
291
|
if (this.generationId && !this.adapter.supportsReleaseScopedGenerations()) {
|
|
292
292
|
throw new Error("The configured background jobs adapter does not support release-scoped generations")
|
|
293
293
|
}
|
|
294
|
+
if (this.generationId && !this.adapter.supportsOwnedEnqueueFromHandoff()) {
|
|
295
|
+
throw new Error("The configured background jobs adapter does not support atomic owned-handoff enqueue")
|
|
296
|
+
}
|
|
294
297
|
|
|
295
298
|
if (!this.generationId || this.initialGenerationState !== "candidate") {
|
|
296
299
|
this.startupHandoffSnapshot = await this._generationOwnedHandoffSnapshot()
|
|
@@ -411,7 +414,7 @@ export default class BackgroundJobsMain {
|
|
|
411
414
|
* @returns {void} */
|
|
412
415
|
_clearTimers() {
|
|
413
416
|
if (this._pollTimer) clearInterval(this._pollTimer)
|
|
414
|
-
if (this._scheduledTimer) clearTimeout(this._scheduledTimer)
|
|
417
|
+
if (this._scheduledTimer) this.clock.clearTimeout(this._scheduledTimer)
|
|
415
418
|
if (this._errorRetryTimer) clearTimeout(this._errorRetryTimer)
|
|
416
419
|
if (this._orphanTimer) clearInterval(this._orphanTimer)
|
|
417
420
|
if (this._workerStaleTimer) clearInterval(this._workerStaleTimer)
|
|
@@ -670,7 +673,7 @@ export default class BackgroundJobsMain {
|
|
|
670
673
|
/** Clears timers that can initiate new global dispatch or schedule work. */
|
|
671
674
|
_clearDispatchTimers() {
|
|
672
675
|
if (this._pollTimer) clearInterval(this._pollTimer)
|
|
673
|
-
if (this._scheduledTimer) clearTimeout(this._scheduledTimer)
|
|
676
|
+
if (this._scheduledTimer) this.clock.clearTimeout(this._scheduledTimer)
|
|
674
677
|
if (this._errorRetryTimer) clearTimeout(this._errorRetryTimer)
|
|
675
678
|
this._pollTimer = undefined
|
|
676
679
|
this._scheduledTimer = undefined
|
|
@@ -1151,10 +1154,10 @@ export default class BackgroundJobsMain {
|
|
|
1151
1154
|
*/
|
|
1152
1155
|
async _handleClientSocketMessage({jsonSocket, message}) {
|
|
1153
1156
|
if (this.generationId && (this.lifecycleState === "retiring" || this.lifecycleState === "retired")) {
|
|
1154
|
-
if (message?.type === "enqueue") jsonSocket.send({type: "enqueue-error", error: "Background jobs generation is retired"})
|
|
1157
|
+
if (message?.type === "enqueue" && !message.producerProof) jsonSocket.send({type: "enqueue-error", error: "Background jobs generation is retired"})
|
|
1155
1158
|
if (message?.type === "replace-scheduled") jsonSocket.send({type: "replace-scheduled-error", error: "Background jobs generation is retired"})
|
|
1156
1159
|
if (message?.type === "cancel-scheduled") jsonSocket.send({type: "cancel-scheduled-error", error: "Background jobs generation is retired"})
|
|
1157
|
-
return
|
|
1160
|
+
if (message?.type !== "enqueue" || !message.producerProof) return
|
|
1158
1161
|
}
|
|
1159
1162
|
|
|
1160
1163
|
if (message?.type === "enqueue") {
|
|
@@ -1473,15 +1476,26 @@ export default class BackgroundJobsMain {
|
|
|
1473
1476
|
*/
|
|
1474
1477
|
async _handleEnqueue({jsonSocket, message}) {
|
|
1475
1478
|
try {
|
|
1476
|
-
|
|
1479
|
+
if (this.generationId
|
|
1480
|
+
&& typeof message.producerProof?.workerId === "string"
|
|
1481
|
+
&& !workerIdBelongsToGeneration({generationId: this.generationId, workerId: message.producerProof.workerId})) {
|
|
1482
|
+
throw VelociousError.safe("Background job producer handoff belongs to another generation.", {
|
|
1483
|
+
code: "background-job-producer-generation-mismatch"
|
|
1484
|
+
})
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
const request = {
|
|
1477
1488
|
jobName: message.jobName,
|
|
1478
1489
|
args: message.args || [],
|
|
1479
1490
|
options: message.options || {}
|
|
1480
|
-
}
|
|
1491
|
+
}
|
|
1492
|
+
const jobId = this.generationId && message.producerProof
|
|
1493
|
+
? await this.store.enqueueFromOwnedHandoff({...request, producerInvocationId: message.producerInvocationId, producerProof: message.producerProof})
|
|
1494
|
+
: await this.store.enqueue(request)
|
|
1481
1495
|
|
|
1482
1496
|
jsonSocket.send({type: "enqueued", jobId})
|
|
1483
1497
|
this._notifyEnqueued()
|
|
1484
|
-
await this._drain()
|
|
1498
|
+
if (this.lifecycleState === "active") await this._drain()
|
|
1485
1499
|
} catch (error) {
|
|
1486
1500
|
this._handleClientMutationError({
|
|
1487
1501
|
context: {jobName: message.jobName, stage: "background-job-enqueue"},
|
|
@@ -2295,7 +2309,7 @@ export default class BackgroundJobsMain {
|
|
|
2295
2309
|
*/
|
|
2296
2310
|
async _armScheduledTimer() {
|
|
2297
2311
|
if (this._scheduledTimer) {
|
|
2298
|
-
clearTimeout(this._scheduledTimer)
|
|
2312
|
+
this.clock.clearTimeout(this._scheduledTimer)
|
|
2299
2313
|
this._scheduledTimer = undefined
|
|
2300
2314
|
}
|
|
2301
2315
|
|
|
@@ -2318,7 +2332,7 @@ export default class BackgroundJobsMain {
|
|
|
2318
2332
|
|
|
2319
2333
|
if (typeof delay !== "number") return
|
|
2320
2334
|
|
|
2321
|
-
this._scheduledTimer = setTimeout(() => {
|
|
2335
|
+
this._scheduledTimer = this.clock.setTimeout(() => {
|
|
2322
2336
|
this._scheduledTimer = undefined
|
|
2323
2337
|
void this._drain()
|
|
2324
2338
|
}, delay)
|
|
@@ -49,9 +49,11 @@ export async function enqueueBackgroundJob({JobClass, jobArgs, jobOptions}) {
|
|
|
49
49
|
* @param {typeof import("./platform-job.js").default} args.JobClass - Job class.
|
|
50
50
|
* @param {Array<ReturnType<typeof JSON.parse>>} args.jobArgs - Job arguments.
|
|
51
51
|
* @param {import("./types.js").BackgroundJobOptions | undefined} args.jobOptions - Job options.
|
|
52
|
+
* @param {string} [args.producerInvocationId] - Stable identity for one owned enqueue invocation.
|
|
53
|
+
* @param {import("./types.js").BackgroundJobProducerProof} [args.producerProof] - Exact internal producer handoff.
|
|
52
54
|
* @returns {Promise<string>} - Durable job id or ephemeral inline performance id.
|
|
53
55
|
*/
|
|
54
|
-
export async function enqueueBackgroundJobForConfiguration({configuration, JobClass, jobArgs, jobOptions}) {
|
|
56
|
+
export async function enqueueBackgroundJobForConfiguration({configuration, JobClass, jobArgs, jobOptions, producerInvocationId, producerProof}) {
|
|
55
57
|
const resolvedJobOptions = JobClass._withJobContext({jobArgs, jobOptions})
|
|
56
58
|
|
|
57
59
|
if (configuration.getBackgroundJobsConfig().mode === "inline") {
|
|
@@ -83,7 +85,9 @@ export async function enqueueBackgroundJobForConfiguration({configuration, JobCl
|
|
|
83
85
|
return await client.enqueue({
|
|
84
86
|
jobName: JobClass.jobName(),
|
|
85
87
|
args: jobArgs,
|
|
86
|
-
options: resolvedJobOptions
|
|
88
|
+
options: resolvedJobOptions,
|
|
89
|
+
producerInvocationId,
|
|
90
|
+
producerProof
|
|
87
91
|
})
|
|
88
92
|
}
|
|
89
93
|
|
|
@@ -10,6 +10,12 @@ export default class SqlBackgroundJobsAdapter extends BackgroundJobsStore {
|
|
|
10
10
|
*/
|
|
11
11
|
supportsReleaseScopedGenerations() { return true }
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Declares atomic owned-handoff enqueue support.
|
|
15
|
+
* @returns {boolean} - The SQL transaction validates ownership and enqueues atomically.
|
|
16
|
+
*/
|
|
17
|
+
supportsOwnedEnqueueFromHandoff() { return true }
|
|
18
|
+
|
|
13
19
|
/**
|
|
14
20
|
* Ensures the built-in SQL schema during migration.
|
|
15
21
|
* @param {{dbs: Record<string, import("../database/drivers/base.js").default>}} args - Migrated databases.
|