From 2eb770edf21fddc32b58f4780807f784ffb102fb Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Mon, 5 May 2025 17:38:16 -0700 Subject: [PATCH 1/6] some imrpovements to Bun.SQL, remove prepareStackTrace (conflicts https://github.com/DefinitelyTyped/DefinitelyTyped/commit/fb8b7354880fac2ba4f80e9486d3cc3d3c187075) --- packages/bun-types/bun.d.ts | 171 +++++++++++++++------- packages/bun-types/globals.d.ts | 7 - test/integration/bun-types/fixture/sql.ts | 22 +++ 3 files changed, 144 insertions(+), 56 deletions(-) create mode 100644 test/integration/bun-types/fixture/sql.ts diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 5ed9e31f906c60..adaa44fcc63a06 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -991,11 +991,9 @@ declare module "bun" { function fileURLToPath(url: URL | string): string; /** - * Fast incremental writer that becomes an `ArrayBuffer` on end(). + * Fast incremental writer that becomes an {@link ArrayBuffer} on end(). */ class ArrayBufferSink { - constructor(); - start(options?: { asUint8Array?: boolean; /** @@ -1320,7 +1318,8 @@ declare module "bun" { * } * }; */ - type SQLOptions = { + + interface SQLOptions { /** Connection URL (can be string or URL object) */ url?: URL | string; /** Database server hostname */ @@ -1369,27 +1368,33 @@ declare module "bun" { bigint?: boolean; /** Automatic creation of prepared statements, defaults to true */ prepare?: boolean; - }; + } /** * Represents a SQL query that can be executed, with additional control methods * Extends Promise to allow for async/await usage */ - interface SQLQuery extends Promise { + interface SQLQuery extends Promise { /** Indicates if the query is currently executing */ active: boolean; + /** Indicates if the query has been cancelled */ cancelled: boolean; + /** Cancels the executing query */ - cancel(): SQLQuery; + cancel(): SQLQuery; + /** Execute as a simple query, no parameters are allowed but can execute multiple commands separated by semicolons */ - simple(): SQLQuery; + simple(): SQLQuery; + /** Executes the query */ - execute(): SQLQuery; + execute(): SQLQuery; + /** Returns the raw query result */ - raw(): SQLQuery; + raw(): SQLQuery; + /** Returns only the values from the query result */ - values(): SQLQuery; + values(): SQLQuery; } /** @@ -1407,65 +1412,99 @@ declare module "bun" { * Main SQL client interface providing connection and transaction management */ interface SQL { - /** Creates a new SQL client instance - * @example - * const sql = new SQL("postgres://localhost:5432/mydb"); - * const sql = new SQL(new URL("postgres://localhost:5432/mydb")); - */ - new (connectionString: string | URL): SQL; - /** Creates a new SQL client instance with options - * @example - * const sql = new SQL("postgres://localhost:5432/mydb", { idleTimeout: 1000 }); - */ - new (connectionString: string | URL, options: SQLOptions): SQL; - /** Creates a new SQL client instance with options - * @example - * const sql = new SQL({ url: "postgres://localhost:5432/mydb", idleTimeout: 1000 }); - */ - new (options?: SQLOptions): SQL; - /** Executes a SQL query using template literals + /** + * Executes a SQL query using template literals * @example + * ```ts * const [user] = await sql`select * from users where id = ${1}`; + * ``` */ - (strings: string | TemplateStringsArray, ...values: any[]): SQLQuery; + (strings: string[] | TemplateStringsArray, ...values: any[]): SQLQuery; + /** * Helper function to allow easy use to insert values into a query * @example + * ```ts * const result = await sql`insert into users ${sql(users)} RETURNING *`; + * ``` */ (obj: any): SQLQuery; - /** Commits a distributed transaction also know as prepared transaction in postgres or XA transaction in MySQL + + /** + * Commits a distributed transaction also know as prepared transaction in postgres or XA transaction in MySQL + * + * @param name - The name of the distributed transaction + * * @example + * ```ts * await sql.commitDistributed("my_distributed_transaction"); + * ``` */ commitDistributed(name: string): Promise; - /** Rolls back a distributed transaction also know as prepared transaction in postgres or XA transaction in MySQL + + /** + * Rolls back a distributed transaction also know as prepared transaction in postgres or XA transaction in MySQL + * + * @param name - The name of the distributed transaction + * * @example + * ```ts * await sql.rollbackDistributed("my_distributed_transaction"); + * ``` */ rollbackDistributed(name: string): Promise; + /** Waits for the database connection to be established + * * @example + * ```ts * await sql.connect(); + * ``` */ connect(): Promise; - /** Closes the database connection with optional timeout in seconds. If timeout is 0, it will close immediately, if is not provided it will wait for all queries to finish before closing. + + /** + * Closes the database connection with optional timeout in seconds. If timeout is 0, it will close immediately, if is not provided it will wait for all queries to finish before closing. + * + * @param options - The options for the close + * * @example + * ```ts * await sql.close({ timeout: 1 }); + * ``` */ close(options?: { timeout?: number }): Promise; - /** Closes the database connection with optional timeout in seconds. If timeout is 0, it will close immediately, if is not provided it will wait for all queries to finish before closing. - * @alias close + + /** + * Closes the database connection with optional timeout in seconds. If timeout is 0, it will close immediately, if is not provided it will wait for all queries to finish before closing. + * This is an alias of {@link SQL.close} + * + * @param options - The options for the close + * * @example + * ```ts * await sql.end({ timeout: 1 }); + * ``` */ end(options?: { timeout?: number }): Promise; - /** Flushes any pending operations */ + + /** + * Flushes any pending operations + * + * @example + * ```ts + * await sql.flush(); + * ``` + */ flush(): void; - /** The reserve method pulls out a connection from the pool, and returns a client that wraps the single connection. - * This can be used for running queries on an isolated connection. - * Calling reserve in a reserved Sql will return a new reserved connection, not the same connection (behavior matches postgres package). + + /** + * The reserve method pulls out a connection from the pool, and returns a client that wraps the single connection. + * This can be used for running queries on an isolated connection. + * Calling reserve in a reserved Sql will return a new reserved connection, not the same connection (behavior matches postgres package). + * * @example + * ```ts * const reserved = await sql.reserve(); * await reserved`select * from users`; * await reserved.release(); @@ -1476,12 +1515,14 @@ declare module "bun" { * } finally { * await reserved.release(); * } - * //To make it simpler bun supportsSymbol.dispose and Symbol.asyncDispose + * + * // Bun supports Symbol.dispose and Symbol.asyncDispose * { - * // always release after context (safer) - * using reserved = await sql.reserve() - * await reserved`select * from users` + * // always release after context (safer) + * using reserved = await sql.reserve() + * await reserved`select * from users` * } + * ``` */ reserve(): Promise; /** Begins a new transaction @@ -1626,6 +1667,45 @@ declare module "bun" { [Symbol.asyncDispose](): Promise; } + const SQL: { + /** + * Creates a new SQL client instance + * + * @param connectionString - The connection string for the SQL client + * + * @example + * ```ts + * const sql = new SQL("postgres://localhost:5432/mydb"); + * const sql = new SQL(new URL("postgres://localhost:5432/mydb")); + * ``` + */ + new (connectionString: string | URL): SQL; + + /** + * Creates a new SQL client instance with options + * + * @param connectionString - The connection string for the SQL client + * @param options - The options for the SQL client + * + * @example + * ```ts + * const sql = new SQL("postgres://localhost:5432/mydb", { idleTimeout: 1000 }); + * ``` + */ + new (connectionString: string | URL, options: Omit): SQL; + + /** + * Creates a new SQL client instance with options + * + * @param options - The options for the SQL client + * + * @example + * ```ts + * const sql = new SQL({ url: "postgres://localhost:5432/mydb", idleTimeout: 1000 }); + * ``` + */ + new (options?: SQLOptions): SQL; + }; /** * Represents a reserved connection from the connection pool @@ -1706,13 +1786,6 @@ declare module "bun" { */ var postgres: SQL; - /** - * The SQL constructor - * - * @category Database - */ - var SQL: SQL; - /** * Generate and verify CSRF tokens * diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts index ce6b5c558cb186..db253bdefcb3be 100644 --- a/packages/bun-types/globals.d.ts +++ b/packages/bun-types/globals.d.ts @@ -954,13 +954,6 @@ interface ErrorConstructor { */ captureStackTrace(targetObject: object, constructorOpt?: Function): void; - /** - * Optional override for formatting stack traces - * - * @see https://v8.dev/docs/stack-trace-api#customizing-stack-traces - */ - prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined; - /** * The maximum number of stack frames to capture. */ diff --git a/test/integration/bun-types/fixture/sql.ts b/test/integration/bun-types/fixture/sql.ts new file mode 100644 index 00000000000000..957b65f8aa62e9 --- /dev/null +++ b/test/integration/bun-types/fixture/sql.ts @@ -0,0 +1,22 @@ +{ + const postgres = new Bun.SQL(); + const id = 1; + await postgres`select * from users where id = ${id}`; +} + +{ + const postgres = new Bun.SQL("postgres://localhost:5432/mydb"); + const id = 1; + await postgres`select * from users where id = ${id}`; +} + +{ + const postgres = new Bun.SQL({ url: "postgres://localhost:5432/mydb" }); + const id = 1; + await postgres`select * from users where id = ${id}`; +} + +{ + const postgres = new Bun.SQL(); + postgres("ok"); +} From f3e9be753dbb772cc814e91d95ff21a288386b92 Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Mon, 5 May 2025 17:48:57 -0700 Subject: [PATCH 2/6] some test cases for sql --- test/integration/bun-types/fixture/sql.ts | 139 ++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/test/integration/bun-types/fixture/sql.ts b/test/integration/bun-types/fixture/sql.ts index 957b65f8aa62e9..fc592226e20b91 100644 --- a/test/integration/bun-types/fixture/sql.ts +++ b/test/integration/bun-types/fixture/sql.ts @@ -1,3 +1,5 @@ +import { expectAssignable, expectNotAssignable, expectType } from "./utilities"; + { const postgres = new Bun.SQL(); const id = 1; @@ -20,3 +22,140 @@ const postgres = new Bun.SQL(); postgres("ok"); } + +const sql1 = new Bun.SQL(); +const sql2 = new Bun.SQL("postgres://localhost:5432/mydb"); +const sql3 = new Bun.SQL(new URL("postgres://localhost:5432/mydb")); +const sql4 = new Bun.SQL({ url: "postgres://localhost:5432/mydb", idleTimeout: 1000 }); + +const query1 = sql1`SELECT * FROM users WHERE id = ${1}`; +const query2 = sql2({ foo: "bar" }); + +query1.cancel().simple().execute().raw().values(); + +const _promise: Promise = query1; + +sql1.connect(); +sql1.close(); +sql1.end(); +sql1.flush(); + +const reservedPromise: Promise = sql1.reserve(); + +sql1.begin(async txn => { + txn`SELECT 1`; + await txn.savepoint("sp", async sp => { + sp`SELECT 2`; + }); +}); + +sql1.transaction(async txn => { + txn`SELECT 3`; +}); + +sql1.begin("read write", async txn => { + txn`SELECT 4`; +}); + +sql1.transaction("read write", async txn => { + txn`SELECT 5`; +}); + +sql1.beginDistributed("foo", async txn => { + txn`SELECT 6`; +}); + +sql1.distributed("bar", async txn => { + txn`SELECT 7`; +}); + +sql1.unsafe("SELECT * FROM users"); +sql1.file("query.sql", [1, 2, 3]); + +sql1.reserve().then(reserved => { + reserved.release(); + reserved[Symbol.dispose]?.(); + reserved`SELECT 8`; +}); + +sql1.begin(async txn => { + txn.savepoint("sp", async sp => { + sp`SELECT 9`; + }); +}); + +sql1.begin(async txn => { + txn.savepoint(async sp => { + sp`SELECT 10`; + }); +}); + +// @ts-expect-error +sql1.commitDistributed(); + +// @ts-expect-error +sql1.rollbackDistributed(); + +// @ts-expect-error +sql1.file(123); + +// @ts-expect-error +sql1.unsafe(123); + +// @ts-expect-error +sql1.begin("read write", 123); + +// @ts-expect-error +sql1.transaction("read write", 123); + +const sqlQueryAny: Bun.SQLQuery = {} as any; +const sqlQueryNumber: Bun.SQLQuery = {} as any; +const sqlQueryString: Bun.SQLQuery = {} as any; + +expectAssignable>(sqlQueryAny); +expectAssignable>(sqlQueryNumber); +expectAssignable>(sqlQueryString); + +expectAssignable>(sqlQueryNumber); +expectAssignable>(sqlQueryString); + +expectNotAssignable>(sqlQueryString); +expectAssignable>(sqlQueryNumber); + +const sql = new Bun.SQL(); +const queryA = sql`SELECT 1`; +expectType(queryA).is(); +const queryB = sql({ foo: "bar" }); +expectType(queryB).is(); + +expectNotAssignable>(sqlQueryAny); +expectNotAssignable>(sqlQueryNumber); + +expectType(sql).is(); + +const opts2: Bun.SQLOptions = { url: "postgres://localhost" }; +expectType(opts2).is(); + +const txCb: Bun.SQLTransactionContextCallback = async sql => [sql`SELECT 1`]; +const spCb: Bun.SQLSavepointContextCallback = async sql => [sql`SELECT 2`]; +expectType(txCb).is(); +expectType(spCb).is(); + +expectType(queryA.cancel()).is(); +expectType(queryA.simple()).is(); +expectType(queryA.execute()).is(); +expectType(queryA.raw()).is(); +expectType(queryA.values()).is(); + +declare const queryNum: Bun.SQLQuery; +expectType(queryNum.cancel()).is>(); +expectType(queryNum.simple()).is>(); +expectType(queryNum.execute()).is>(); +expectType(queryNum.raw()).is>(); +expectType(queryNum.values()).is>(); + +expectType(await queryNum.cancel()).is(); +expectType(await queryNum.simple()).is(); +expectType(await queryNum.execute()).is(); +expectType(await queryNum.raw()).is(); +expectType(await queryNum.values()).is(); From 5a8ec6f1b8ee8b399c13dcb0b2d34fbf8edbb658 Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Mon, 5 May 2025 17:59:24 -0700 Subject: [PATCH 3/6] fix issue described in #18519 --- packages/bun-types/bun.d.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index adaa44fcc63a06..ef090c7c6c98e4 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -1422,13 +1422,31 @@ declare module "bun" { (strings: string[] | TemplateStringsArray, ...values: any[]): SQLQuery; /** - * Helper function to allow easy use to insert values into a query + * Helper function for inserting an object into a query + * * @example * ```ts + * // Insert an object * const result = await sql`insert into users ${sql(users)} RETURNING *`; + * + * // Or pick specific columns + * const result = await sql`insert into users ${sql(users, "id", "name")} RETURNING *`; + * + * // Or a single object + * const result = await sql`insert into users ${sql(user)} RETURNING *`; + * ``` + */ + >(obj: T | T[], ...values: (keyof T)[]): SQLQuery; + + /** + * Helper function for inserting any serializable value into a query + * + * @example + * ```ts + * const result = await sql`SELECT * FROM users WHERE id IN ${sql([1, 2, 3])}`; * ``` */ - (obj: any): SQLQuery; + (obj: unknown): SQLQuery; /** * Commits a distributed transaction also know as prepared transaction in postgres or XA transaction in MySQL From 4a1c0bd38d0313a01a6623bd80293b627eedc74b Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Mon, 5 May 2025 18:11:29 -0700 Subject: [PATCH 4/6] some more sql changes + tests --- packages/bun-types/bun.d.ts | 6 ++-- test/integration/bun-types/fixture/sql.ts | 38 ++++++++++++++++++++++- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index ef090c7c6c98e4..e0c0a12f6b39c1 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -1436,7 +1436,7 @@ declare module "bun" { * const result = await sql`insert into users ${sql(user)} RETURNING *`; * ``` */ - >(obj: T | T[], ...values: (keyof T)[]): SQLQuery; + (obj: T | T[] | readonly T[], ...columns: (keyof T)[]): SQLQuery; /** * Helper function for inserting any serializable value into a query @@ -1795,14 +1795,14 @@ declare module "bun" { * * @category Database */ - var sql: SQL; + const sql: SQL; /** * SQL client for PostgreSQL * * @category Database */ - var postgres: SQL; + const postgres: SQL; /** * Generate and verify CSRF tokens diff --git a/test/integration/bun-types/fixture/sql.ts b/test/integration/bun-types/fixture/sql.ts index fc592226e20b91..1e44dea124eb82 100644 --- a/test/integration/bun-types/fixture/sql.ts +++ b/test/integration/bun-types/fixture/sql.ts @@ -1,3 +1,4 @@ +import { sql } from "bun"; import { expectAssignable, expectNotAssignable, expectType } from "./utilities"; { @@ -122,7 +123,6 @@ expectAssignable>(sqlQueryString); expectNotAssignable>(sqlQueryString); expectAssignable>(sqlQueryNumber); -const sql = new Bun.SQL(); const queryA = sql`SELECT 1`; expectType(queryA).is(); const queryB = sql({ foo: "bar" }); @@ -159,3 +159,39 @@ expectType(await queryNum.simple()).is(); expectType(await queryNum.execute()).is(); expectType(await queryNum.raw()).is(); expectType(await queryNum.values()).is(); + +const _sqlInstance: Bun.SQL = Bun.sql; + +expectType(sql({ name: "Alice", email: "alice@example.com" })).is(); + +expectType( + sql([ + { name: "Alice", email: "alice@example.com" }, + { name: "Bob", email: "bob@example.com" }, + ]), +).is(); + +const user = { name: "Alice", email: "alice@example.com", age: 25 }; +expectType(sql(user, "name", "email")).is(); + +const users = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, +]; +expectType(sql(users, "id")).is(); + +expectType(sql([1, 2, 3])).is(); + +expectType(sql("users")).is(); + +// @ts-expect-error - missing key in object +sql(user, "notAKey"); + +// @ts-expect-error - wrong type for key argument +sql(user, 123); + +// @ts-expect-error - array of objects, missing key +sql(users, "notAKey"); + +// @ts-expect-error - array of numbers, extra key argument +sql([1, 2, 3], "notAKey"); From 801714eef039f7f45d473b64fbd10c1edece7b3f Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Mon, 5 May 2025 19:10:10 -0700 Subject: [PATCH 5/6] Update packages/bun-types/bun.d.ts Co-authored-by: Ciro Spaciari --- packages/bun-types/bun.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index e0c0a12f6b39c1..afdf569432c233 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -1511,7 +1511,7 @@ declare module "bun" { * * @example * ```ts - * await sql.flush(); + * sql.flush(); * ``` */ flush(): void; From e7586d3b78d7eb83c04676ec75cc22b849bc70b6 Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Tue, 6 May 2025 14:41:35 -0700 Subject: [PATCH 6/6] rm broken assignability checks --- test/integration/bun-types/fixture/spawn.ts | 5 ----- test/integration/bun-types/fixture/sql.ts | 13 ++++--------- test/integration/bun-types/fixture/utilities.ts | 1 - 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/test/integration/bun-types/fixture/spawn.ts b/test/integration/bun-types/fixture/spawn.ts index 59ff8e4832c52c..b999e6be8a8a8c 100644 --- a/test/integration/bun-types/fixture/spawn.ts +++ b/test/integration/bun-types/fixture/spawn.ts @@ -179,16 +179,11 @@ function depromise(_promise: Promise): T { tsd.expectType(proc.stdin); } tsd.expectAssignable(Bun.spawn([], { stdio: ["pipe", "pipe", "pipe"] })); -tsd.expectNotAssignable(Bun.spawn([], { stdio: ["inherit", "inherit", "inherit"] })); tsd.expectAssignable(Bun.spawn([], { stdio: ["ignore", "pipe", "pipe"] })); tsd.expectAssignable(Bun.spawn([], { stdio: ["pipe", "pipe", "pipe"] })); -tsd.expectNotAssignable(Bun.spawn([], { stdio: ["pipe", "ignore", "pipe"] })); tsd.expectAssignable(Bun.spawn([], { stdio: ["pipe", "pipe", "pipe"] })); tsd.expectAssignable(Bun.spawn([], { stdio: ["pipe", "ignore", "inherit"] })); -tsd.expectNotAssignable(Bun.spawn([], { stdio: ["ignore", "pipe", "pipe"] })); tsd.expectAssignable(Bun.spawn([], { stdio: ["ignore", "inherit", "ignore"] })); tsd.expectAssignable(Bun.spawn([], { stdio: [null, null, null] })); -tsd.expectNotAssignable(Bun.spawn([], {})); -tsd.expectNotAssignable(Bun.spawn([], {})); tsd.expectAssignable>(Bun.spawnSync([], {})); diff --git a/test/integration/bun-types/fixture/sql.ts b/test/integration/bun-types/fixture/sql.ts index 1e44dea124eb82..245d4d160272f6 100644 --- a/test/integration/bun-types/fixture/sql.ts +++ b/test/integration/bun-types/fixture/sql.ts @@ -1,5 +1,5 @@ import { sql } from "bun"; -import { expectAssignable, expectNotAssignable, expectType } from "./utilities"; +import { expectAssignable, expectType } from "./utilities"; { const postgres = new Bun.SQL(); @@ -117,20 +117,15 @@ expectAssignable>(sqlQueryAny); expectAssignable>(sqlQueryNumber); expectAssignable>(sqlQueryString); -expectAssignable>(sqlQueryNumber); -expectAssignable>(sqlQueryString); - -expectNotAssignable>(sqlQueryString); -expectAssignable>(sqlQueryNumber); +expectType(sqlQueryNumber).is>(); +expectType(sqlQueryString).is>(); +expectType(sqlQueryNumber).is>(); const queryA = sql`SELECT 1`; expectType(queryA).is(); const queryB = sql({ foo: "bar" }); expectType(queryB).is(); -expectNotAssignable>(sqlQueryAny); -expectNotAssignable>(sqlQueryNumber); - expectType(sql).is(); const opts2: Bun.SQLOptions = { url: "postgres://localhost" }; diff --git a/test/integration/bun-types/fixture/utilities.ts b/test/integration/bun-types/fixture/utilities.ts index d3b0d543f684cc..4a81e3acdf7db9 100644 --- a/test/integration/bun-types/fixture/utilities.ts +++ b/test/integration/bun-types/fixture/utilities.ts @@ -33,5 +33,4 @@ export function expectType(arg?: T) { } export declare const expectAssignable: (expression: T) => void; -export declare const expectNotAssignable: (expression: any) => void; export declare const expectTypeEquals: (expression: T extends S ? (S extends T ? true : false) : false) => void;