From 9b4b53739e6e77ba987d91d35fc1e365fcf3bcc0 Mon Sep 17 00:00:00 2001 From: Christopher Pruijsen Date: Sun, 13 Sep 2026 09:19:12 +0000 Subject: [PATCH] fix: preserve concurrency: 0 so it disables the limit as documented expandConfig normalized the option with `concurrency || 20`, which turns the documented "0 disables concurrency limits" into the default of 20. Use `??` so only a missing value falls back to the default. The sqlite3 client sizes its connection pool from the same config value; map a preserved 0 to an unbounded pool there too, matching the http and ws clients where promise-limit treats a falsy limit as pass-through. Fixes #351 --- .../src/__tests__/config.test.ts | 37 +++++++++++++++++++ packages/libsql-client/src/sqlite3.ts | 6 ++- packages/libsql-core/src/config.ts | 2 +- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/libsql-client/src/__tests__/config.test.ts b/packages/libsql-client/src/__tests__/config.test.ts index 30fd41f..9c86ae5 100644 --- a/packages/libsql-client/src/__tests__/config.test.ts +++ b/packages/libsql-client/src/__tests__/config.test.ts @@ -232,3 +232,40 @@ describe("expandConfig - parsing of valid arguments", () => { }); } }); + +describe("expandConfig - concurrency", () => { + const cases = [ + { + name: "default", + config: { url: "file://local.db" }, + concurrency: 20, + }, + { + name: "explicit limit", + config: { url: "file://local.db", concurrency: 42 }, + concurrency: 42, + }, + { + name: "zero disables the limit", + config: { url: "file://local.db", concurrency: 0 }, + concurrency: 0, + }, + { + name: "negative clamps to zero", + config: { url: "file://local.db", concurrency: -5 }, + concurrency: 0, + }, + { + name: "in-memory zero disables the limit", + config: { url: ":memory:", concurrency: 0 }, + concurrency: 0, + }, + ]; + for (const { name, config, concurrency } of cases) { + test(name, () => { + expect(expandConfig(config, false).concurrency).toEqual( + concurrency, + ); + }); + } +}); diff --git a/packages/libsql-client/src/sqlite3.ts b/packages/libsql-client/src/sqlite3.ts index 3044da3..be852dc 100644 --- a/packages/libsql-client/src/sqlite3.ts +++ b/packages/libsql-client/src/sqlite3.ts @@ -82,8 +82,12 @@ export function _createClient(config: ExpandedConfig): Client { // second connection would be a second, empty database rather than another // way into the same one. Each connection to an embedded replica carries // its own sync state. Both are therefore single-connection databases. + // A concurrency of 0 means no limit, matching the remote clients, whose + // promise-limit semaphore treats a falsy limit as pass-through. const maxConnections = - isInMemory || config.syncUrl ? 1 : Math.max(1, config.concurrency); + isInMemory || config.syncUrl + ? 1 + : Math.max(1, config.concurrency || Infinity); const options = { authToken: config.authToken, diff --git a/packages/libsql-core/src/config.ts b/packages/libsql-core/src/config.ts index 44dd7bf..cda770c 100644 --- a/packages/libsql-core/src/config.ts +++ b/packages/libsql-core/src/config.ts @@ -53,7 +53,7 @@ export function expandConfig( let { url, authToken, tls, intMode, concurrency } = config; // fill simple defaults right here - concurrency = Math.max(0, concurrency || 20); + concurrency = Math.max(0, concurrency ?? 20); intMode ??= "number"; let connectionQueryParams: string[] = []; // recognized query parameters which we sanitize through white list of valid key-value pairs