Files
CherryHQ-cherry-studio/patches/@libsql__client@0.15.15.patch
fullex 8f9bf0b516 fix(data): patch @libsql/client to replay PRAGMAs after transaction()
`Sqlite3Client.transaction()` nullifies its internal connection
(`this.#db = null`), causing the next operation to create a new
connection with default PRAGMAs. This resets `synchronous` from
NORMAL back to FULL — roughly 2x write performance degradation.

Patch `@libsql/client@0.15.15` to add `setPragma()` which registers
per-connection PRAGMAs and replays them in `#getDb()` and `reconnect()`
whenever a new connection is created. Pattern borrowed from upstream
PR #328's ATTACH replay mechanism.

Update DbService and MigrationDbService to use `createClient()` +
`setPragma()` instead of `db.run(sql\`PRAGMA ...\`)` for per-connection
settings.

Upstream issues (still open, no official fix):
- tursodatabase/libsql-client-ts#229
- tursodatabase/libsql-client-ts#288

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-10 08:21:49 -07:00

136 lines
4.7 KiB
Diff

diff --git a/lib-cjs/sqlite3.js b/lib-cjs/sqlite3.js
index 8527e6190ea92dc2c832049bf2c8862e81f36c2b..be09d2dfd44b837ee98115b5d19bb653672e3e86 100644
--- a/lib-cjs/sqlite3.js
+++ b/lib-cjs/sqlite3.js
@@ -77,6 +77,7 @@ class Sqlite3Client {
#options;
#db;
#intMode;
+ #connectionPragmas = [];
closed;
protocol;
/** @private */
@@ -88,6 +89,15 @@ class Sqlite3Client {
this.closed = false;
this.protocol = "file";
}
+ setPragma(pragmaStmt) {
+ if (typeof pragmaStmt !== 'string' || !pragmaStmt.trim().toUpperCase().startsWith('PRAGMA')) {
+ throw new api_1.LibsqlError('setPragma() requires a PRAGMA statement string', 'PRAGMA_INVALID');
+ }
+ this.#connectionPragmas.push(pragmaStmt);
+ if (this.#db !== null) {
+ executeStmt(this.#db, pragmaStmt, this.#intMode);
+ }
+ }
async execute(stmtOrSql, args) {
let stmt;
if (typeof stmtOrSql === "string") {
@@ -181,6 +191,9 @@ class Sqlite3Client {
}
finally {
this.#db = new libsql_1.default(this.#path, this.#options);
+ for (const pragma of this.#connectionPragmas) {
+ executeStmt(this.#db, pragma, this.#intMode);
+ }
this.closed = false;
}
}
@@ -200,6 +213,9 @@ class Sqlite3Client {
#getDb() {
if (this.#db === null) {
this.#db = new libsql_1.default(this.#path, this.#options);
+ for (const pragma of this.#connectionPragmas) {
+ executeStmt(this.#db, pragma, this.#intMode);
+ }
}
return this.#db;
}
diff --git a/lib-esm/node.d.ts b/lib-esm/node.d.ts
index 3e82e6b41e65881f5e48b7dd221b0046f47862d8..76af5c20d63990e993f701ace7a696bcef77a495 100644
--- a/lib-esm/node.d.ts
+++ b/lib-esm/node.d.ts
@@ -1,5 +1,22 @@
import type { Config, Client } from "@libsql/core/api";
export * from "@libsql/core/api";
+
+/**
+ * Augment the Client interface with setPragma() from our patch.
+ *
+ * Sqlite3Client.setPragma() registers per-connection PRAGMAs that are
+ * replayed whenever a new connection is lazily created (e.g. after
+ * transaction()). Only Sqlite3Client implements this; HTTP/WS clients
+ * will throw at runtime.
+ *
+ * See patches/@libsql__client@0.15.15.patch for the implementation.
+ */
+declare module "@libsql/core/api" {
+ interface Client {
+ setPragma(pragmaStmt: string): void;
+ }
+}
+
/** Creates a {@link Client} object.
*
* You must pass at least an `url` in the {@link Config} object.
diff --git a/lib-esm/sqlite3.d.ts b/lib-esm/sqlite3.d.ts
index 77e075ab5f30e1401dbd8bd50f1759e98bafd89f..54f7f7c54ea139c00ba4247ef46a77087120dcb5 100644
--- a/lib-esm/sqlite3.d.ts
+++ b/lib-esm/sqlite3.d.ts
@@ -17,6 +17,7 @@ export declare class Sqlite3Client implements Client {
transaction(mode?: TransactionMode): Promise<Transaction>;
executeMultiple(sql: string): Promise<void>;
sync(): Promise<Replicated>;
+ setPragma(pragmaStmt: string): void;
reconnect(): Promise<void>;
close(): void;
}
diff --git a/lib-esm/sqlite3.js b/lib-esm/sqlite3.js
index d9a4f0136804059b31105e977eaeb953f521abdd..1ecdc599a26785489e163d2a9071ea1320a0473d 100644
--- a/lib-esm/sqlite3.js
+++ b/lib-esm/sqlite3.js
@@ -55,6 +55,7 @@ export class Sqlite3Client {
#options;
#db;
#intMode;
+ #connectionPragmas = [];
closed;
protocol;
/** @private */
@@ -66,6 +67,15 @@ export class Sqlite3Client {
this.closed = false;
this.protocol = "file";
}
+ setPragma(pragmaStmt) {
+ if (typeof pragmaStmt !== 'string' || !pragmaStmt.trim().toUpperCase().startsWith('PRAGMA')) {
+ throw new LibsqlError('setPragma() requires a PRAGMA statement string', 'PRAGMA_INVALID');
+ }
+ this.#connectionPragmas.push(pragmaStmt);
+ if (this.#db !== null) {
+ executeStmt(this.#db, pragmaStmt, this.#intMode);
+ }
+ }
async execute(stmtOrSql, args) {
let stmt;
if (typeof stmtOrSql === "string") {
@@ -159,6 +169,9 @@ export class Sqlite3Client {
}
finally {
this.#db = new Database(this.#path, this.#options);
+ for (const pragma of this.#connectionPragmas) {
+ executeStmt(this.#db, pragma, this.#intMode);
+ }
this.closed = false;
}
}
@@ -178,6 +191,9 @@ export class Sqlite3Client {
#getDb() {
if (this.#db === null) {
this.#db = new Database(this.#path, this.#options);
+ for (const pragma of this.#connectionPragmas) {
+ executeStmt(this.#db, pragma, this.#intMode);
+ }
}
return this.#db;
}