velocious 1.0.565 → 1.0.567

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 CHANGED
@@ -1180,6 +1180,8 @@ Evaluate inline JavaScript (Rails-style runner) with initialized app/database co
1180
1180
  npx velocious runner "const users = await db.query('SELECT COUNT(*) AS count FROM users'); console.log(users[0].count)"
1181
1181
  ```
1182
1182
 
1183
+ Successful CLI commands exit with status `0`. If command execution rejects, Velocious marks the process failed and always runs final database cleanup. Successful cleanup preserves and rethrows the original command error unchanged; if cleanup also fails, an `AggregateError` retains the command error first and as its `cause`, followed by the cleanup error. Cleanup-only failures also exit nonzero. These failures therefore remain nonzero even when application code installs an `uncaughtException` listener that reports and consumes the error. See [CLI process exit behavior](docs/cli.md#process-exit-behavior).
1184
+
1183
1185
  By default, migrations write `db/structure-<identifier>.sql` files for each database in non-test environments. Test skips these automatic writes unless you explicitly opt in. Configure allow/deny lists in your configuration:
1184
1186
 
1185
1187
  ```js
@@ -1568,7 +1570,7 @@ database: {
1568
1570
 
1569
1571
  Velocious includes a lightweight websocket entry point for API-style calls and server-side events.
1570
1572
 
1571
- Inbound frames remain ordered when TCP splits a frame across reads. Velocious limits a single final client data frame and a reassembled fragmented message to 16 MiB; larger payloads close the connection.
1573
+ Inbound frames remain ordered when TCP splits a frame across reads. Velocious limits a single final client data frame and a reassembled fragmented message to 16 MiB; larger payloads close the connection. Decoded inbound work is bounded independently per session before authorization or request handling: the defaults retain at most 256 active/queued messages or 16 MiB of raw UTF-8 payload bytes. Exact FIFO is preserved within the limits. A next message exceeding either limit is rejected before decoding and permanently closes that session with status `1008` and reason `Inbound message backlog exceeded`. Configure positive safe integers with `httpServer.websocketInboundQueue.maxPendingMessages` and `maxPendingBytes`.
1572
1574
 
1573
1575
  Server-to-client WebSocket delivery is bounded independently per client. The defaults retain at most 256 queued/in-flight completed frames or 16 MiB of serialized frame output; the HTTP 101 upgrade response and ordinary HTTP/file output remain outside that budget, while exact FIFO is preserved. A slow client that exceeds either limit is reported through the framework error events and deterministically closed without affecting other clients. V2 channel broadcast delivery and persistence remain isolated to the originating configuration when several applications share a process. Configure positive safe integers with `httpServer.websocketOutboundQueue.maxPendingFrames` and `maxPendingBytes`. See [WebSocket connections](docs/websocket-connections.md) for configuration and wire-protocol details.
1574
1576
 
@@ -1712,6 +1714,8 @@ configuration.getErrorEvents().on("all-error", ({error, errorType}) => {
1712
1714
 
1713
1715
  Genuinely unexpected frontend-model command failures reach this bus too. The frontend-model controller catches them to return a client-safe `Request failed.` response, but it also emits them as `framework-error`/`all-error` (with `context.frontendModelEndpoint === true`) so they are reported instead of being silently swallowed. Expected user-flow errors are excluded: validation failures are forwarded with their real message (for example `Name can't be blank`), invalid client query descriptors are returned as frontend-model query errors, and `error.velocious`-annotated / `safeToExpose` / `errorType`-marked errors keep their expected-error status — none of these reach the error bus.
1714
1716
 
1717
+ Unexpected inbound decoded WebSocket dispatch failures emit one `framework-error` and one matching `all-error`. Established expected client-flow errors remain excluded from both events.
1718
+
1715
1719
  ## Use the Websocket client API (HTTP-like)
1716
1720
 
1717
1721
  ```js
package/bin/velocious.js CHANGED
@@ -39,10 +39,37 @@ const cli = new Cli({
39
39
  processArgs
40
40
  })
41
41
 
42
+ let commandError
43
+ let commandFailed = false
44
+
42
45
  try {
43
46
  await cli.execute()
44
- } finally {
47
+ } catch (error) {
48
+ commandError = error
49
+ commandFailed = true
50
+ process.exitCode = 1
51
+ }
52
+
53
+ let cleanupError
54
+ let cleanupFailed = false
55
+
56
+ try {
45
57
  await configuration.closeDatabaseConnections()
58
+ } catch (error) {
59
+ cleanupError = error
60
+ cleanupFailed = true
61
+ process.exitCode = 1
62
+ }
63
+
64
+ if (commandFailed && cleanupFailed) {
65
+ throw new AggregateError(
66
+ [commandError, cleanupError],
67
+ "Velocious CLI command execution and database cleanup both failed",
68
+ {cause: commandError}
69
+ )
46
70
  }
47
71
 
72
+ if (commandFailed) throw commandError
73
+ if (cleanupFailed) throw cleanupError
74
+
48
75
  process.exit(0)
@@ -39,10 +39,37 @@ const cli = new Cli({
39
39
  processArgs
40
40
  })
41
41
 
42
+ let commandError
43
+ let commandFailed = false
44
+
42
45
  try {
43
46
  await cli.execute()
44
- } finally {
47
+ } catch (error) {
48
+ commandError = error
49
+ commandFailed = true
50
+ process.exitCode = 1
51
+ }
52
+
53
+ let cleanupError
54
+ let cleanupFailed = false
55
+
56
+ try {
45
57
  await configuration.closeDatabaseConnections()
58
+ } catch (error) {
59
+ cleanupError = error
60
+ cleanupFailed = true
61
+ process.exitCode = 1
62
+ }
63
+
64
+ if (commandFailed && cleanupFailed) {
65
+ throw new AggregateError(
66
+ [commandError, cleanupError],
67
+ "Velocious CLI command execution and database cleanup both failed",
68
+ {cause: commandError}
69
+ )
46
70
  }
47
71
 
72
+ if (commandFailed) throw commandError
73
+ if (cleanupFailed) throw cleanupError
74
+
48
75
  process.exit(0)
@@ -255,6 +255,7 @@
255
255
  * @property {boolean} [inProcess] - Run HTTP handlers in the main thread instead of worker threads.
256
256
  * @property {number} [maxWorkers] - Backward-compatible alias for workers.
257
257
  * @property {number} [port] - Port to bind the HTTP server to.
258
+ * @property {{maxPendingBytes?: number, maxPendingMessages?: number}} [websocketInboundQueue] - Per-session retained inbound WebSocket message limits.
258
259
  * @property {{maxPendingBytes?: number, maxPendingFrames?: number}} [websocketOutboundQueue] - Per-client retained outbound WebSocket frame limits.
259
260
  * @property {number} [workers] - Worker handlers to start for the HTTP server.
260
261
  */
@@ -114,6 +114,8 @@ function resolveBeaconUnreachableReportMs(value) {
114
114
  return 30_000
115
115
  }
116
116
 
117
+ const DEFAULT_WEBSOCKET_INBOUND_MAX_PENDING_BYTES = 16 * 1024 * 1024
118
+ const DEFAULT_WEBSOCKET_INBOUND_MAX_PENDING_MESSAGES = 256
117
119
  const DEFAULT_WEBSOCKET_OUTBOUND_MAX_PENDING_BYTES = 16 * 1024 * 1024
118
120
  const DEFAULT_WEBSOCKET_OUTBOUND_MAX_PENDING_FRAMES = 256
119
121
 
@@ -228,10 +230,15 @@ export default class VelociousConfiguration {
228
230
  * @type {Promise<void> | undefined}
229
231
  */
230
232
  this._initializePromise = undefined
233
+ const websocketInboundQueue = httpServer?.websocketInboundQueue
231
234
  const websocketOutboundQueue = httpServer?.websocketOutboundQueue
232
235
 
233
236
  this.httpServer = {
234
237
  ...(httpServer || {}),
238
+ websocketInboundQueue: {
239
+ maxPendingBytes: positiveSafeInteger(websocketInboundQueue?.maxPendingBytes, "httpServer.websocketInboundQueue.maxPendingBytes", DEFAULT_WEBSOCKET_INBOUND_MAX_PENDING_BYTES),
240
+ maxPendingMessages: positiveSafeInteger(websocketInboundQueue?.maxPendingMessages, "httpServer.websocketInboundQueue.maxPendingMessages", DEFAULT_WEBSOCKET_INBOUND_MAX_PENDING_MESSAGES)
241
+ },
235
242
  websocketOutboundQueue: {
236
243
  maxPendingBytes: positiveSafeInteger(websocketOutboundQueue?.maxPendingBytes, "httpServer.websocketOutboundQueue.maxPendingBytes", DEFAULT_WEBSOCKET_OUTBOUND_MAX_PENDING_BYTES),
237
244
  maxPendingFrames: positiveSafeInteger(websocketOutboundQueue?.maxPendingFrames, "httpServer.websocketOutboundQueue.maxPendingFrames", DEFAULT_WEBSOCKET_OUTBOUND_MAX_PENDING_FRAMES)
@@ -2370,6 +2377,19 @@ export default class VelociousConfiguration {
2370
2377
  */
2371
2378
  getWebsocketSessionHeartbeatSeconds() { return this._websocketSessionHeartbeatSeconds }
2372
2379
 
2380
+ /**
2381
+ * Gets per-session WebSocket inbound message queue limits.
2382
+ * @returns {{maxBytes: number, maxMessages: number}} - Per-session inbound queue high-water marks.
2383
+ */
2384
+ getWebsocketInboundQueueLimits() {
2385
+ const queue = this.httpServer.websocketInboundQueue
2386
+
2387
+ return {
2388
+ maxBytes: queue.maxPendingBytes,
2389
+ maxMessages: queue.maxPendingMessages
2390
+ }
2391
+ }
2392
+
2373
2393
  /**
2374
2394
  * Gets per-client WebSocket outbound queue limits.
2375
2395
  * @returns {{maxBytes: number, maxFrames: number}} - Per-client outbound queue high-water marks.