velocious 1.0.617 → 1.0.618
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 +3 -1
- package/build/background-jobs/client.js +12 -3
- package/build/background-jobs/socket-request.js +4 -0
- package/build/database/migration/index.js +2 -1
- package/build/database/pool/async-tracked-multi-connection.js +78 -1
- package/build/database/pool/base.js +32 -0
- package/build/database/pool/single-multi-use.js +65 -4
- package/build/src/background-jobs/client.d.ts +4 -1
- package/build/src/background-jobs/client.d.ts.map +1 -1
- package/build/src/background-jobs/client.js +12 -4
- package/build/src/background-jobs/socket-request.d.ts.map +1 -1
- package/build/src/background-jobs/socket-request.js +4 -1
- package/build/src/database/migration/index.d.ts +3 -2
- package/build/src/database/migration/index.d.ts.map +1 -1
- package/build/src/database/migration/index.js +3 -2
- package/build/src/database/pool/async-tracked-multi-connection.d.ts +20 -0
- package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
- package/build/src/database/pool/async-tracked-multi-connection.js +75 -2
- package/build/src/database/pool/base.d.ts +25 -0
- package/build/src/database/pool/base.d.ts.map +1 -1
- package/build/src/database/pool/base.js +30 -1
- package/build/src/database/pool/single-multi-use.d.ts +30 -3
- package/build/src/database/pool/single-multi-use.d.ts.map +1 -1
- package/build/src/database/pool/single-multi-use.js +58 -5
- package/build/src/testing/shared-transaction-connection-coordinator.d.ts +5 -0
- package/build/src/testing/shared-transaction-connection-coordinator.d.ts.map +1 -1
- package/build/src/testing/shared-transaction-connection-coordinator.js +28 -4
- package/build/src/testing/test-runner.d.ts +75 -0
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +201 -10
- package/build/testing/shared-transaction-connection-coordinator.js +29 -3
- package/build/testing/test-runner.js +198 -9
- package/package.json +1 -1
- package/src/background-jobs/client.js +12 -3
- package/src/background-jobs/socket-request.js +4 -0
- package/src/database/migration/index.js +2 -1
- package/src/database/pool/async-tracked-multi-connection.js +78 -1
- package/src/database/pool/base.js +32 -0
- package/src/database/pool/single-multi-use.js +65 -4
- package/src/testing/shared-transaction-connection-coordinator.js +29 -3
- package/src/testing/test-runner.js +198 -9
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
/** @
|
|
3
|
+
/** @typedef {{coordinator: (callback: () => Promise<unknown>) => Promise<unknown>, ownedQueue: Promise<void>, owner: symbol}} CoordinatorRegistration */
|
|
4
|
+
|
|
5
|
+
/** @type {WeakMap<object, CoordinatorRegistration>} */
|
|
4
6
|
const coordinators = new WeakMap()
|
|
5
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Serializes sibling work that inherited one coordinator owner without re-entering the broker queue.
|
|
10
|
+
* @template T
|
|
11
|
+
* @param {CoordinatorRegistration} registration - Physical connection registration.
|
|
12
|
+
* @param {() => Promise<T>} callback - Owned operation.
|
|
13
|
+
* @returns {Promise<T>} - Operation result.
|
|
14
|
+
*/
|
|
15
|
+
async function coordinateOwnedSharedTransactionConnection(registration, callback) {
|
|
16
|
+
const previous = registration.ownedQueue
|
|
17
|
+
/**
|
|
18
|
+
* Releases the next owned sibling operation.
|
|
19
|
+
* @type {() => void}
|
|
20
|
+
*/
|
|
21
|
+
let release = () => {}
|
|
22
|
+
|
|
23
|
+
registration.ownedQueue = new Promise((resolve) => { release = resolve })
|
|
24
|
+
await previous
|
|
25
|
+
try {
|
|
26
|
+
return await callback()
|
|
27
|
+
} finally {
|
|
28
|
+
release()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
6
32
|
/**
|
|
7
33
|
* Registers test-only serialization owned by the active broker.
|
|
8
34
|
* @param {object} connection - Parent physical connection.
|
|
@@ -12,7 +38,7 @@ const coordinators = new WeakMap()
|
|
|
12
38
|
export function setSharedTransactionCoordinator(connection, coordinator) {
|
|
13
39
|
const owner = Symbol("shared-transaction-coordinator")
|
|
14
40
|
|
|
15
|
-
coordinators.set(connection, {coordinator, owner})
|
|
41
|
+
coordinators.set(connection, {coordinator, ownedQueue: Promise.resolve(), owner})
|
|
16
42
|
return owner
|
|
17
43
|
}
|
|
18
44
|
|
|
@@ -43,7 +69,7 @@ export async function coordinateSharedTransactionConnection(connection, callback
|
|
|
43
69
|
const environmentHandler = connection.configuration.getEnvironmentHandler()
|
|
44
70
|
|
|
45
71
|
if (environmentHandler.getSharedTransactionCoordinatorOwner(connection) === registration.owner) {
|
|
46
|
-
return await callback
|
|
72
|
+
return await coordinateOwnedSharedTransactionConnection(registration, callback)
|
|
47
73
|
}
|
|
48
74
|
|
|
49
75
|
return /** @type {T} */ (await registration.coordinator(async () => {
|
|
@@ -39,6 +39,7 @@ import { SHARED_TRANSACTION_BROKER_ENV } from "./shared-transaction-proxy-driver
|
|
|
39
39
|
* @property {string[] | string} [tags] - Tags for filtering.
|
|
40
40
|
* @property {number} [timeoutSeconds] - Timeout in seconds for the test.
|
|
41
41
|
* @property {string} [type] - Test type identifier.
|
|
42
|
+
* @property {(args: {databaseIdentifier: string, tenant: object}) => Promise<void>} [registerTransactionalTenant] - Registers one resolved tenant database transaction for this attempt.
|
|
42
43
|
*/
|
|
43
44
|
/**
|
|
44
45
|
* TestData type.
|
|
@@ -118,6 +119,18 @@ import { SHARED_TRANSACTION_BROKER_ENV } from "./shared-transaction-proxy-driver
|
|
|
118
119
|
* @property {boolean} environmentPublished - Whether child-process coordinates were published.
|
|
119
120
|
* @property {string | undefined} previousEnvironment - Environment value to restore after publication.
|
|
120
121
|
*/
|
|
122
|
+
/**
|
|
123
|
+
* TransactionalTenantRegistration type.
|
|
124
|
+
* @typedef {object} TransactionalTenantRegistration
|
|
125
|
+
* @property {Promise<{connection: import("../database/drivers/base.js").default | undefined, error: Error | undefined}> | undefined} [checkoutPromise] - Attempt-owned physical checkout outcome.
|
|
126
|
+
* @property {import("../database/drivers/base.js").default | undefined} connection - Attempt-owned physical connection once checkout resolves.
|
|
127
|
+
* @property {Promise<void> | undefined} [cleanupPromise] - Single cleanup operation shared by emergency and eventual lifecycle cleanup.
|
|
128
|
+
* @property {boolean | undefined} [discardOnCleanup] - Whether timeout emergency cleanup must quarantine this connection.
|
|
129
|
+
* @property {import("../database/pool/base.js").default} pool - Owning logical pool.
|
|
130
|
+
* @property {boolean} revoked - Whether this attempt may still publish the physical registration.
|
|
131
|
+
* @property {string} reuseKey - Resolved physical configuration identity.
|
|
132
|
+
* @property {import("../database/pool/base.js").TestSharedConnectionRegistration | undefined} sharedRegistration - Physical-key shared registration once published.
|
|
133
|
+
*/
|
|
121
134
|
|
|
122
135
|
/**
|
|
123
136
|
* Runs run with timeout.
|
|
@@ -641,6 +654,138 @@ export default class TestRunner {
|
|
|
641
654
|
}
|
|
642
655
|
}
|
|
643
656
|
|
|
657
|
+
/**
|
|
658
|
+
* Checks out and registers one physical tenant transaction for the current attempt.
|
|
659
|
+
* @param {{databaseIdentifier: string, tenant: object}} args - Logical identifier and tenant descriptor.
|
|
660
|
+
* @param {TransactionalTenantRegistration[]} registrations - Current attempt registrations.
|
|
661
|
+
* @returns {Promise<void>}
|
|
662
|
+
*/
|
|
663
|
+
async registerTransactionalTenant({databaseIdentifier, tenant, ...restArgs}, registrations) {
|
|
664
|
+
restArgsError(restArgs)
|
|
665
|
+
if (!databaseIdentifier) throw new Error("registerTransactionalTenant requires a databaseIdentifier")
|
|
666
|
+
if (!tenant) throw new Error("registerTransactionalTenant requires a tenant")
|
|
667
|
+
|
|
668
|
+
const configuration = this.getConfiguration()
|
|
669
|
+
const pool = configuration.getDatabasePool(databaseIdentifier)
|
|
670
|
+
const databaseConfiguration = configuration.resolveDatabaseConfiguration(databaseIdentifier, tenant)
|
|
671
|
+
if (!databaseConfiguration.tenantOnly) {
|
|
672
|
+
throw new Error(`registerTransactionalTenant requires a tenantOnly database: ${databaseIdentifier}`)
|
|
673
|
+
}
|
|
674
|
+
const reuseKey = pool.getConfigurationReuseKey(databaseConfiguration)
|
|
675
|
+
if (registrations.some((registration) => registration.pool === pool && registration.reuseKey === reuseKey)) return
|
|
676
|
+
|
|
677
|
+
/** @type {TransactionalTenantRegistration} */
|
|
678
|
+
const registration = {
|
|
679
|
+
connection: undefined,
|
|
680
|
+
pool,
|
|
681
|
+
reuseKey,
|
|
682
|
+
revoked: false,
|
|
683
|
+
sharedRegistration: undefined
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
registrations.push(registration)
|
|
687
|
+
registration.checkoutPromise = pool
|
|
688
|
+
.checkoutForConfiguration(databaseConfiguration, {name: "Transactional tenant test registration"})
|
|
689
|
+
.then(
|
|
690
|
+
(connection) => ({connection, error: undefined}),
|
|
691
|
+
(error) => ({
|
|
692
|
+
connection: undefined,
|
|
693
|
+
error: error instanceof Error ? error : new Error("Transactional tenant connection checkout failed", {cause: error})
|
|
694
|
+
})
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
try {
|
|
698
|
+
const checkoutOutcome = await registration.checkoutPromise
|
|
699
|
+
|
|
700
|
+
if (checkoutOutcome.error) throw checkoutOutcome.error
|
|
701
|
+
if (!checkoutOutcome.connection) throw new Error("Transactional tenant connection checkout returned no connection")
|
|
702
|
+
registration.connection = checkoutOutcome.connection
|
|
703
|
+
if (registration.revoked) throw new Error("Transactional tenant test registration attempt is no longer active")
|
|
704
|
+
|
|
705
|
+
await registration.connection.startTransaction()
|
|
706
|
+
if (registration.revoked) throw new Error("Transactional tenant test registration attempt is no longer active")
|
|
707
|
+
|
|
708
|
+
const sharedRegistration = pool.setTestSharedConnectionForConfiguration(registration.connection, reuseKey)
|
|
709
|
+
if (!sharedRegistration) throw new Error(`Database pool does not support transactional tenant test connections: ${databaseIdentifier}`)
|
|
710
|
+
registration.sharedRegistration = sharedRegistration
|
|
711
|
+
if (registration.revoked) {
|
|
712
|
+
pool.clearTestSharedConnection(sharedRegistration)
|
|
713
|
+
throw new Error("Transactional tenant test registration attempt is no longer active")
|
|
714
|
+
}
|
|
715
|
+
} catch (error) {
|
|
716
|
+
registration.revoked = true
|
|
717
|
+
try {
|
|
718
|
+
await this.cleanupTransactionalTenants([registration], {discard: registration.discardOnCleanup === true})
|
|
719
|
+
} catch (cleanupError) {
|
|
720
|
+
throw new AggregateError([error, cleanupError], "Failed to register and clean up a transactional tenant test connection", {cause: cleanupError})
|
|
721
|
+
}
|
|
722
|
+
throw error
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Revokes attempt registrations before rolling back and releasing their connections.
|
|
728
|
+
* @param {TransactionalTenantRegistration[]} registrations - Attempt registrations.
|
|
729
|
+
* @param {{discard?: boolean}} [options] - Whether connections must be discarded instead of returned to the pool.
|
|
730
|
+
* @returns {Promise<void>}
|
|
731
|
+
*/
|
|
732
|
+
async cleanupTransactionalTenants(registrations, {discard = false} = {}) {
|
|
733
|
+
for (const registration of registrations) {
|
|
734
|
+
registration.revoked = true
|
|
735
|
+
if (discard) registration.discardOnCleanup = true
|
|
736
|
+
if (registration.sharedRegistration) registration.pool.clearTestSharedConnection(registration.sharedRegistration)
|
|
737
|
+
}
|
|
738
|
+
const cleanupResults = await Promise.allSettled([...registrations].reverse().map((registration) => {
|
|
739
|
+
registration.cleanupPromise ??= this.cleanupTransactionalTenantRegistration(registration)
|
|
740
|
+
|
|
741
|
+
return registration.cleanupPromise
|
|
742
|
+
}))
|
|
743
|
+
const errors = cleanupResults
|
|
744
|
+
.filter((result) => result.status === "rejected")
|
|
745
|
+
.map((result) => result.reason)
|
|
746
|
+
|
|
747
|
+
if (errors.length === 1) throw errors[0]
|
|
748
|
+
if (errors.length > 1) throw new AggregateError(errors, "Failed to clean up transactional tenant test connections")
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Cleans one attempt registration exactly once, including a checkout that was still pending at revocation.
|
|
753
|
+
* @param {TransactionalTenantRegistration} registration - Attempt-owned registration.
|
|
754
|
+
* @returns {Promise<void>} - Resolves after rollback and release or quarantine.
|
|
755
|
+
*/
|
|
756
|
+
async cleanupTransactionalTenantRegistration(registration) {
|
|
757
|
+
let connection = registration.connection
|
|
758
|
+
|
|
759
|
+
if (!connection && registration.checkoutPromise) {
|
|
760
|
+
const checkoutOutcome = await registration.checkoutPromise
|
|
761
|
+
|
|
762
|
+
if (checkoutOutcome.error) return
|
|
763
|
+
connection = checkoutOutcome.connection
|
|
764
|
+
registration.connection = connection
|
|
765
|
+
}
|
|
766
|
+
if (!connection) return
|
|
767
|
+
|
|
768
|
+
const errors = []
|
|
769
|
+
|
|
770
|
+
try {
|
|
771
|
+
if (connection.insideTransaction()) await connection.rollbackTransaction()
|
|
772
|
+
} catch (error) {
|
|
773
|
+
errors.push(error)
|
|
774
|
+
} finally {
|
|
775
|
+
try {
|
|
776
|
+
if (registration.discardOnCleanup) {
|
|
777
|
+
await registration.pool.discard(connection)
|
|
778
|
+
} else {
|
|
779
|
+
await registration.pool.checkin(connection)
|
|
780
|
+
}
|
|
781
|
+
} catch (error) {
|
|
782
|
+
errors.push(error)
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
if (errors.length === 1) throw errors[0]
|
|
786
|
+
if (errors.length > 1) throw new AggregateError(errors, "Failed to clean up a transactional tenant test connection")
|
|
787
|
+
}
|
|
788
|
+
|
|
644
789
|
/**
|
|
645
790
|
* Selects the current non-tenant connections eligible for shared transaction work.
|
|
646
791
|
* @param {{transactionsOnly: boolean}} args - Selection options.
|
|
@@ -1014,6 +1159,29 @@ export default class TestRunner {
|
|
|
1014
1159
|
console.error(error)
|
|
1015
1160
|
}
|
|
1016
1161
|
|
|
1162
|
+
/**
|
|
1163
|
+
* Records a rollback/discard failure that settled after timeout cleanup moved on.
|
|
1164
|
+
* @param {unknown} reason - Detached cleanup rejection.
|
|
1165
|
+
* @returns {void}
|
|
1166
|
+
*/
|
|
1167
|
+
recordTransactionalTenantCleanupFailure(reason) {
|
|
1168
|
+
const error = reason instanceof Error ? reason : new Error(`Transactional tenant cleanup failed: ${String(reason)}`)
|
|
1169
|
+
const near = this._lastTestContext
|
|
1170
|
+
const attribution = near ? `, near test: ${near.fullDescription} (${near.filePath}:${near.line})` : ""
|
|
1171
|
+
|
|
1172
|
+
this._failedTests = (this._failedTests || 0) + 1
|
|
1173
|
+
this._failedTestDetails.push({
|
|
1174
|
+
fullDescription: `<transactional tenant emergency cleanup failure${attribution}>`,
|
|
1175
|
+
filePath: near ? near.filePath : "<test runner>",
|
|
1176
|
+
line: near ? near.line : 0,
|
|
1177
|
+
error,
|
|
1178
|
+
consoleOutput: undefined
|
|
1179
|
+
})
|
|
1180
|
+
|
|
1181
|
+
console.error(picocolors.red(`\n[test-runner] transactional tenant emergency cleanup failed after the bounded timeout grace.${attribution}`))
|
|
1182
|
+
console.error(error)
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1017
1185
|
async run() {
|
|
1018
1186
|
/**
|
|
1019
1187
|
* Handles a process-level unhandled rejection during the run.
|
|
@@ -1232,6 +1400,11 @@ export default class TestRunner {
|
|
|
1232
1400
|
let sharedTransactionBrokerRegistration
|
|
1233
1401
|
/** @type {SharedTransactionBrokerRegistration | undefined} */
|
|
1234
1402
|
let sharedTransactionBrokerPreparation
|
|
1403
|
+
/** @type {TransactionalTenantRegistration[]} */
|
|
1404
|
+
const transactionalTenantRegistrations = []
|
|
1405
|
+
testArgs.registerTransactionalTenant = async (args) => {
|
|
1406
|
+
await this.registerTransactionalTenant(args, transactionalTenantRegistrations)
|
|
1407
|
+
}
|
|
1235
1408
|
const stopConsoleCapture = this.startConsoleCapture({
|
|
1236
1409
|
passthrough: testConfig.consoleOutput === "live"
|
|
1237
1410
|
})
|
|
@@ -1324,15 +1497,19 @@ export default class TestRunner {
|
|
|
1324
1497
|
sharedTransactionBrokerRegistration = undefined
|
|
1325
1498
|
sharedTransactionBrokerPreparation = undefined
|
|
1326
1499
|
} finally {
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1500
|
+
try {
|
|
1501
|
+
for (const afterEachData of newAfterEaches) {
|
|
1502
|
+
await this.runProfileSpan({
|
|
1503
|
+
phase: "afterEach",
|
|
1504
|
+
declarationIndex: afterEachData.declarationIndex,
|
|
1505
|
+
declarationScopeId: afterEachData.declarationScopeId,
|
|
1506
|
+
filePath: afterEachData.ownerFilePath
|
|
1507
|
+
}, async () => {
|
|
1508
|
+
await afterEachData.callback({configuration: this.getConfiguration(), testArgs, testData})
|
|
1509
|
+
})
|
|
1510
|
+
}
|
|
1511
|
+
} finally {
|
|
1512
|
+
await this.cleanupTransactionalTenants(transactionalTenantRegistrations)
|
|
1336
1513
|
}
|
|
1337
1514
|
}
|
|
1338
1515
|
}
|
|
@@ -1401,6 +1578,18 @@ export default class TestRunner {
|
|
|
1401
1578
|
await this.stopSharedTransactionBroker(sharedTransactionBrokerRegistration || sharedTransactionBrokerPreparation)
|
|
1402
1579
|
sharedTransactionBrokerRegistration = undefined
|
|
1403
1580
|
sharedTransactionBrokerPreparation = undefined
|
|
1581
|
+
const emergencyCleanup = this.cleanupTransactionalTenants(transactionalTenantRegistrations, {discard: true})
|
|
1582
|
+
const emergencyCleanupSettled = await awaitSettledOrGrace(emergencyCleanup, timeoutMs ?? 60000)
|
|
1583
|
+
|
|
1584
|
+
if (emergencyCleanupSettled) {
|
|
1585
|
+
await emergencyCleanup
|
|
1586
|
+
} else {
|
|
1587
|
+
// The timed-out attempt must not block the runner indefinitely, but a
|
|
1588
|
+
// later rollback/discard failure still becomes a visible test failure.
|
|
1589
|
+
void emergencyCleanup.catch((cleanupError) => {
|
|
1590
|
+
this.recordTransactionalTenantCleanupFailure(cleanupError)
|
|
1591
|
+
})
|
|
1592
|
+
}
|
|
1404
1593
|
}
|
|
1405
1594
|
|
|
1406
1595
|
willRetry = retriesUsed < retryCount
|
package/package.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
import timeout from "awaitery/build/timeout.js"
|
|
3
4
|
import configurationResolver from "../configuration-resolver.js"
|
|
4
5
|
import BackgroundJobsSocketRequest from "./socket-request.js"
|
|
5
6
|
|
|
7
|
+
const DEFAULT_ENQUEUE_TIMEOUT_MS = 5000
|
|
8
|
+
|
|
6
9
|
export default class BackgroundJobsClient {
|
|
7
10
|
/**
|
|
8
11
|
* Runs constructor.
|
|
9
12
|
* @param {object} [args] - Options.
|
|
10
13
|
* @param {import("../configuration.js").default} [args.configuration] - Configuration.
|
|
14
|
+
* @param {number} [args.enqueueTimeoutMs] - Maximum time to wait for an enqueue acknowledgement in milliseconds (default: 5000).
|
|
11
15
|
*/
|
|
12
|
-
constructor({configuration} = {}) {
|
|
16
|
+
constructor({configuration, enqueueTimeoutMs = DEFAULT_ENQUEUE_TIMEOUT_MS} = {}) {
|
|
13
17
|
this.configurationPromise = configuration ? Promise.resolve(configuration) : configurationResolver()
|
|
18
|
+
this.enqueueTimeoutMs = enqueueTimeoutMs
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
/**
|
|
@@ -35,7 +40,11 @@ export default class BackgroundJobsClient {
|
|
|
35
40
|
async enqueue({jobName, args, options}) {
|
|
36
41
|
const request = await this._request()
|
|
37
42
|
|
|
38
|
-
return await
|
|
43
|
+
return await timeout({
|
|
44
|
+
errorMessage: `Background job enqueue acknowledgement timed out after ${this.enqueueTimeoutMs}ms`,
|
|
45
|
+
timeout: this.enqueueTimeoutMs
|
|
46
|
+
}, async ({control}) => await request.run({
|
|
47
|
+
signal: control.signal,
|
|
39
48
|
onConnect: (jsonSocket) => {
|
|
40
49
|
jsonSocket.send({
|
|
41
50
|
type: "enqueue",
|
|
@@ -54,7 +63,7 @@ export default class BackgroundJobsClient {
|
|
|
54
63
|
reject(new Error(message.error || "Failed to enqueue job"))
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
|
-
})
|
|
66
|
+
}))
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
/**
|
|
@@ -89,6 +89,10 @@ export default class BackgroundJobsSocketRequest {
|
|
|
89
89
|
finish({}, () => reject(error))
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
+
jsonSocket.on("close", () => {
|
|
93
|
+
finish({destroy: true}, () => reject(new Error("Background jobs socket closed before the request was acknowledged")))
|
|
94
|
+
})
|
|
95
|
+
|
|
92
96
|
/**
|
|
93
97
|
* Handles the socket response message.
|
|
94
98
|
* @param {import("./types.js").BackgroundJobSocketMessage} message - Socket message.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
/** @typedef {import("../table-data/table-column.js").TableColumnArgsType} AddColumnArgsType */
|
|
3
4
|
/**
|
|
4
5
|
* CreateTableIdArgsType type.
|
|
5
6
|
* @typedef {object} CreateTableIdArgsType
|
|
@@ -109,7 +110,7 @@ export default class VelociousDatabaseMigration {
|
|
|
109
110
|
* @param {string} tableName - Table name.
|
|
110
111
|
* @param {string} columnName - Column name.
|
|
111
112
|
* @param {string} columnType - Column type.
|
|
112
|
-
* @param {
|
|
113
|
+
* @param {AddColumnArgsType} [args] - Options object.
|
|
113
114
|
* @returns {Promise<void>} - Resolves when complete.
|
|
114
115
|
*/
|
|
115
116
|
async addColumn(tableName, columnName, columnType, args) {
|
|
@@ -55,6 +55,9 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
|
|
|
55
55
|
*/
|
|
56
56
|
_testSharedConnectionRegistration = undefined
|
|
57
57
|
|
|
58
|
+
/** Attempt-owned shared connections keyed by resolved physical configuration. */
|
|
59
|
+
_testSharedConnectionsByReuseKey = new Map()
|
|
60
|
+
|
|
58
61
|
/**
|
|
59
62
|
* Connections.
|
|
60
63
|
* @type {import("../drivers/base.js").default[]} */
|
|
@@ -249,6 +252,30 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
|
|
|
249
252
|
if (this.connections.includes(connection)) await this.handleCheckedInIdleConnection()
|
|
250
253
|
}
|
|
251
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Permanently removes and closes a checked-out connection.
|
|
257
|
+
* @param {import("../drivers/base.js").default} connection - Connection that must not return to the pool.
|
|
258
|
+
*/
|
|
259
|
+
async discard(connection) {
|
|
260
|
+
const id = connection.getIdSeq()
|
|
261
|
+
const errors = []
|
|
262
|
+
|
|
263
|
+
this.untrackConnectionInUse(connection, id)
|
|
264
|
+
try {
|
|
265
|
+
await this.closeConnection(connection)
|
|
266
|
+
} catch (error) {
|
|
267
|
+
errors.push(error)
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
await this.drainPendingCheckouts()
|
|
271
|
+
} catch (error) {
|
|
272
|
+
errors.push(error)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (errors.length === 1) throw errors[0]
|
|
276
|
+
if (errors.length > 1) throw new AggregateError(errors, "Failed to discard a database connection")
|
|
277
|
+
}
|
|
278
|
+
|
|
252
279
|
/**
|
|
253
280
|
* Runs close checked out connection after checkin failure.
|
|
254
281
|
* @param {import("../drivers/base.js").default} connection - Connection that failed check-in cleanup.
|
|
@@ -885,6 +912,13 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
|
|
|
885
912
|
|
|
886
913
|
if (!actualCallback) throw new Error("withConnection requires a callback")
|
|
887
914
|
|
|
915
|
+
const testSharedConnection = this.activeTestSharedConnection()
|
|
916
|
+
if (testSharedConnection && this.connectionMatchesCurrentConfiguration(testSharedConnection)) {
|
|
917
|
+
return await this.asyncLocalStorage.run(testSharedConnection.getIdSeq(), async () => {
|
|
918
|
+
return await actualCallback(testSharedConnection)
|
|
919
|
+
})
|
|
920
|
+
}
|
|
921
|
+
|
|
888
922
|
const connection = await this.checkout(options)
|
|
889
923
|
const id = connection.getIdSeq()
|
|
890
924
|
|
|
@@ -1079,12 +1113,35 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
|
|
|
1079
1113
|
return registration
|
|
1080
1114
|
}
|
|
1081
1115
|
|
|
1116
|
+
/**
|
|
1117
|
+
* Registers an attempt-owned connection for exactly one physical configuration.
|
|
1118
|
+
* @param {import("../drivers/base.js").default} connection - Attempt-owned connection.
|
|
1119
|
+
* @param {string} reuseKey - Resolved physical configuration identity.
|
|
1120
|
+
* @returns {import("./base.js").TestSharedConnectionRegistration} - Opaque registration handle.
|
|
1121
|
+
*/
|
|
1122
|
+
setTestSharedConnectionForConfiguration(connection, reuseKey) {
|
|
1123
|
+
const registration = {owner: Symbol("test-shared-physical-connection")}
|
|
1124
|
+
|
|
1125
|
+
this._testSharedConnectionsByReuseKey.set(reuseKey, {connection, registration})
|
|
1126
|
+
return registration
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1082
1129
|
/**
|
|
1083
1130
|
* Clears the current shared connection registration. A supplied stale registration
|
|
1084
1131
|
* cannot clear a provider installed by a newer lifecycle.
|
|
1085
1132
|
* @param {import("./base.js").TestSharedConnectionRegistration} [registration] - Opaque registration handle to clear conditionally.
|
|
1086
1133
|
* @returns {void} */
|
|
1087
1134
|
clearTestSharedConnection(registration) {
|
|
1135
|
+
if (registration) {
|
|
1136
|
+
for (const [reuseKey, entry] of this._testSharedConnectionsByReuseKey) {
|
|
1137
|
+
if (entry.registration !== registration) continue
|
|
1138
|
+
this._testSharedConnectionsByReuseKey.delete(reuseKey)
|
|
1139
|
+
return
|
|
1140
|
+
}
|
|
1141
|
+
} else {
|
|
1142
|
+
this._testSharedConnectionsByReuseKey.clear()
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1088
1145
|
if (registration && registration !== this._testSharedConnectionRegistration) return
|
|
1089
1146
|
|
|
1090
1147
|
this._testSharedConnection = undefined
|
|
@@ -1103,18 +1160,38 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
|
|
|
1103
1160
|
* @returns {T} - Callback result.
|
|
1104
1161
|
*/
|
|
1105
1162
|
runWithTestSharedConnection(callback) {
|
|
1106
|
-
const connection = this.
|
|
1163
|
+
const connection = this.activeTestSharedConnection()
|
|
1107
1164
|
|
|
1108
1165
|
if (!connection) return callback()
|
|
1109
1166
|
|
|
1110
1167
|
return this.asyncLocalStorage.run(connection.getIdSeq(), callback)
|
|
1111
1168
|
}
|
|
1112
1169
|
|
|
1170
|
+
/**
|
|
1171
|
+
* Resolves a test-shared connection only while its checkout ID is still owned by this pool.
|
|
1172
|
+
* Fallback-only registrations have no checkout ID and must enter the normal checkout path.
|
|
1173
|
+
* @returns {import("../drivers/base.js").default | undefined} - Active shared connection.
|
|
1174
|
+
*/
|
|
1175
|
+
activeTestSharedConnection() {
|
|
1176
|
+
const connection = this.testSharedConnection()
|
|
1177
|
+
const id = connection?.getIdSeq()
|
|
1178
|
+
|
|
1179
|
+
if (typeof id !== "number") return
|
|
1180
|
+
if (this.connectionsInUse[id] !== connection) return
|
|
1181
|
+
|
|
1182
|
+
return connection
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1113
1185
|
/**
|
|
1114
1186
|
* Resolves the connection currently eligible for in-process test request sharing.
|
|
1115
1187
|
* @returns {import("../drivers/base.js").default | undefined} - Shared connection.
|
|
1116
1188
|
*/
|
|
1117
1189
|
testSharedConnection() {
|
|
1190
|
+
const reuseKey = this.getConfigurationReuseKey()
|
|
1191
|
+
const physicalRegistration = this._testSharedConnectionsByReuseKey.get(reuseKey)
|
|
1192
|
+
|
|
1193
|
+
if (physicalRegistration) return physicalRegistration.connection
|
|
1194
|
+
|
|
1118
1195
|
return this._testSharedConnectionProvider
|
|
1119
1196
|
? this._testSharedConnectionProvider()
|
|
1120
1197
|
: this._testSharedConnection
|
|
@@ -126,6 +126,16 @@ class VelociousDatabasePoolBase {
|
|
|
126
126
|
throw new Error("'checkin' not implemented")
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Permanently discards an attempt-owned checked-out connection.
|
|
131
|
+
* @abstract
|
|
132
|
+
* @param {import("../drivers/base.js").default} _connection - Connection that must not return to the pool.
|
|
133
|
+
* @returns {Promise<void>} - Resolves after the connection is closed and removed from pool ownership.
|
|
134
|
+
*/
|
|
135
|
+
discard(_connection) {
|
|
136
|
+
throw new Error("'discard' not implemented")
|
|
137
|
+
}
|
|
138
|
+
|
|
129
139
|
/**
|
|
130
140
|
* Runs checkout.
|
|
131
141
|
* @abstract
|
|
@@ -175,6 +185,16 @@ class VelociousDatabasePoolBase {
|
|
|
175
185
|
return undefined
|
|
176
186
|
}
|
|
177
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Registers a test connection for one resolved physical database configuration.
|
|
190
|
+
* @param {import("../drivers/base.js").default} _connection - Attempt-owned connection.
|
|
191
|
+
* @param {string} _reuseKey - Resolved physical configuration identity.
|
|
192
|
+
* @returns {TestSharedConnectionRegistration | undefined} - Opaque registration handle when supported.
|
|
193
|
+
*/
|
|
194
|
+
setTestSharedConnectionForConfiguration(_connection, _reuseKey) {
|
|
195
|
+
return undefined
|
|
196
|
+
}
|
|
197
|
+
|
|
178
198
|
/**
|
|
179
199
|
* Clears the shared connection or provider set for in-process test requests. No-op by default.
|
|
180
200
|
* When a registration is provided, clears only if it is still the active registration.
|
|
@@ -395,6 +415,18 @@ class VelociousDatabasePoolBase {
|
|
|
395
415
|
return connection
|
|
396
416
|
}
|
|
397
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Checks out a connection for an already-resolved physical configuration.
|
|
420
|
+
* Multi-configuration pools override this for explicit tenant registrations.
|
|
421
|
+
* @param {import("../../configuration-types.js").DatabaseConfigurationType} _databaseConfiguration - Captured configuration.
|
|
422
|
+
* @param {ConnectionCheckoutOptions} [_options] - Checkout options.
|
|
423
|
+
* @param {{retain: boolean}} [_args] - Pool-specific retention behavior.
|
|
424
|
+
* @returns {Promise<import("../drivers/base.js").default>} - Checked-out connection.
|
|
425
|
+
*/
|
|
426
|
+
async checkoutForConfiguration(_databaseConfiguration, _options, _args) {
|
|
427
|
+
throw new Error("Database pool does not support captured configuration checkout")
|
|
428
|
+
}
|
|
429
|
+
|
|
398
430
|
/**
|
|
399
431
|
* Runs close connection after failed connect.
|
|
400
432
|
* @param {import("../drivers/base.js").default} connection - Connection to close.
|