From 3448e2648f309c1fe032e2a8a95fc364e1959653 Mon Sep 17 00:00:00 2001 From: PsudoKit Date: Thu, 7 May 2026 16:01:34 +0530 Subject: [PATCH] Make ResultSet generic for typed row access ResultSet now accepts an optional type parameter so callers can express the expected row shape without casting: const { rows } = await client.execute("SELECT * FROM books"); const title: string = rows[0].title; // no cast needed The default is Row so existing code is unaffected. Fixes #218 --- packages/libsql-client/src/hrana.ts | 11 +++++---- packages/libsql-client/src/http.ts | 17 +++++++------- packages/libsql-client/src/sqlite3.ts | 33 +++++++++++++++------------ packages/libsql-client/src/ws.ts | 17 +++++++------- packages/libsql-core/src/api.ts | 21 ++++++++++------- 5 files changed, 57 insertions(+), 42 deletions(-) diff --git a/packages/libsql-client/src/hrana.ts b/packages/libsql-client/src/hrana.ts index 588796b..b31389d 100644 --- a/packages/libsql-client/src/hrana.ts +++ b/packages/libsql-client/src/hrana.ts @@ -2,6 +2,7 @@ import * as hrana from "@libsql/hrana-client"; import type { InStatement, ResultSet, + Row, Transaction, TransactionMode, InArgs, @@ -32,11 +33,13 @@ export abstract class HranaTransaction implements Transaction { abstract close(): void; abstract get closed(): boolean; - execute(stmt: InStatement): Promise { - return this.batch([stmt]).then((results) => results[0]); + execute(stmt: InStatement): Promise> { + return this.batch([stmt]).then((results) => results[0]); } - async batch(stmts: Array): Promise> { + async batch( + stmts: Array, + ): Promise>> { const stream = this._getStream(); if (stream.closed) { throw new LibsqlError( @@ -167,7 +170,7 @@ export abstract class HranaTransaction implements Transaction { throw mappedError; } } - return resultSets; + return resultSets as Array>; } catch (e) { throw mapHranaError(e); } diff --git a/packages/libsql-client/src/http.ts b/packages/libsql-client/src/http.ts index 72aa2c5..08d4cc6 100644 --- a/packages/libsql-client/src/http.ts +++ b/packages/libsql-client/src/http.ts @@ -4,6 +4,7 @@ import type { Config, Client } from "@libsql/core/api"; import type { InStatement, ResultSet, + Row, Transaction, IntMode, InArgs, @@ -114,10 +115,10 @@ export class HttpClient implements Client { return this.#promiseLimitFunction(fn); } - async execute( + async execute( stmtOrSql: InStatement | string, args?: InArgs, - ): Promise { + ): Promise> { let stmt: InStatement; if (typeof stmtOrSql === "string") { @@ -129,7 +130,7 @@ export class HttpClient implements Client { stmt = stmtOrSql; } - return this.limit(async () => { + return this.limit>(async () => { try { const hranaStmt = stmtToHrana(stmt); @@ -145,18 +146,18 @@ export class HttpClient implements Client { const rowsResult = await rowsPromise; - return resultSetFromHrana(rowsResult); + return resultSetFromHrana(rowsResult) as ResultSet; } catch (e) { throw mapHranaError(e); } }); } - async batch( + async batch( stmts: Array, mode: TransactionMode = "deferred", - ): Promise> { - return this.limit>(async () => { + ): Promise>> { + return this.limit>>(async () => { try { const normalizedStmts = stmts.map((stmt) => { if (Array.isArray(stmt)) { @@ -198,7 +199,7 @@ export class HttpClient implements Client { const results = await resultsPromise; - return results; + return results as Array>; } catch (e) { throw mapHranaError(e); } diff --git a/packages/libsql-client/src/sqlite3.ts b/packages/libsql-client/src/sqlite3.ts index b8a9ad8..dc5dbfe 100644 --- a/packages/libsql-client/src/sqlite3.ts +++ b/packages/libsql-client/src/sqlite3.ts @@ -122,10 +122,10 @@ export class Sqlite3Client implements Client { this.protocol = "file"; } - async execute( + async execute( stmtOrSql: InStatement | string, args?: InArgs, - ): Promise { + ): Promise> { let stmt: InStatement; if (typeof stmtOrSql === "string") { @@ -138,13 +138,13 @@ export class Sqlite3Client implements Client { } this.#checkNotClosed(); - return executeStmt(this.#getDb(), stmt, this.#intMode); + return executeStmt(this.#getDb(), stmt, this.#intMode) as ResultSet; } - async batch( + async batch( stmts: Array, mode: TransactionMode = "deferred", - ): Promise> { + ): Promise>> { this.#checkNotClosed(); const db = this.#getDb(); try { @@ -184,7 +184,7 @@ export class Sqlite3Client implements Client { } } executeStmt(db, "COMMIT", this.#intMode); - return resultSets; + return resultSets as Array>; } finally { if (db.inTransaction) { executeStmt(db, "ROLLBACK", this.#intMode); @@ -308,13 +308,18 @@ export class Sqlite3Transaction implements Transaction { this.#intMode = intMode; } - async execute(stmt: InStatement): Promise; - async execute(sql: string, args?: InArgs): Promise; + async execute( + stmt: InStatement, + ): Promise>; + async execute( + sql: string, + args?: InArgs, + ): Promise>; - async execute( + async execute( stmtOrSql: InStatement | string, args?: InArgs, - ): Promise { + ): Promise> { let stmt: InStatement; if (typeof stmtOrSql === "string") { @@ -327,12 +332,12 @@ export class Sqlite3Transaction implements Transaction { } this.#checkNotClosed(); - return executeStmt(this.#database, stmt, this.#intMode); + return executeStmt(this.#database, stmt, this.#intMode) as ResultSet; } - async batch( + async batch( stmts: Array, - ): Promise> { + ): Promise>> { const resultSets = []; for (let i = 0; i < stmts.length; i++) { try { @@ -361,7 +366,7 @@ export class Sqlite3Transaction implements Transaction { throw e; } } - return resultSets; + return resultSets as Array>; } async executeMultiple(sql: string): Promise { diff --git a/packages/libsql-client/src/ws.ts b/packages/libsql-client/src/ws.ts index e17fbb3..d6c01cb 100644 --- a/packages/libsql-client/src/ws.ts +++ b/packages/libsql-client/src/ws.ts @@ -6,6 +6,7 @@ import type { Client, Transaction, ResultSet, + Row, InStatement, InArgs, Replicated, @@ -154,10 +155,10 @@ export class WsClient implements Client { return this.#promiseLimitFunction(fn); } - async execute( + async execute( stmtOrSql: InStatement | string, args?: InArgs, - ): Promise { + ): Promise> { let stmt: InStatement; if (typeof stmtOrSql === "string") { @@ -169,7 +170,7 @@ export class WsClient implements Client { stmt = stmtOrSql; } - return this.limit(async () => { + return this.limit>(async () => { const streamState = await this.#openStream(); try { const hranaStmt = stmtToHrana(stmt); @@ -182,7 +183,7 @@ export class WsClient implements Client { const hranaRowsResult = await hranaRowsPromise; - return resultSetFromHrana(hranaRowsResult); + return resultSetFromHrana(hranaRowsResult) as ResultSet; } catch (e) { throw mapHranaError(e); } finally { @@ -191,11 +192,11 @@ export class WsClient implements Client { }); } - async batch( + async batch( stmts: Array, mode: TransactionMode = "deferred", - ): Promise> { - return this.limit>(async () => { + ): Promise>> { + return this.limit>>(async () => { const streamState = await this.#openStream(); try { const normalizedStmts = stmts.map((stmt) => { @@ -224,7 +225,7 @@ export class WsClient implements Client { const results = await resultsPromise; - return results; + return results as Array>; } catch (e) { throw mapHranaError(e); } finally { diff --git a/packages/libsql-core/src/api.ts b/packages/libsql-core/src/api.ts index 471eb22..2c34f1e 100644 --- a/packages/libsql-core/src/api.ts +++ b/packages/libsql-core/src/api.ts @@ -95,8 +95,11 @@ export interface Client { * }); * ``` */ - execute(stmt: InStatement): Promise; - execute(sql: string, args?: InArgs): Promise; + execute(stmt: InStatement): Promise>; + execute( + sql: string, + args?: InArgs, + ): Promise>; /** Execute a batch of SQL statements in a transaction. * @@ -131,10 +134,10 @@ export interface Client { * ], "write"); * ``` */ - batch( + batch( stmts: Array, mode?: TransactionMode, - ): Promise>; + ): Promise>>; /** Execute a batch of SQL statements in a transaction with PRAGMA foreign_keys=off; before and PRAGMA foreign_keys=on; after. * @@ -308,14 +311,16 @@ export interface Transaction { * }); * ``` */ - execute(stmt: InStatement): Promise; + execute(stmt: InStatement): Promise>; /** Execute a batch of SQL statements in this transaction. * * If any of the statements in the batch fails with an error, further statements are not executed and the * returned promise is rejected with an error, but the transaction is not rolled back. */ - batch(stmts: Array): Promise>; + batch( + stmts: Array, + ): Promise>>; /** Execute a sequence of SQL statements separated by semicolons. * @@ -404,7 +409,7 @@ export type TransactionMode = "write" | "read" | "deferred"; * console.log(`Deleted ${rs.rowsAffected} books`); * ``` */ -export interface ResultSet { +export interface ResultSet { /** Names of columns. * * Names of columns can be defined using the `AS` keyword in SQL: @@ -424,7 +429,7 @@ export interface ResultSet { columnTypes: Array; /** Rows produced by the statement. */ - rows: Array; + rows: Array; /** Number of rows that were affected by an UPDATE, INSERT or DELETE operation. *