velocious 1.0.631 → 1.0.633

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 (174) hide show
  1. package/README.md +18 -5
  2. package/build/application.js +30 -10
  3. package/build/background-jobs/forked-runner-child.js +26 -30
  4. package/build/background-jobs/job-runner.js +5 -7
  5. package/build/background-jobs/lifecycle-client.js +3 -3
  6. package/build/background-jobs/local-dispatcher.js +29 -18
  7. package/build/background-jobs/main.js +33 -28
  8. package/build/background-jobs/pooled-runner-child.js +17 -9
  9. package/build/background-jobs/runner-graceful-shutdown.js +26 -10
  10. package/build/background-jobs/worker.js +34 -13
  11. package/build/configuration-types.js +8 -0
  12. package/build/configuration.js +267 -32
  13. package/build/database/drivers/base.js +163 -119
  14. package/build/database/drivers/sqlite/index.web.js +19 -11
  15. package/build/database/migrator.js +8 -5
  16. package/build/database/pool/async-tracked-multi-connection.js +73 -24
  17. package/build/database/pool/base.js +5 -0
  18. package/build/database/pool/single-multi-use.js +43 -11
  19. package/build/database/record/attachments/store.js +35 -26
  20. package/build/environment-handlers/base.js +122 -9
  21. package/build/environment-handlers/node/cli/commands/background-jobs-worker.js +47 -16
  22. package/build/environment-handlers/node/cli/commands/server.js +7 -2
  23. package/build/environment-handlers/node.js +15 -9
  24. package/build/error-reporting/request-details.js +28 -31
  25. package/build/frontend-model-controller.js +19 -13
  26. package/build/http-server/client/index.js +5 -13
  27. package/build/http-server/client/request-runner.js +26 -9
  28. package/build/http-server/client/request-timing.js +19 -0
  29. package/build/http-server/client/websocket-session.js +54 -14
  30. package/build/http-server/websocket-events-host.js +10 -8
  31. package/build/http-server/worker-handler/worker-thread.js +13 -6
  32. package/build/initializer.js +19 -1
  33. package/build/log-redactor.js +341 -0
  34. package/build/routes/resolver.js +32 -4
  35. package/build/src/application.d.ts +7 -0
  36. package/build/src/application.d.ts.map +1 -1
  37. package/build/src/application.js +32 -12
  38. package/build/src/background-jobs/forked-runner-child.js +25 -31
  39. package/build/src/background-jobs/job-runner.d.ts +3 -1
  40. package/build/src/background-jobs/job-runner.d.ts.map +1 -1
  41. package/build/src/background-jobs/job-runner.js +6 -9
  42. package/build/src/background-jobs/lifecycle-client.d.ts +1 -0
  43. package/build/src/background-jobs/lifecycle-client.d.ts.map +1 -1
  44. package/build/src/background-jobs/lifecycle-client.js +4 -4
  45. package/build/src/background-jobs/local-dispatcher.d.ts.map +1 -1
  46. package/build/src/background-jobs/local-dispatcher.js +31 -20
  47. package/build/src/background-jobs/main.d.ts +0 -4
  48. package/build/src/background-jobs/main.d.ts.map +1 -1
  49. package/build/src/background-jobs/main.js +34 -32
  50. package/build/src/background-jobs/pooled-runner-child.js +18 -11
  51. package/build/src/background-jobs/runner-graceful-shutdown.d.ts +9 -0
  52. package/build/src/background-jobs/runner-graceful-shutdown.d.ts.map +1 -1
  53. package/build/src/background-jobs/runner-graceful-shutdown.js +27 -13
  54. package/build/src/background-jobs/worker.d.ts +5 -0
  55. package/build/src/background-jobs/worker.d.ts.map +1 -1
  56. package/build/src/background-jobs/worker.js +31 -18
  57. package/build/src/configuration-types.d.ts +21 -0
  58. package/build/src/configuration-types.d.ts.map +1 -1
  59. package/build/src/configuration-types.js +8 -1
  60. package/build/src/configuration.d.ts +94 -0
  61. package/build/src/configuration.d.ts.map +1 -1
  62. package/build/src/configuration.js +239 -31
  63. package/build/src/database/drivers/base.d.ts +5 -1
  64. package/build/src/database/drivers/base.d.ts.map +1 -1
  65. package/build/src/database/drivers/base.js +159 -115
  66. package/build/src/database/drivers/sqlite/index.web.d.ts +2 -0
  67. package/build/src/database/drivers/sqlite/index.web.d.ts.map +1 -1
  68. package/build/src/database/drivers/sqlite/index.web.js +17 -11
  69. package/build/src/database/migrator.d.ts.map +1 -1
  70. package/build/src/database/migrator.js +9 -6
  71. package/build/src/database/pool/async-tracked-multi-connection.d.ts +15 -0
  72. package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
  73. package/build/src/database/pool/async-tracked-multi-connection.js +71 -21
  74. package/build/src/database/pool/base.d.ts +2 -0
  75. package/build/src/database/pool/base.d.ts.map +1 -1
  76. package/build/src/database/pool/base.js +5 -1
  77. package/build/src/database/pool/single-multi-use.d.ts.map +1 -1
  78. package/build/src/database/pool/single-multi-use.js +47 -13
  79. package/build/src/database/record/attachments/store.d.ts +6 -0
  80. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  81. package/build/src/database/record/attachments/store.js +31 -23
  82. package/build/src/environment-handlers/base.d.ts +79 -8
  83. package/build/src/environment-handlers/base.d.ts.map +1 -1
  84. package/build/src/environment-handlers/base.js +106 -10
  85. package/build/src/environment-handlers/node/cli/commands/background-jobs-worker.d.ts +36 -0
  86. package/build/src/environment-handlers/node/cli/commands/background-jobs-worker.d.ts.map +1 -1
  87. package/build/src/environment-handlers/node/cli/commands/background-jobs-worker.js +45 -16
  88. package/build/src/environment-handlers/node/cli/commands/server.d.ts.map +1 -1
  89. package/build/src/environment-handlers/node/cli/commands/server.js +8 -3
  90. package/build/src/environment-handlers/node.d.ts +3 -6
  91. package/build/src/environment-handlers/node.d.ts.map +1 -1
  92. package/build/src/environment-handlers/node.js +13 -10
  93. package/build/src/error-reporting/request-details.d.ts +8 -1
  94. package/build/src/error-reporting/request-details.d.ts.map +1 -1
  95. package/build/src/error-reporting/request-details.js +30 -32
  96. package/build/src/frontend-model-controller.d.ts.map +1 -1
  97. package/build/src/frontend-model-controller.js +20 -14
  98. package/build/src/http-server/client/index.d.ts.map +1 -1
  99. package/build/src/http-server/client/index.js +5 -12
  100. package/build/src/http-server/client/request-runner.d.ts.map +1 -1
  101. package/build/src/http-server/client/request-runner.js +23 -10
  102. package/build/src/http-server/client/request-timing.d.ts +13 -0
  103. package/build/src/http-server/client/request-timing.d.ts.map +1 -1
  104. package/build/src/http-server/client/request-timing.js +19 -1
  105. package/build/src/http-server/client/websocket-session.d.ts +9 -2
  106. package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
  107. package/build/src/http-server/client/websocket-session.js +46 -15
  108. package/build/src/http-server/websocket-events-host.d.ts.map +1 -1
  109. package/build/src/http-server/websocket-events-host.js +11 -8
  110. package/build/src/http-server/worker-handler/worker-thread.d.ts.map +1 -1
  111. package/build/src/http-server/worker-handler/worker-thread.js +13 -6
  112. package/build/src/initializer.d.ts +14 -1
  113. package/build/src/initializer.d.ts.map +1 -1
  114. package/build/src/initializer.js +18 -2
  115. package/build/src/log-redactor.d.ts +97 -0
  116. package/build/src/log-redactor.d.ts.map +1 -0
  117. package/build/src/log-redactor.js +297 -0
  118. package/build/src/routes/resolver.d.ts +7 -0
  119. package/build/src/routes/resolver.d.ts.map +1 -1
  120. package/build/src/routes/resolver.js +26 -5
  121. package/build/src/testing/sqljs-test-database.d.ts +41 -0
  122. package/build/src/testing/sqljs-test-database.d.ts.map +1 -0
  123. package/build/src/testing/sqljs-test-database.js +72 -0
  124. package/build/src/testing/test-runner.d.ts +79 -6
  125. package/build/src/testing/test-runner.d.ts.map +1 -1
  126. package/build/src/testing/test-runner.js +498 -139
  127. package/build/src/testing/test.js +2 -2
  128. package/build/src/utils/shutdown-lifecycle.d.ts +13 -0
  129. package/build/src/utils/shutdown-lifecycle.d.ts.map +1 -1
  130. package/build/src/utils/shutdown-lifecycle.js +35 -26
  131. package/build/testing/sqljs-test-database.js +73 -0
  132. package/build/testing/test-runner.js +529 -141
  133. package/build/testing/test.js +1 -1
  134. package/build/tsconfig.tsbuildinfo +1 -1
  135. package/build/utils/shutdown-lifecycle.js +33 -30
  136. package/package.json +1 -1
  137. package/scripts/test-browser.js +16 -29
  138. package/src/application.js +30 -10
  139. package/src/background-jobs/forked-runner-child.js +26 -30
  140. package/src/background-jobs/job-runner.js +5 -7
  141. package/src/background-jobs/lifecycle-client.js +3 -3
  142. package/src/background-jobs/local-dispatcher.js +29 -18
  143. package/src/background-jobs/main.js +33 -28
  144. package/src/background-jobs/pooled-runner-child.js +17 -9
  145. package/src/background-jobs/runner-graceful-shutdown.js +26 -10
  146. package/src/background-jobs/worker.js +34 -13
  147. package/src/configuration-types.js +8 -0
  148. package/src/configuration.js +267 -32
  149. package/src/database/drivers/base.js +163 -119
  150. package/src/database/drivers/sqlite/index.web.js +19 -11
  151. package/src/database/migrator.js +8 -5
  152. package/src/database/pool/async-tracked-multi-connection.js +73 -24
  153. package/src/database/pool/base.js +5 -0
  154. package/src/database/pool/single-multi-use.js +43 -11
  155. package/src/database/record/attachments/store.js +35 -26
  156. package/src/environment-handlers/base.js +122 -9
  157. package/src/environment-handlers/node/cli/commands/background-jobs-worker.js +47 -16
  158. package/src/environment-handlers/node/cli/commands/server.js +7 -2
  159. package/src/environment-handlers/node.js +15 -9
  160. package/src/error-reporting/request-details.js +28 -31
  161. package/src/frontend-model-controller.js +19 -13
  162. package/src/http-server/client/index.js +5 -13
  163. package/src/http-server/client/request-runner.js +26 -9
  164. package/src/http-server/client/request-timing.js +19 -0
  165. package/src/http-server/client/websocket-session.js +54 -14
  166. package/src/http-server/websocket-events-host.js +10 -8
  167. package/src/http-server/worker-handler/worker-thread.js +13 -6
  168. package/src/initializer.js +19 -1
  169. package/src/log-redactor.js +341 -0
  170. package/src/routes/resolver.js +32 -4
  171. package/src/testing/sqljs-test-database.js +73 -0
  172. package/src/testing/test-runner.js +529 -141
  173. package/src/testing/test.js +1 -1
  174. package/src/utils/shutdown-lifecycle.js +33 -30
@@ -4,6 +4,13 @@
4
4
  * @module types
5
5
  */
6
6
 
7
+ /**
8
+ * Immutable identity shared by application initializers for one process lifecycle.
9
+ * @typedef {object} ApplicationProcessContext
10
+ * @property {string} type - Generic process type supplied to `configuration.initialize()`.
11
+ * @property {string} instanceId - Opaque identity unique to this lifecycle.
12
+ */
13
+
7
14
  /**
8
15
  * @typedef {(args: {request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default, response: import("./http-server/client/response.js").default}) => Promise<void>} CorsType
9
16
  */
@@ -133,6 +140,7 @@
133
140
  * @property {Array<"debug-low-level" | "debug" | "info" | "warn" | "error">} [levels] - Override which log levels are emitted.
134
141
  * @property {boolean} [debugLowLevel] - Convenience flag to include very low-level debug logs.
135
142
  * @property {boolean} [queryLogging] - Enable/disable database query logging. Defaults to true outside test and false in test.
143
+ * @property {string[]} [sensitiveNames] - Additional case-insensitive sensitive header/parameter names to redact from logging.
136
144
  * @property {LoggerConfig[]} [loggers] - Logger instances (converted to outputs when configured).
137
145
  * @property {LoggingOutputConfig[]} [outputs] - Explicit logger outputs (overrides console/file defaults when provided).
138
146
  */
@@ -22,6 +22,7 @@
22
22
 
23
23
  import { digg } from "diggerize"
24
24
  import gettextConfig from "gettext-universal/build/src/config.js"
25
+ import UUID from "pure-uuid"
25
26
  import translate from "gettext-universal/build/src/translate.js"
26
27
  import Ability from "./authorization/ability.js"
27
28
  import BackgroundJobsAdapter from "./background-jobs/adapter.js"
@@ -31,6 +32,7 @@ import EventEmitter from "./utils/event-emitter.js"
31
32
  import VelociousWebsocketChannelSubscribers from "./http-server/websocket-channel-subscribers.js"
32
33
  import { CurrentConfigurationNotSetError, currentConfiguration, setCurrentConfiguration } from "./current-configuration.js"
33
34
  import { requestDetails } from "./error-reporting/request-details.js"
35
+ import LogRedactor from "./log-redactor.js"
34
36
  import { frontendModelApiManifest, frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition, frontendModelResourcesForBackendProject } from "./frontend-models/resource-definition.js"
35
37
  import { currentOfflineGrantSigningKey, normalizeOfflineGrantSigningKey } from "./sync/offline-grant.js"
36
38
  import PluginRoutes from "./routes/plugin-routes.js"
@@ -41,6 +43,7 @@ import { withTrackedStack } from "./utils/with-tracked-stack.js"
41
43
  import VelociousPackage from "./packages/velocious-package.js"
42
44
  import FrontendTenantSqliteLifecycle from "./tenants/frontend-tenant-sqlite-lifecycle.js"
43
45
  import { resolveGenerationId, resolveInitialGenerationState, resolveLifecycleSocketPath } from "./background-jobs/generation-identity.js"
46
+ import { runShutdownSteps } from "./utils/shutdown-lifecycle.js"
44
47
 
45
48
  export { CurrentConfigurationNotSetError }
46
49
 
@@ -303,6 +306,16 @@ export default class VelociousConfiguration {
303
306
  }
304
307
 
305
308
  this._isInitialized = false
309
+ /** @type {import("./configuration-types.js").ApplicationProcessContext | undefined} */
310
+ this._applicationProcessContext = undefined
311
+ /** @type {import("./initializer.js").default[]} */
312
+ this._successfulInitializers = []
313
+ /** @type {boolean} */
314
+ this._applicationLifecycleInitialized = false
315
+ /** @type {Promise<void> | undefined} */
316
+ this._shutdownPromise = undefined
317
+ /** @type {Promise<void> | undefined} */
318
+ this._queuedInitializePromise = undefined
306
319
  this._modelsInitialized = false
307
320
  /**
308
321
  * Invalidates model phases that started before database connections closed.
@@ -323,6 +336,8 @@ export default class VelociousConfiguration {
323
336
  * @type {Promise<void> | undefined}
324
337
  */
325
338
  this._initializePromise = undefined
339
+ /** @type {number | undefined} */
340
+ this._initializePromiseGeneration = undefined
326
341
  const websocketInboundQueue = httpServer?.websocketInboundQueue
327
342
  const websocketOutboundQueue = httpServer?.websocketOutboundQueue
328
343
 
@@ -425,6 +440,7 @@ export default class VelociousConfiguration {
425
440
  * @type {((session: import("./http-server/client/websocket-session.js").default) => ReturnType<typeof JSON.parse> | Promise<ReturnType<typeof JSON.parse>>) | null} */
426
441
  this._websocketSessionIdentityResolver = null
427
442
  this._logging = logging
443
+ this._logRedactor = new LogRedactor({sensitiveNames: logging?.sensitiveNames})
428
444
  this._mailerBackend = mailerBackend
429
445
  this._routeResolverHooks = [...(routeResolverHooks || [])]
430
446
  this._addDebugEndpointRouteHook()
@@ -1445,6 +1461,14 @@ export default class VelociousConfiguration {
1445
1461
  }
1446
1462
  }
1447
1463
 
1464
+ /**
1465
+ * Gets the configuration-owned structured logging redactor.
1466
+ * @returns {LogRedactor} - Structured logging redactor.
1467
+ */
1468
+ getLogRedactor() {
1469
+ return this._logRedactor
1470
+ }
1471
+
1448
1472
  /**
1449
1473
  * Runs get query logging enabled.
1450
1474
  * @returns {boolean} - Whether database query logging is enabled.
@@ -2364,49 +2388,152 @@ export default class VelociousConfiguration {
2364
2388
  * @param {string} args.type - Type identifier.
2365
2389
  * @returns {Promise<void>} - Resolves when complete.
2366
2390
  */
2367
- async initialize({type} = {type: "undefined"}) {
2391
+ initialize({type} = {type: "undefined"}) {
2392
+ if (this._queuedInitializePromise) return this._queuedInitializePromise
2393
+
2394
+ if (this._shutdownPromise) {
2395
+ return this._queueInitialize({continueAfterWaitFailure: true, type, waitFor: this._shutdownPromise})
2396
+ }
2397
+
2368
2398
  if (this._closeDatabaseConnectionsPromise) {
2369
- await this._closeDatabaseConnectionsPromise
2399
+ return this._queueInitialize({continueAfterWaitFailure: false, type, waitFor: this._closeDatabaseConnectionsPromise})
2370
2400
  }
2371
2401
 
2402
+ return this._beginInitialize({type})
2403
+ }
2404
+
2405
+ /**
2406
+ * Starts or joins initialization after lifecycle blockers have settled.
2407
+ * @param {object} args - Startup options.
2408
+ * @param {string} args.type - Generic application process type.
2409
+ * @returns {Promise<void>} - Shared startup promise.
2410
+ */
2411
+ _beginInitialize({type}) {
2372
2412
  const initializationGeneration = this._modelInitializationGeneration
2373
2413
 
2374
- if (this._isInitialized) return
2414
+ if (this._initializePromise && this._initializePromiseGeneration === initializationGeneration) {
2415
+ return this._initializePromise
2416
+ }
2417
+
2418
+ if (this._initializePromise) {
2419
+ return this._queueInitialize({continueAfterWaitFailure: false, type, waitFor: this._initializePromise})
2420
+ }
2421
+
2422
+ if (this._isInitialized) {
2423
+ this._initializePromise = Promise.resolve()
2424
+ this._initializePromiseGeneration = initializationGeneration
2425
+
2426
+ return this._initializePromise
2427
+ }
2375
2428
  // Memoize the in-progress initialization so concurrent callers await the same
2376
2429
  // bootstrap instead of racing. `_isInitialized` was previously set to `true`
2377
2430
  // up front, so a second caller (e.g. a pooled runner with
2378
2431
  // `pooledRunnerConcurrency > 1` starting several jobs on a cold child) could
2379
2432
  // skip initialization and load models / perform a job while the first call
2380
2433
  // was still awaiting model discovery and initializers. Mirrors connectBeacon.
2381
- if (this._initializePromise) {
2382
- const initializePromise = this._initializePromise
2434
+ const initializePromise = this._runInitialize({initializationGeneration, type})
2435
+
2436
+ this._initializePromise = initializePromise
2437
+ this._initializePromiseGeneration = initializationGeneration
2383
2438
 
2384
- await initializePromise
2439
+ return initializePromise
2440
+ }
2385
2441
 
2386
- if (this._modelInitializationGeneration === initializationGeneration && !this._isInitialized) {
2387
- if (this._initializePromise === initializePromise) {
2442
+ /**
2443
+ * Queues one shared initialization behind an incompatible lifecycle phase.
2444
+ * @param {object} args - Queue options.
2445
+ * @param {boolean} args.continueAfterWaitFailure - Whether a completed failed shutdown still permits replacement startup.
2446
+ * @param {string} args.type - Replacement process type.
2447
+ * @param {Promise<void>} args.waitFor - Lifecycle phase that must settle first.
2448
+ * @returns {Promise<void>} - Shared queued startup promise.
2449
+ */
2450
+ _queueInitialize({continueAfterWaitFailure, type, waitFor}) {
2451
+ if (this._queuedInitializePromise) return this._queuedInitializePromise
2452
+
2453
+ const queuedInitializePromise = (async () => {
2454
+ await this._waitForInitializeBlocker({continueAfterWaitFailure, waitFor})
2455
+
2456
+ if (this._shutdownPromise === waitFor) this._shutdownPromise = undefined
2457
+ if (this._initializePromise === waitFor) {
2458
+ this._initializePromise = undefined
2459
+ this._initializePromiseGeneration = undefined
2460
+ }
2461
+
2462
+ const shutdownPromise = this._shutdownPromise
2463
+
2464
+ if (shutdownPromise) {
2465
+ await this._waitForInitializeBlocker({continueAfterWaitFailure: true, waitFor: shutdownPromise})
2466
+ if (this._shutdownPromise === shutdownPromise) this._shutdownPromise = undefined
2467
+ }
2468
+
2469
+ if (this._initializePromise && this._initializePromiseGeneration !== this._modelInitializationGeneration) {
2470
+ const staleInitializePromise = this._initializePromise
2471
+
2472
+ await staleInitializePromise
2473
+ if (this._initializePromise === staleInitializePromise) {
2388
2474
  this._initializePromise = undefined
2475
+ this._initializePromiseGeneration = undefined
2389
2476
  }
2390
-
2391
- return await this.initialize({type})
2392
2477
  }
2393
2478
 
2394
- return
2479
+ await this._beginInitialize({type})
2480
+ })().finally(() => {
2481
+ this._queuedInitializePromise = undefined
2482
+ })
2483
+
2484
+ this._queuedInitializePromise = queuedInitializePromise
2485
+
2486
+ return queuedInitializePromise
2487
+ }
2488
+
2489
+ /**
2490
+ * Waits for a lifecycle phase before queued initialization proceeds.
2491
+ * @param {object} args - Wait policy.
2492
+ * @param {boolean} args.continueAfterWaitFailure - Whether replacement startup remains available after a failed phase.
2493
+ * @param {Promise<void>} args.waitFor - Lifecycle phase that must settle first.
2494
+ * @returns {Promise<void>} - Resolves when queued initialization may continue.
2495
+ */
2496
+ async _waitForInitializeBlocker({continueAfterWaitFailure, waitFor}) {
2497
+ try {
2498
+ await waitFor
2499
+ } catch (error) {
2500
+ if (!continueAfterWaitFailure) throw error
2501
+ }
2502
+ }
2503
+
2504
+ /**
2505
+ * Runs one atomic framework and application initialization attempt.
2506
+ * @param {object} args - Initialization identity.
2507
+ * @param {number} args.initializationGeneration - Framework model generation.
2508
+ * @param {string} args.type - Generic application process type.
2509
+ * @returns {Promise<void>} - Resolves when initialized.
2510
+ */
2511
+ async _runInitialize({initializationGeneration, type}) {
2512
+ const startsApplicationLifecycle = !this._applicationLifecycleInitialized
2513
+
2514
+ if (startsApplicationLifecycle) {
2515
+ this._applicationProcessContext = Object.freeze({
2516
+ instanceId: new UUID(4).format(),
2517
+ type
2518
+ })
2395
2519
  }
2396
2520
 
2397
- const initializePromise = (async () => {
2521
+ try {
2398
2522
  await this.initializeModels({type})
2399
2523
 
2400
2524
  // Model initialization can be invalidated by a concurrent connection close.
2401
2525
  // If models are not ready, stop without marking the configuration initialized
2402
2526
  // so the next caller retries a full bootstrap.
2403
- if (this._modelInitializationGeneration !== initializationGeneration || !this._modelsInitialized) return
2527
+ if (this._modelInitializationGeneration !== initializationGeneration || !this._modelsInitialized) {
2528
+ if (startsApplicationLifecycle) this._resetApplicationLifecycle()
2529
+ return
2530
+ }
2404
2531
 
2405
2532
  await this.getEnvironmentHandler().autoDiscoverResources(this)
2406
2533
  this._mergeDiscoveredAbilityResources()
2407
2534
  this._validateResourceRelationshipsOnModels()
2408
2535
 
2409
- if (this._initializers) {
2536
+ if (startsApplicationLifecycle && this._initializers) {
2410
2537
  const initializers = await this._initializers({configuration: this})
2411
2538
  const {requireContext, ...restArgs} = initializers
2412
2539
 
@@ -2415,37 +2542,106 @@ export default class VelociousConfiguration {
2415
2542
  if (requireContext) {
2416
2543
  for (const initializerKey of requireContext.keys()) {
2417
2544
  const InitializerClass = requireContext(initializerKey).default
2418
- const initializerInstance = new InitializerClass({configuration: this, type})
2545
+ const processContext = this._applicationProcessContext
2546
+
2547
+ if (!processContext) throw new Error("Application process context is not available during initializer startup")
2548
+
2549
+ const initializerInstance = new InitializerClass({configuration: this, processContext, type})
2419
2550
 
2420
2551
  await initializerInstance.run()
2552
+ this._successfulInitializers.push(initializerInstance)
2421
2553
  }
2422
2554
  }
2423
2555
  }
2424
2556
 
2557
+ if (startsApplicationLifecycle) this._applicationLifecycleInitialized = true
2558
+
2425
2559
  if (this._modelInitializationGeneration === initializationGeneration) {
2426
2560
  this._isInitialized = true
2427
2561
  }
2428
- })()
2562
+ } catch (error) {
2563
+ if (startsApplicationLifecycle) {
2564
+ let teardownError
2429
2565
 
2430
- this._initializePromise = initializePromise
2566
+ try {
2567
+ await this._teardownSuccessfulInitializers()
2568
+ } catch (caughtTeardownError) {
2569
+ teardownError = caughtTeardownError
2570
+ } finally {
2571
+ this._resetApplicationLifecycle()
2572
+ }
2431
2573
 
2432
- try {
2433
- await initializePromise
2434
- } catch (error) {
2435
- // Let a later call retry a failed initialization instead of every future
2436
- // caller awaiting the same cached rejection.
2437
- if (this._initializePromise === initializePromise) {
2438
- this._initializePromise = undefined
2574
+ if (teardownError instanceof AggregateError) {
2575
+ throw new AggregateError(
2576
+ [error, ...teardownError.errors],
2577
+ "Application process startup and cleanup failed",
2578
+ {cause: error}
2579
+ )
2580
+ }
2581
+
2582
+ if (teardownError !== undefined) {
2583
+ throw new AggregateError(
2584
+ [error, teardownError],
2585
+ "Application process startup and cleanup failed",
2586
+ {cause: error}
2587
+ )
2588
+ }
2439
2589
  }
2590
+
2440
2591
  throw error
2592
+ } finally {
2593
+ if (!this._isInitialized && this._initializePromiseGeneration === initializationGeneration) {
2594
+ this._initializePromise = undefined
2595
+ this._initializePromiseGeneration = undefined
2596
+ }
2441
2597
  }
2598
+ }
2442
2599
 
2443
- // If the inner IIFE returned without marking the configuration initialized
2444
- // (e.g. because models were invalidated mid-bootstrap), clear the promise so
2445
- // a later call retries a full bootstrap.
2446
- if (!this._isInitialized && this._initializePromise === initializePromise) {
2447
- this._initializePromise = undefined
2448
- }
2600
+ /**
2601
+ * Tears down every successfully started initializer in reverse order.
2602
+ * @returns {Promise<void>} - Resolves when every teardown succeeds.
2603
+ */
2604
+ async _teardownSuccessfulInitializers() {
2605
+ const successfulInitializers = this._successfulInitializers.splice(0).reverse()
2606
+
2607
+ await runShutdownSteps({
2608
+ message: "Application initializer teardown failed",
2609
+ steps: successfulInitializers.map((initializer) => async () => await initializer.teardown())
2610
+ })
2611
+ }
2612
+
2613
+ /** Clears application-owned lifecycle state after every teardown attempt. */
2614
+ _resetApplicationLifecycle() {
2615
+ this._applicationLifecycleInitialized = false
2616
+ this._applicationProcessContext = undefined
2617
+ this._successfulInitializers = []
2618
+ }
2619
+
2620
+ /**
2621
+ * Tears down the current application lifecycle once.
2622
+ * @returns {Promise<void>} - Exact shared shutdown promise.
2623
+ */
2624
+ shutdown() {
2625
+ if (this._shutdownPromise) return this._shutdownPromise
2626
+
2627
+ const initializePromise = this._initializePromise
2628
+ const shutdownPromise = (async () => {
2629
+ try {
2630
+ if (initializePromise) await initializePromise
2631
+ await this._teardownSuccessfulInitializers()
2632
+ } finally {
2633
+ this._resetApplicationLifecycle()
2634
+ this._isInitialized = false
2635
+ if (this._initializePromise === initializePromise) {
2636
+ this._initializePromise = undefined
2637
+ this._initializePromiseGeneration = undefined
2638
+ }
2639
+ }
2640
+ })()
2641
+
2642
+ this._shutdownPromise = shutdownPromise
2643
+
2644
+ return shutdownPromise
2449
2645
  }
2450
2646
 
2451
2647
  /**
@@ -3312,7 +3508,9 @@ export default class VelociousConfiguration {
3312
3508
  async clientErrorPayloadForError(args) {
3313
3509
  /** @type {import("./configuration-types.js").ClientErrorPayloadReporterPayload} */
3314
3510
  const payload = {}
3315
- const details = requestDetails(args.request)
3511
+ const requestTiming = this.getCurrentRequestTiming()
3512
+ const sensitiveValues = requestTiming ? requestTiming.getLogSensitiveValues() : new Set()
3513
+ const details = requestDetails(args.request, {redactor: this.getLogRedactor(), sensitiveValues})
3316
3514
 
3317
3515
  for (const reporter of this._clientErrorPayloadReporters) {
3318
3516
  const reporterPayload = await reporter({
@@ -3328,6 +3526,32 @@ export default class VelociousConfiguration {
3328
3526
  return payload
3329
3527
  }
3330
3528
 
3529
+ /**
3530
+ * Runs one test attempt in a revocable database-access context.
3531
+ * @template T
3532
+ * @param {{revoked: boolean}} scope - Attempt-owned access scope.
3533
+ * @param {() => T | Promise<T>} callback - Attempt work.
3534
+ * @returns {T | Promise<T>} - Callback result.
3535
+ */
3536
+ runWithTestDatabaseAccessScope(scope, callback) {
3537
+ return this.getEnvironmentHandler().runWithTestDatabaseAccessScope(scope, callback)
3538
+ }
3539
+
3540
+ /**
3541
+ * Runs persistent framework work without inheriting a test attempt's revocable database-access scope.
3542
+ * @template T
3543
+ * @param {() => T | Promise<T>} callback - Persistent work to run.
3544
+ * @returns {Promise<T>} - Callback result.
3545
+ */
3546
+ async withoutCurrentTestDatabaseAccessScope(callback) {
3547
+ return await this.getEnvironmentHandler().runWithCapturedTestDatabaseAccessScope(undefined, callback)
3548
+ }
3549
+
3550
+ /** Throws when a timed-out test attempt tries to start more database work. */
3551
+ assertDatabaseAccessAllowed() {
3552
+ this.getEnvironmentHandler().assertTestDatabaseAccessAllowed()
3553
+ }
3554
+
3331
3555
  /**
3332
3556
  * Runs with connections.
3333
3557
  * @template T
@@ -3336,6 +3560,7 @@ export default class VelociousConfiguration {
3336
3560
  * @returns {Promise<T>} - Resolves with the callback result.
3337
3561
  */
3338
3562
  async withConnections(optionsOrCallback, callback) {
3563
+ this.assertDatabaseAccessAllowed()
3339
3564
  const {
3340
3565
  callback: actualWithConnectionsCallback,
3341
3566
  databaseIdentifiers,
@@ -3366,6 +3591,7 @@ export default class VelociousConfiguration {
3366
3591
  * @returns {Promise<T>} - Resolves with the callback result.
3367
3592
  */
3368
3593
  async withTransaction({databaseIdentifier, name = "Configuration.withTransaction", ...restArgs}, callback) {
3594
+ this.assertDatabaseAccessAllowed()
3369
3595
  restArgsError(restArgs)
3370
3596
 
3371
3597
  if (!databaseIdentifier) throw new Error("Configuration.withTransaction requires a databaseIdentifier")
@@ -3379,6 +3605,7 @@ export default class VelociousConfiguration {
3379
3605
  const pool = this.getDatabasePool(databaseIdentifier)
3380
3606
 
3381
3607
  return await pool.withOperationConnection({name}, async (connection, owner) => {
3608
+ this.assertDatabaseAccessAllowed()
3382
3609
  const operation = new DatabaseOperation({
3383
3610
  configuration: this,
3384
3611
  databaseConfiguration,
@@ -3390,7 +3617,10 @@ export default class VelociousConfiguration {
3390
3617
  })
3391
3618
 
3392
3619
  try {
3393
- return await operation.transaction(async () => await callback(operation))
3620
+ return await operation.transaction(async () => {
3621
+ this.assertDatabaseAccessAllowed()
3622
+ return await callback(operation)
3623
+ })
3394
3624
  } finally {
3395
3625
  operation.complete()
3396
3626
  }
@@ -3407,6 +3637,7 @@ export default class VelociousConfiguration {
3407
3637
  * @returns {Promise<T>} - Callback result.
3408
3638
  */
3409
3639
  async withDatabaseOperation({databaseConfiguration, databaseIdentifier, name = "Configuration.withDatabaseOperation", schemaGeneration, tenant, ...restArgs}, callback) {
3640
+ this.assertDatabaseAccessAllowed()
3410
3641
  restArgsError(restArgs)
3411
3642
 
3412
3643
  if (!databaseIdentifier) throw new Error("Configuration.withDatabaseOperation requires a databaseIdentifier")
@@ -3417,6 +3648,7 @@ export default class VelociousConfiguration {
3417
3648
  const configurationReuseKey = pool.getConfigurationReuseKey(databaseConfiguration)
3418
3649
 
3419
3650
  return await pool.withCapturedOperationConnection({databaseConfiguration, name}, async (connection, owner) => {
3651
+ this.assertDatabaseAccessAllowed()
3420
3652
  const operation = new DatabaseOperation({
3421
3653
  configuration: this,
3422
3654
  databaseConfiguration,
@@ -3446,6 +3678,7 @@ export default class VelociousConfiguration {
3446
3678
  async withDatabaseIdentifierConnections({callback, dbs, identifiers, name, stackLabel}) {
3447
3679
  const stack = Error().stack
3448
3680
  const actualCallback = async () => {
3681
+ this.assertDatabaseAccessAllowed()
3449
3682
  return await withTrackedStack(stack || stackLabel, async () => {
3450
3683
  return await callback(dbs)
3451
3684
  })
@@ -3479,6 +3712,7 @@ export default class VelociousConfiguration {
3479
3712
  * @returns {Record<string, import("./database/drivers/base.js").default>} A map of database connections with identifier as key
3480
3713
  */
3481
3714
  getCurrentConnections(databaseIdentifiers = this.getDatabaseIdentifiers()) {
3715
+ this.assertDatabaseAccessAllowed()
3482
3716
  /**
3483
3717
  * Dbs.
3484
3718
  * @type {{[key: string]: import("./database/drivers/base.js").default}} */
@@ -3568,6 +3802,7 @@ export default class VelociousConfiguration {
3568
3802
  * @returns {Promise<T>} - Resolves with the callback result.
3569
3803
  */
3570
3804
  async ensureConnections(optionsOrCallback, callback) {
3805
+ this.assertDatabaseAccessAllowed()
3571
3806
  const {
3572
3807
  callback: actualWithConnectionsCallback,
3573
3808
  databaseIdentifiers,