velocious 1.0.594 → 1.0.595

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.
@@ -1456,14 +1456,15 @@ export default class TestRunner {
1456
1456
  * @type {Record<ConsoleMethodName, (...args: Array<ReturnType<typeof JSON.parse>>) => void>} */
1457
1457
  const consoleObject = /** @type {Record<ConsoleMethodName, (...args: Array<ReturnType<typeof JSON.parse>>) => void>} */ (console)
1458
1458
  /**
1459
- * Original console methods.
1459
+ * Original console methods captured as direct references so stopping restores
1460
+ * the exact method that was installed at capture start.
1460
1461
  * @type {Record<ConsoleMethodName, (...args: Array<ReturnType<typeof JSON.parse>>) => void>} */
1461
1462
  const originalConsoleMethods = {
1462
- debug: consoleObject.debug.bind(console),
1463
- error: consoleObject.error.bind(console),
1464
- info: consoleObject.info.bind(console),
1465
- log: consoleObject.log.bind(console),
1466
- warn: consoleObject.warn.bind(console)
1463
+ debug: consoleObject.debug,
1464
+ error: consoleObject.error,
1465
+ info: consoleObject.info,
1466
+ log: consoleObject.log,
1467
+ warn: consoleObject.warn
1467
1468
  }
1468
1469
  let stopped = false
1469
1470
  let outputText = ""
@@ -1473,7 +1474,7 @@ export default class TestRunner {
1473
1474
  lines.push(`[${new Date().toISOString()}] [${methodName}] ${format(...args)}`)
1474
1475
 
1475
1476
  if (passthrough) {
1476
- originalConsoleMethods[methodName](...args)
1477
+ originalConsoleMethods[methodName].apply(consoleObject, args)
1477
1478
  }
1478
1479
  }
1479
1480
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "velocious": "build/bin/velocious.js"
4
4
  },
5
5
  "name": "velocious",
6
- "version": "1.0.594",
6
+ "version": "1.0.595",
7
7
  "main": "build/index.js",
8
8
  "types": "build/index.d.ts",
9
9
  "files": [
@@ -27,6 +27,7 @@
27
27
  "benchmark:http-client-content-length-byte-length": "node benchmark/http-client-content-length-byte-length.js",
28
28
  "benchmark:frontend-model-serialization-resource-metadata": "node benchmark/frontend-model-serialization-resource-metadata.js",
29
29
  "benchmark:sql-diagnostic-scans": "node --expose-gc benchmark/sql-diagnostic-scans.js",
30
+ "benchmark:test-mode-diagnostics": "node --expose-gc benchmark/test-mode-diagnostics.js",
30
31
  "build": "node scripts/clean-build.js && npm run compile",
31
32
  "compile": "tsc -b && npm run copy:js && npm run copy:ejs && npm run copy:templates && node scripts/ensure-bin-executable.js",
32
33
  "copy:js": "cpy \"index.js\" build && cpy \"bin/**/*.js\" build/bin && cpy \"src/**/*.js\" build --parents",
@@ -7,6 +7,7 @@ import { createRequire } from "node:module"
7
7
  import path from "node:path"
8
8
  import { promisify } from "node:util"
9
9
  import timeout from "awaitery/build/timeout.js"
10
+ import SystemTest from "system-testing/build/system-test.js"
10
11
 
11
12
  /**
12
13
  * @typedef {object} BrowserTestChromeRuntime
@@ -29,6 +30,14 @@ import timeout from "awaitery/build/timeout.js"
29
30
  * @property {() => Promise<string>} diagnostics - Reads retained ChromeDriver diagnostics.
30
31
  */
31
32
 
33
+ /**
34
+ * @typedef {object} BrowserTestSystemTestConfig
35
+ * @property {boolean} debug - Whether SystemTest should emit debug output.
36
+ * @property {{type: "selenium", options: {chromeArguments: string[], chromeBinaryPath: string}}} driver - Selenium driver config.
37
+ * @property {string} httpHost - HTTP host for the browser-test app.
38
+ * @property {number} httpPort - HTTP port for the browser-test app.
39
+ */
40
+
32
41
  const require = createRequire(import.meta.url)
33
42
  const { binaryPaths } = require("selenium-webdriver/common/seleniumManager.js")
34
43
  const { CancellationError, waitForServer } = require("selenium-webdriver/http/util.js")
@@ -185,6 +194,57 @@ export async function loadOrPrewarmBrowserTestChromeRuntime() {
185
194
  }
186
195
  }
187
196
 
197
+ /**
198
+ * Formatted default Chrome launch arguments for every browser-test session. Mirrors the
199
+ * Selenium driver defaults used by system-testing so supplying the list does not drop the
200
+ * headless container requirements, and adds pipe-mode devtools transport, which ChromeDriver
201
+ * recommends over the injected `--remote-debugging-port` and which keeps the Chrome instance
202
+ * alive until a session is created on headless container CI.
203
+ * @returns {string[]} - Chrome launch arguments.
204
+ */
205
+ function browserTestChromeArguments() {
206
+ return [
207
+ "--disable-backgrounding-occluded-windows",
208
+ "--disable-background-timer-throttling",
209
+ "--disable-dev-shm-usage",
210
+ "--disable-gpu",
211
+ "--disable-renderer-backgrounding",
212
+ "--headless=new",
213
+ "--no-sandbox",
214
+ "--remote-debugging-pipe",
215
+ "--window-size=1920,1080"
216
+ ]
217
+ }
218
+
219
+ /**
220
+ * Builds the SystemTest factory used by BrowserTestSession so the Selenium builder path
221
+ * receives the pinned Chrome binary and the required headless container Chrome arguments.
222
+ * @param {object} [args] - Factory options.
223
+ * @param {string[]} [args.chromeArguments] - Chrome launch arguments.
224
+ * @param {boolean} [args.debug] - Whether SystemTest should emit debug output.
225
+ * @param {string} [args.httpHost] - HTTP host for the browser-test app.
226
+ * @param {number} [args.httpPort] - HTTP port for the browser-test app.
227
+ * @param {(config: BrowserTestSystemTestConfig) => BrowserTestSystemTest} [args.systemTestCurrent] - SystemTest factory.
228
+ * @returns {(args: {browserPath: string, remoteUrl: string}) => BrowserTestSystemTest} - SystemTest factory.
229
+ */
230
+ export function buildBrowserTestSystemTestFactory({
231
+ chromeArguments = browserTestChromeArguments(),
232
+ debug = false,
233
+ httpHost = "127.0.0.1",
234
+ httpPort = 1984,
235
+ systemTestCurrent = SystemTest.current
236
+ } = {}) {
237
+ return ({browserPath}) => systemTestCurrent({
238
+ debug,
239
+ driver: {
240
+ type: "selenium",
241
+ options: {chromeArguments, chromeBinaryPath: browserPath}
242
+ },
243
+ httpHost,
244
+ httpPort
245
+ })
246
+ }
247
+
188
248
  /**
189
249
  * Formats a child process exit.
190
250
  * @param {{code: number | null, error?: Error, signal: NodeJS.Signals | null}} result - Exit result.
@@ -5,7 +5,6 @@ import path from "node:path"
5
5
  import {pathToFileURL} from "node:url"
6
6
  import {build} from "esbuild"
7
7
  import initSqlJs from "sql.js"
8
- import SystemTest from "system-testing/build/system-test.js"
9
8
  import Application from "../src/application.js"
10
9
  import Configuration from "../src/configuration.js"
11
10
  import BrowserEnvironmentHandler from "../src/environment-handlers/browser.js"
@@ -22,6 +21,7 @@ import dummyDirectory from "../spec/dummy/dummy-directory.js"
22
21
  import {withSourcePeerPackage} from "../src/environment-handlers/node/source-peer-package.js"
23
22
  import {
24
23
  BrowserTestSession,
24
+ buildBrowserTestSystemTestFactory,
25
25
  loadOrPrewarmBrowserTestChromeRuntime,
26
26
  stopBrowserTestSession
27
27
  } from "./browser-test-session.js"
@@ -405,12 +405,8 @@ async function runBrowserTests() {
405
405
  backendApplication = await startBrowserBackendServer(backendConfiguration, browserBackendPort)
406
406
  browserTestSession = new BrowserTestSession({
407
407
  runtime: chromeRuntime,
408
- systemTestFactory: ({browserPath}) => SystemTest.current({
408
+ systemTestFactory: buildBrowserTestSystemTestFactory({
409
409
  debug: process.env.SYSTEM_TEST_DEBUG === "true",
410
- driver: {
411
- type: "selenium",
412
- options: {chromeBinaryPath: browserPath}
413
- },
414
410
  httpHost: systemTestHttpHost,
415
411
  httpPort: systemTestHttpPort
416
412
  })
@@ -364,6 +364,15 @@ export default class VelociousConfiguration {
364
364
  */
365
365
  this._websocketChannelSubscriptions = new Map()
366
366
 
367
+ /**
368
+ * In-flight local (per-process) websocket channel broadcast deliveries,
369
+ * launched fire-and-forget from `_broadcastToChannelLocal` so one slow
370
+ * subscriber never blocks another. Tracked here so
371
+ * `awaitPendingBroadcasts` can snapshot and drain them before settling.
372
+ * Settled deliveries are removed by the tracking-level cleanup.
373
+ * @type {Set<Promise<void>>} */
374
+ this._localBroadcastDeliveries = new Set()
375
+
367
376
  /**
368
377
  * Stores the websocket sessions value.
369
378
  * @type {Set<import("./http-server/client/websocket-session.js").default>} - Live websocket sessions, including paused sessions within the grace window.
@@ -2810,8 +2819,13 @@ export default class VelociousConfiguration {
2810
2819
  const websocketEvents = this._websocketEvents
2811
2820
 
2812
2821
  if (websocketEvents && typeof websocketEvents.awaitPendingBroadcasts === "function") {
2822
+ // Drain the host/worker publish queues (including event-log persistence)
2823
+ // before draining local deliveries, because host dispatch launches the
2824
+ // local deliveries synchronously and they must be part of the snapshot.
2813
2825
  await websocketEvents.awaitPendingBroadcasts()
2814
2826
  }
2827
+
2828
+ await this._awaitLocalBroadcastDeliveries()
2815
2829
  }
2816
2830
 
2817
2831
  /**
@@ -2845,7 +2859,7 @@ export default class VelociousConfiguration {
2845
2859
 
2846
2860
  if (!matches) continue
2847
2861
 
2848
- void this.withoutCurrentConnectionContexts(() => {
2862
+ const delivery = this.withoutCurrentConnectionContexts(() => {
2849
2863
  return Promise
2850
2864
  .resolve()
2851
2865
  .then(() => this._deliverWebsocketChannelBroadcast(subscription, body, {eventId: meta?.eventId}))
@@ -2853,9 +2867,35 @@ export default class VelociousConfiguration {
2853
2867
  console.error(`broadcastToChannel: ${name} subscription ${subscription.subscriptionId} deliverBroadcast threw`, error)
2854
2868
  })
2855
2869
  })
2870
+
2871
+ // Keep the fire-and-forget delivery (never awaited at broadcast time) but
2872
+ // track it so `awaitPendingBroadcasts` can drain it before settling. Remove
2873
+ // on settle; the failure handler also satisfies the promise so a rejected
2874
+ // delivery never becomes an unhandled rejection.
2875
+ this._localBroadcastDeliveries.add(delivery)
2876
+
2877
+ delivery.then(
2878
+ () => { this._localBroadcastDeliveries.delete(delivery) },
2879
+ () => { this._localBroadcastDeliveries.delete(delivery) }
2880
+ )
2856
2881
  }
2857
2882
  }
2858
2883
 
2884
+ /**
2885
+ * Awaits a snapshot of the in-flight local (per-process) websocket channel
2886
+ * broadcast deliveries. Called from `awaitPendingBroadcasts` after the host
2887
+ * publish queues drain, so every delivery those queues launched is captured.
2888
+ * New deliveries enqueued after the snapshot are not awaited. Individual
2889
+ * delivery errors are isolated per subscriber — the delivery chain already
2890
+ * logs them and resolves — so a snapshotted rejection never fails this barrier.
2891
+ * @returns {Promise<void>}
2892
+ */
2893
+ async _awaitLocalBroadcastDeliveries() {
2894
+ const snapshot = [...this._localBroadcastDeliveries]
2895
+
2896
+ await Promise.allSettled(snapshot)
2897
+ }
2898
+
2859
2899
  /**
2860
2900
  * Runs deliver websocket channel broadcast.
2861
2901
  * @param {import("./http-server/websocket-channel.js").default} subscription - Channel subscription.
@@ -1456,14 +1456,15 @@ export default class TestRunner {
1456
1456
  * @type {Record<ConsoleMethodName, (...args: Array<ReturnType<typeof JSON.parse>>) => void>} */
1457
1457
  const consoleObject = /** @type {Record<ConsoleMethodName, (...args: Array<ReturnType<typeof JSON.parse>>) => void>} */ (console)
1458
1458
  /**
1459
- * Original console methods.
1459
+ * Original console methods captured as direct references so stopping restores
1460
+ * the exact method that was installed at capture start.
1460
1461
  * @type {Record<ConsoleMethodName, (...args: Array<ReturnType<typeof JSON.parse>>) => void>} */
1461
1462
  const originalConsoleMethods = {
1462
- debug: consoleObject.debug.bind(console),
1463
- error: consoleObject.error.bind(console),
1464
- info: consoleObject.info.bind(console),
1465
- log: consoleObject.log.bind(console),
1466
- warn: consoleObject.warn.bind(console)
1463
+ debug: consoleObject.debug,
1464
+ error: consoleObject.error,
1465
+ info: consoleObject.info,
1466
+ log: consoleObject.log,
1467
+ warn: consoleObject.warn
1467
1468
  }
1468
1469
  let stopped = false
1469
1470
  let outputText = ""
@@ -1473,7 +1474,7 @@ export default class TestRunner {
1473
1474
  lines.push(`[${new Date().toISOString()}] [${methodName}] ${format(...args)}`)
1474
1475
 
1475
1476
  if (passthrough) {
1476
- originalConsoleMethods[methodName](...args)
1477
+ originalConsoleMethods[methodName].apply(consoleObject, args)
1477
1478
  }
1478
1479
  }
1479
1480
  }