Summary
On the WebSocket transport, if the connection drops while a query is in flight, the query's promise never settles: no result, no error. The disconnect is reported to the connect callback, which already ran, so the pending command is never told. Unlike the TLS transport, there's also no timeout to fall back on.
Version: @sqlitecloud/drivers 1.0.878 (socket.io-client 4.8.3), with usewebsocket: true, in Node and in the browser.
Reproduce
The easiest trigger is a request over 1 MB, which the gateway drops (see the Context section):
const db = new Database({ host, apikey, database, usewebsocket: true })
await db.sql(`SELECT length(X'${'AB'.repeat(495_000)}') AS n`) // 0.99 MB → ok, ~0.7 s
await db.sql(`SELECT length(X'${'AB'.repeat(505_000)}') AS n`) // 1.01 MB → never resolves
In the second case the console logs SQLiteCloudConnection.connect - error connecting … SQLiteCloudError: Disconnected (ERR_CONNECTION_ENDED, cause transport error), but the db.sql(...) promise stays pending forever.
| Request size |
Result |
| 0.50 MB |
ok, 641 ms |
| 0.99 MB |
ok, 676 ms |
| 1.01 MB |
pending forever, connection closed |
| 1.50 MB |
pending forever, connection closed |
Any other drop mid-query (for example a ping timeout) goes through the same code path.
Cause (lib/drivers/connection-ws.js)
- Wrong callback:
connectTransport registers socket.on('disconnect', …) (line 94). On disconnect it calls close(), then the connect callback, which already ran on connect. Nothing reaches the command in flight.
- The pending query is never resolved:
transportCommands (line 133) waits on the ack of socket.emit('GET /v2/weblite/sql', …, ack). When the socket closes, that ack never fires and nothing else calls the command's callback. Because close() also calls this.operations.clear(), the queued done is dropped as well.
- No timeout: the TLS transport honours
config.timeout (default 300 s, connection-tls.js:134); the WebSocket transport has none.
Context: the 1 MB limit (a configuration decision, not a bug)
The drop at ~1 MB comes from the gateway. sqlitecloud-gateway src/gatewayWebsocket.ts:30 creates the socket.io Server without maxHttpBufferSize, so socket.io's default of 1,000,000 bytes applies, and engine.io closes any connection that sends a larger message. That limit is worth reconsidering:
- BLOBs sent as
X'…' hex double in size, so a ~490 KB file already exceeds it.
- A multi-statement transaction (for example a Studio Save of several rows) can reach it too.
Raising it to a deliberate value, e.g. 16–64 MB in line with the core's limits, and documenting it would help. This issue doesn't depend on that change: whatever the limit is, the driver should report the error instead of hanging.
Impact
The SQLite Cloud dashboard's Studio uses this transport. A save that includes a BLOB over ~490 KB, or any batch over 1 MB, shows "Saving…" forever. Nothing is written, but the user gets no error.
Summary
On the WebSocket transport, if the connection drops while a query is in flight, the query's promise never settles: no result, no error. The
disconnectis reported to the connect callback, which already ran, so the pending command is never told. Unlike the TLS transport, there's also no timeout to fall back on.Version:
@sqlitecloud/drivers1.0.878 (socket.io-client 4.8.3), withusewebsocket: true, in Node and in the browser.Reproduce
The easiest trigger is a request over 1 MB, which the gateway drops (see the Context section):
In the second case the console logs
SQLiteCloudConnection.connect - error connecting … SQLiteCloudError: Disconnected(ERR_CONNECTION_ENDED, causetransport error), but thedb.sql(...)promise stays pending forever.Any other drop mid-query (for example a
ping timeout) goes through the same code path.Cause (
lib/drivers/connection-ws.js)connectTransportregisterssocket.on('disconnect', …)(line 94). On disconnect it callsclose(), then the connect callback, which already ran onconnect. Nothing reaches the command in flight.transportCommands(line 133) waits on the ack ofsocket.emit('GET /v2/weblite/sql', …, ack). When the socket closes, that ack never fires and nothing else calls the command's callback. Becauseclose()also callsthis.operations.clear(), the queueddoneis dropped as well.config.timeout(default 300 s,connection-tls.js:134); the WebSocket transport has none.Context: the 1 MB limit (a configuration decision, not a bug)
The drop at ~1 MB comes from the gateway.
sqlitecloud-gatewaysrc/gatewayWebsocket.ts:30creates the socket.ioServerwithoutmaxHttpBufferSize, so socket.io's default of 1,000,000 bytes applies, and engine.io closes any connection that sends a larger message. That limit is worth reconsidering:X'…'hex double in size, so a ~490 KB file already exceeds it.Raising it to a deliberate value, e.g. 16–64 MB in line with the core's limits, and documenting it would help. This issue doesn't depend on that change: whatever the limit is, the driver should report the error instead of hanging.
Impact
The SQLite Cloud dashboard's Studio uses this transport. A save that includes a BLOB over ~490 KB, or any batch over 1 MB, shows "Saving…" forever. Nothing is written, but the user gets no error.