velocious 1.0.618 → 1.0.620

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 (61) hide show
  1. package/README.md +4 -2
  2. package/build/configuration.js +1 -1
  3. package/build/database/pool/async-tracked-multi-connection.js +25 -5
  4. package/build/database/pool/base.js +20 -10
  5. package/build/environment-handlers/base.js +9 -1
  6. package/build/environment-handlers/node.js +22 -4
  7. package/build/src/configuration.js +2 -2
  8. package/build/src/database/pool/async-tracked-multi-connection.d.ts +19 -1
  9. package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
  10. package/build/src/database/pool/async-tracked-multi-connection.js +26 -4
  11. package/build/src/database/pool/base.d.ts +12 -1
  12. package/build/src/database/pool/base.d.ts.map +1 -1
  13. package/build/src/database/pool/base.js +20 -11
  14. package/build/src/environment-handlers/base.d.ts +9 -1
  15. package/build/src/environment-handlers/base.d.ts.map +1 -1
  16. package/build/src/environment-handlers/base.js +9 -2
  17. package/build/src/environment-handlers/node.d.ts +10 -2
  18. package/build/src/environment-handlers/node.d.ts.map +1 -1
  19. package/build/src/environment-handlers/node.js +24 -5
  20. package/build/src/testing/shared-transaction-broker-client.d.ts +4 -2
  21. package/build/src/testing/shared-transaction-broker-client.d.ts.map +1 -1
  22. package/build/src/testing/shared-transaction-broker-client.js +5 -3
  23. package/build/src/testing/shared-transaction-broker.d.ts +21 -0
  24. package/build/src/testing/shared-transaction-broker.d.ts.map +1 -1
  25. package/build/src/testing/shared-transaction-broker.js +61 -18
  26. package/build/src/testing/shared-transaction-connection-coordinator.d.ts +2 -0
  27. package/build/src/testing/shared-transaction-connection-coordinator.d.ts.map +1 -1
  28. package/build/src/testing/shared-transaction-connection-coordinator.js +79 -12
  29. package/build/src/testing/shared-transaction-proxy-driver.d.ts +22 -3
  30. package/build/src/testing/shared-transaction-proxy-driver.d.ts.map +1 -1
  31. package/build/src/testing/shared-transaction-proxy-driver.js +51 -14
  32. package/build/src/testing/test-transaction-session.d.ts +129 -0
  33. package/build/src/testing/test-transaction-session.d.ts.map +1 -0
  34. package/build/src/testing/test-transaction-session.js +255 -0
  35. package/build/src/testing/test.d.ts +1 -1
  36. package/build/src/testing/test.d.ts.map +1 -1
  37. package/build/src/testing/test.js +2 -2
  38. package/build/testing/shared-transaction-broker-client.js +4 -2
  39. package/build/testing/shared-transaction-broker.js +58 -16
  40. package/build/testing/shared-transaction-connection-coordinator.js +83 -10
  41. package/build/testing/shared-transaction-proxy-driver.js +52 -13
  42. package/build/testing/test-transaction-session.js +240 -0
  43. package/build/testing/test.js +1 -1
  44. package/build/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +2 -1
  46. package/src/configuration.js +1 -1
  47. package/src/database/pool/async-tracked-multi-connection.js +25 -5
  48. package/src/database/pool/base.js +20 -10
  49. package/src/environment-handlers/base.js +9 -1
  50. package/src/environment-handlers/node.js +22 -4
  51. package/src/testing/shared-transaction-broker-client.js +4 -2
  52. package/src/testing/shared-transaction-broker.js +58 -16
  53. package/src/testing/shared-transaction-connection-coordinator.js +83 -10
  54. package/src/testing/shared-transaction-proxy-driver.js +52 -13
  55. package/src/testing/test-transaction-session.js +240 -0
  56. package/src/testing/test.js +1 -1
  57. package/build/src/testing/wait-for-event.d.ts +0 -46
  58. package/build/src/testing/wait-for-event.d.ts.map +0 -1
  59. package/build/src/testing/wait-for-event.js +0 -64
  60. package/build/testing/wait-for-event.js +0 -72
  61. package/src/testing/wait-for-event.js +0 -72
package/README.md CHANGED
@@ -259,7 +259,7 @@ npx velocious test:timing-manifest:merge --output tmp/test-timings.json \
259
259
  See [test profiling](docs/test-profiling.md) for lifecycle accounting, custom
260
260
  activity spans, schema, and privacy guarantees.
261
261
 
262
- Prefer waiting for a real signal or condition over sleeping a fixed duration. `waitForEvent(emitter, eventName, {timeoutMs, filter})` resolves the instant a matching event fires (a background job finishing, a model update, a websocket message) and rejects on timeout; for polling an arbitrary condition, use awaitery's `waitFor`.
262
+ Prefer waiting for a real signal or condition over sleeping a fixed duration. `waitForEvent(emitter, eventName, {timeoutMs, filter})` resolves the instant a matching event fires (a background job finishing, a model update, a websocket message) and rejects on timeout; for polling an arbitrary condition, use awaitery's `waitFor`. The stable Velocious import remains `velocious/build/src/testing/test.js`; its generic `waitForEvent` primitive comes from `@velocious/testing`, while the keyed testing DSL and framework runner remain owned by Velocious.
263
263
 
264
264
  ```js
265
265
  import {waitForEvent} from "velocious/build/src/testing/test.js"
@@ -2162,7 +2162,9 @@ option; the default configuration keeps sequential `TRUNCATE TABLE` requests. Se
2162
2162
 
2163
2163
  Request tests share transaction-active, non-tenant database connections with their in-process HTTP handlers. Eligibility is evaluated when each request is dispatched, so a hook can start a transaction and issue a request in the same callback. This makes uncommitted setup visible to handlers while preserving rollback isolation. Without an active transaction, handlers use independent pooled connections, so concurrency and locking tests can opt out of transaction cleanup and exercise production-style connections. Shared connection state is scoped to the test lifecycle and cleared around each test. See [docs/testing-guidelines.md](docs/testing-guidelines.md#request-test-database-connections).
2164
2164
 
2165
- Transactional tests also share active non-tenant connections with real forked, reusable pooled, and spawned background-job child runners through a per-attempt test-only loopback broker. Parent setup and child writes therefore occupy the same physical transaction and roll back together, including background-job persistence. Multiple configured databases route by identifier. Tenant-only databases remain excluded by default; a test can explicitly call `registerTransactionalTenant({databaseIdentifier, tenant})` from its attempt args to share one transaction with same-process paths resolving that exact physical tenant configuration. That registration remains active through `afterEach` and is revoked, rolled back, and released afterward. Emergency cleanup for a lifecycle hung beyond timeout grace revokes pending setup before it can publish stale state, bounds cleanup waits, and discards its physical tenant connection, so stale resumed work cannot use a driver recycled into a successor attempt. Tests using `{transaction: false, truncate: true}` retain ordinary independent physical connections for concurrency and locking coverage. See [docs/testing-guidelines.md](docs/testing-guidelines.md#in-process-test-database-connections).
2165
+ Transactional tests also share active non-tenant connections with real forked, reusable pooled, and spawned background-job child runners through a per-attempt test-only loopback broker. Parent setup and child writes therefore occupy the same physical transaction and roll back together, including background-job persistence. Backend harnesses can use [`TestTransactionSession`](docs/test-transaction-sessions.md) to propagate an ephemeral capability to already-running services and lazily enroll exact tenant physical identities. Tests using `{transaction: false, truncate: true}` retain ordinary independent connections for DDL, lock contention, independent commits, and genuine concurrency. See [docs/testing-guidelines.md](docs/testing-guidelines.md#request-test-database-connections).
2166
+
2167
+ Multiple configured databases route by identifier. Tenant-only databases remain excluded by default; a test can explicitly call `registerTransactionalTenant({databaseIdentifier, tenant})` from its attempt args to share one transaction with same-process paths resolving that exact physical tenant configuration. That registration remains active through `afterEach` and is revoked, rolled back, and released afterward. Emergency cleanup for a lifecycle hung beyond timeout grace revokes pending setup before it can publish stale state, bounds cleanup waits, and discards its physical tenant connection, so stale resumed work cannot use a driver recycled into a successor attempt. See [docs/testing-guidelines.md](docs/testing-guidelines.md#in-process-test-database-connections).
2166
2168
 
2167
2169
  Warm pooled children receive the active broker capability per job, discard retained proxy state when the capability changes, and fail closed if a transactional dispatch lacks coordinates. Child transaction/savepoint work holds a FIFO lease on the parent physical connection until the matching root release or rollback.
2168
2170
 
@@ -3475,7 +3475,7 @@ export default class VelociousConfiguration {
3475
3475
  * @returns {T} - Callback result.
3476
3476
  */
3477
3477
  withoutCurrentConnectionContexts(callback) {
3478
- let runCallback = callback
3478
+ let runCallback = () => this.getEnvironmentHandler().runWithoutSharedTransactionCoordinatorOwners(callback)
3479
3479
 
3480
3480
  for (const pool of Object.values(this.databasePools)) {
3481
3481
  if (!pool) continue
@@ -58,6 +58,12 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
58
58
  /** Attempt-owned shared connections keyed by resolved physical configuration. */
59
59
  _testSharedConnectionsByReuseKey = new Map()
60
60
 
61
+ /**
62
+ * Concurrent providers selected by live async join context.
63
+ * @type {Map<import("./base.js").TestSharedConnectionRegistration, {matches: () => boolean, provider: () => import("../drivers/base.js").default | undefined}>}
64
+ */
65
+ _testSharedConnectionProviders = new Map()
66
+
61
67
  /**
62
68
  * Connections.
63
69
  * @type {import("../drivers/base.js").default[]} */
@@ -176,15 +182,16 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
176
182
  /**
177
183
  * Spawns and times a physical connection without retaining its configuration.
178
184
  * @param {import("../../configuration-types.js").DatabaseConfigurationType} config - Resolved database configuration.
185
+ * @param {string} [reuseKey] - Exact resolved physical identity.
179
186
  * @returns {Promise<import("../drivers/base.js").default>} - Connected driver.
180
187
  */
181
- async spawnConnectionWithConfiguration(config) {
188
+ async spawnConnectionWithConfiguration(config, reuseKey) {
182
189
  const startedAt = this.nowMs()
183
190
  const profileContext = currentTestProfileContext(this.configuration)
184
191
  let failed = true
185
192
 
186
193
  try {
187
- const connection = await super.spawnConnectionWithConfiguration(config)
194
+ const connection = await super.spawnConnectionWithConfiguration(config, reuseKey)
188
195
 
189
196
  failed = false
190
197
  const liveConnectionCount = this.liveConnectionCount() - this.connectionsBeingSpawned + 1
@@ -544,7 +551,7 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
544
551
  try {
545
552
  const environmentHandler = this.configuration.getEnvironmentHandler()
546
553
  const connection = await environmentHandler.runWithTestProfileContext(profileContext, async () => {
547
- return await this.spawnConnectionWithConfiguration(databaseConfig)
554
+ return await this.spawnConnectionWithConfiguration(databaseConfig, this.getConfigurationReuseKey(databaseConfig))
548
555
  })
549
556
 
550
557
  this.stampConnectionForConfigurationReuseKey(connection, reuseKey)
@@ -1113,6 +1120,17 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
1113
1120
  return registration
1114
1121
  }
1115
1122
 
1123
+ /**
1124
+ * Registers a provider selected by the current live async join context.
1125
+ * @param {{matches: () => boolean, provider: () => import("../drivers/base.js").default | undefined}} args - Context selector and provider.
1126
+ * @returns {import("./base.js").TestSharedConnectionRegistration} - Opaque scoped registration handle.
1127
+ */
1128
+ registerTestSharedConnectionProvider(args) {
1129
+ const registration = {owner: Symbol("test-shared-connection-context-provider")}
1130
+ this._testSharedConnectionProviders.set(registration, args)
1131
+ return registration
1132
+ }
1133
+
1116
1134
  /**
1117
1135
  * Registers an attempt-owned connection for exactly one physical configuration.
1118
1136
  * @param {import("../drivers/base.js").default} connection - Attempt-owned connection.
@@ -1132,6 +1150,7 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
1132
1150
  * @param {import("./base.js").TestSharedConnectionRegistration} [registration] - Opaque registration handle to clear conditionally.
1133
1151
  * @returns {void} */
1134
1152
  clearTestSharedConnection(registration) {
1153
+ if (registration && this._testSharedConnectionProviders.delete(registration)) return
1135
1154
  if (registration) {
1136
1155
  for (const [reuseKey, entry] of this._testSharedConnectionsByReuseKey) {
1137
1156
  if (entry.registration !== registration) continue
@@ -1141,7 +1160,6 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
1141
1160
  } else {
1142
1161
  this._testSharedConnectionsByReuseKey.clear()
1143
1162
  }
1144
-
1145
1163
  if (registration && registration !== this._testSharedConnectionRegistration) return
1146
1164
 
1147
1165
  this._testSharedConnection = undefined
@@ -1187,11 +1205,13 @@ export default class VelociousDatabasePoolAsyncTrackedMultiConnection extends Ba
1187
1205
  * @returns {import("../drivers/base.js").default | undefined} - Shared connection.
1188
1206
  */
1189
1207
  testSharedConnection() {
1208
+ for (const {matches, provider} of this._testSharedConnectionProviders.values()) {
1209
+ if (matches()) return provider()
1210
+ }
1190
1211
  const reuseKey = this.getConfigurationReuseKey()
1191
1212
  const physicalRegistration = this._testSharedConnectionsByReuseKey.get(reuseKey)
1192
1213
 
1193
1214
  if (physicalRegistration) return physicalRegistration.connection
1194
-
1195
1215
  return this._testSharedConnectionProvider
1196
1216
  ? this._testSharedConnectionProvider()
1197
1217
  : this._testSharedConnection
@@ -185,6 +185,16 @@ class VelociousDatabasePoolBase {
185
185
  return undefined
186
186
  }
187
187
 
188
+ /**
189
+ * Registers a test shared connection selected by the caller's live async context.
190
+ * Base pools that do not track async context ignore it.
191
+ * @param {{matches: () => boolean, provider: () => import("../drivers/base.js").default | undefined}} _args - Context selector and connection provider.
192
+ * @returns {TestSharedConnectionRegistration | undefined} - Opaque scoped registration handle.
193
+ */
194
+ registerTestSharedConnectionProvider(_args) {
195
+ return undefined
196
+ }
197
+
188
198
  /**
189
199
  * Registers a test connection for one resolved physical database configuration.
190
200
  * @param {import("../drivers/base.js").default} _connection - Attempt-owned connection.
@@ -357,7 +367,7 @@ class VelociousDatabasePoolBase {
357
367
  */
358
368
  async spawnConnectionForConfiguration(databaseConfiguration) {
359
369
  const reuseKey = this.getConfigurationReuseKey(databaseConfiguration)
360
- const connection = await this.spawnConnectionWithConfiguration(databaseConfiguration)
370
+ const connection = await this.spawnConnectionWithConfiguration(databaseConfiguration, reuseKey)
361
371
 
362
372
  this.stampConnectionForConfigurationReuseKey(connection, reuseKey)
363
373
 
@@ -388,21 +398,21 @@ class VelociousDatabasePoolBase {
388
398
  /**
389
399
  * Runs spawn connection with configuration.
390
400
  * @param {import("../../configuration-types.js").DatabaseConfigurationType} config - Configuration object.
401
+ * @param {string} [reuseKey] - Exact resolved physical identity.
391
402
  * @returns {Promise<import("../drivers/base.js").default>} - Resolves with the spawn connection with configuration.
392
403
  */
393
- async spawnConnectionWithConfiguration(config) {
404
+ async spawnConnectionWithConfiguration(config, reuseKey) {
394
405
  const DriverClass = config.driver || this.driverClass
395
406
 
396
407
  if (!DriverClass) throw new Error("No driver class set in database pool or in given config")
397
408
 
398
- const sharedConnection = config.tenantOnly
399
- ? undefined
400
- : await this.configuration.getEnvironmentHandler().createTestSharedTransactionConnection({
401
- DriverClass,
402
- config,
403
- configuration: this.configuration,
404
- databaseIdentifier: this.identifier
405
- })
409
+ const sharedConnection = await this.configuration.getEnvironmentHandler().createTestSharedTransactionConnection({
410
+ DriverClass,
411
+ config,
412
+ configuration: this.configuration,
413
+ databaseIdentifier: this.identifier,
414
+ reuseKey
415
+ })
406
416
  const connection = sharedConnection || new DriverClass(config, this.configuration)
407
417
 
408
418
  try {
@@ -31,7 +31,7 @@ export default class VelociousEnvironmentHandlerBase {
31
31
  /**
32
32
  * Node test runtimes may replace a physical child connection with a broker
33
33
  * proxy. Other environments never participate in this test-only protocol.
34
- * @param {{DriverClass: typeof import("../database/drivers/base.js").default, config: import("../configuration-types.js").DatabaseConfigurationType, configuration: import("../configuration.js").default, databaseIdentifier: string}} _args - Connection details.
34
+ * @param {{DriverClass: typeof import("../database/drivers/base.js").default, config: import("../configuration-types.js").DatabaseConfigurationType, configuration: import("../configuration.js").default, databaseIdentifier: string, reuseKey?: string}} _args - Connection details.
35
35
  * @returns {Promise<import("../database/drivers/base.js").default | undefined>} - Optional proxy.
36
36
  */
37
37
  async createTestSharedTransactionConnection(_args) { return undefined }
@@ -54,6 +54,14 @@ export default class VelociousEnvironmentHandlerBase {
54
54
  */
55
55
  runWithSharedTransactionCoordinatorOwner(_connection, _owner, callback) { return callback() }
56
56
 
57
+ /**
58
+ * Runs work without inherited shared-transaction coordinator ownership.
59
+ * @template T
60
+ * @param {() => T} callback - Detached work.
61
+ * @returns {T} - Callback result.
62
+ */
63
+ runWithoutSharedTransactionCoordinatorOwners(callback) { return callback() }
64
+
57
65
  /**
58
66
  * Runs work with test-profile attribution. Runtimes without async-context
59
67
  * storage execute the callback without installing ambient attribution.
@@ -40,7 +40,7 @@ import InitializerFromRequireContext from "../database/initializer-from-require-
40
40
  import toImportSpecifier from "../utils/to-import-specifier.js"
41
41
  import {validateTimeZone} from "../time-zone.js"
42
42
  import AttachmentPathSource from "./node/attachment-path-source.js"
43
- import { createSharedTransactionProxyDriver, sharedTransactionBrokerConfig } from "../testing/shared-transaction-proxy-driver.js"
43
+ import { automaticSharedTransactionBrokerOmits, createSharedTransactionProxyDriver, sharedTransactionBrokerConfig } from "../testing/shared-transaction-proxy-driver.js"
44
44
 
45
45
  /**
46
46
  * Defines this typedef.
@@ -95,6 +95,8 @@ export default class VelociousEnvironmentHandlerNode extends Base{
95
95
  * @returns {typeof import("../database/pool/base.js").default} - Pool type for this context.
96
96
  */
97
97
  resolveTestSharedTransactionPoolType({configuredPoolType, databaseIdentifier}) {
98
+ const databaseConfiguration = this.getConfiguration().getDatabaseIdentifier(databaseIdentifier)
99
+ if (databaseConfiguration.tenantOnly && automaticSharedTransactionBrokerOmits(databaseIdentifier)) return configuredPoolType
98
100
  if (!sharedTransactionBrokerConfig(databaseIdentifier)) return configuredPoolType
99
101
 
100
102
  return AsyncTrackedMultiConnectionPool
@@ -102,13 +104,17 @@ export default class VelociousEnvironmentHandlerNode extends Base{
102
104
 
103
105
  /**
104
106
  * Creates a test-only child proxy when TestRunner supplied an active broker.
105
- * @param {{DriverClass: typeof import("../database/drivers/base.js").default, config: import("../configuration-types.js").DatabaseConfigurationType, configuration: import("../configuration.js").default, databaseIdentifier: string}} args - Connection details.
107
+ * @param {{DriverClass: typeof import("../database/drivers/base.js").default, config: import("../configuration-types.js").DatabaseConfigurationType, configuration: import("../configuration.js").default, databaseIdentifier: string, reuseKey?: string}} args - Connection details.
106
108
  * @returns {Promise<import("../database/drivers/base.js").default | undefined>} - Optional proxy.
107
109
  */
108
- async createTestSharedTransactionConnection({DriverClass, config, configuration, databaseIdentifier}) {
110
+ async createTestSharedTransactionConnection({DriverClass, config, configuration, databaseIdentifier, reuseKey}) {
111
+ if (config.tenantOnly && automaticSharedTransactionBrokerOmits(databaseIdentifier)) return undefined
109
112
  const brokerConfig = sharedTransactionBrokerConfig(databaseIdentifier)
110
113
  if (!brokerConfig) return undefined
111
- return createSharedTransactionProxyDriver(DriverClass, config, configuration, databaseIdentifier, brokerConfig)
114
+ return createSharedTransactionProxyDriver(DriverClass, config, configuration, databaseIdentifier, {
115
+ ...brokerConfig,
116
+ reuseKey: brokerConfig.allowDynamicIdentities ? reuseKey : undefined
117
+ })
112
118
  }
113
119
 
114
120
  /**
@@ -148,6 +154,18 @@ export default class VelociousEnvironmentHandlerNode extends Base{
148
154
  return this._sharedTransactionCoordinatorAsyncLocalStorage.run(owners, callback)
149
155
  }
150
156
 
157
+ /**
158
+ * Runs work without inherited shared-transaction coordinator ownership.
159
+ * @template T
160
+ * @param {() => T} callback - Detached work.
161
+ * @returns {T} - Callback result.
162
+ */
163
+ runWithoutSharedTransactionCoordinatorOwners(callback) {
164
+ if (!this._sharedTransactionCoordinatorAsyncLocalStorage) return callback()
165
+
166
+ return this._sharedTransactionCoordinatorAsyncLocalStorage.run(new Map(), callback)
167
+ }
168
+
151
169
  /**
152
170
  * Runs work with async-safe test profile attribution.
153
171
  * @template T