Fix silent exit when a query runs on a connection left idle by a prompt

An idle Bun.SQL connection does not keep the event loop alive. After an
inquirer prompt released stdin, the next query let Bun exit with code 0
mid-query: `schema` never applied confirmed destructive changes, and
`admin` table views exited instead of showing results. Hold the loop open
for the duration of every query and transaction.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-16 06:18:59 +01:00
co-authored by Claude Opus 5.5
parent 9b8aba639e
commit 688f4bcb58
6 changed files with 46 additions and 9 deletions
+7
View File
@@ -15,6 +15,13 @@ type Param = {
* and CRUD do not open a new connection per query.
*/
export declare function resolveClient(config?: BunMariaDBConfig): Bun.SQL | undefined;
/**
* Keep the event loop alive until `fn` settles.
* An idle Bun.SQL connection does not hold the process open, so a query sent
* on it after something else released the loop (e.g. an inquirer prompt
* releasing stdin) lets Bun exit silently mid-query.
*/
export declare function holdEventLoop<T>(fn: () => Promise<T>): Promise<T>;
/**
* # Main DB Handler Function
*/
+16 -1
View File
@@ -16,6 +16,21 @@ export function resolveClient(config) {
}
return grabMariaDBClient({ config });
}
/**
* Keep the event loop alive until `fn` settles.
* An idle Bun.SQL connection does not hold the process open, so a query sent
* on it after something else released the loop (e.g. an inquirer prompt
* releasing stdin) lets Bun exit silently mid-query.
*/
export async function holdEventLoop(fn) {
const timer = setInterval(() => { }, 1 << 30);
try {
return await fn();
}
finally {
clearInterval(timer);
}
}
/**
* Convert driver rows to plain objects.
* - BigInt -> number (or string when outside the safe integer range)
@@ -54,7 +69,7 @@ export default async function dbHandler({ query, values, config, client }) {
if (!CLIENT) {
throw new Error(`Couldn't grab MariaDB Client.`);
}
const res = await CLIENT.unsafe(query, values);
const res = await holdEventLoop(() => CLIENT.unsafe(query, values));
const count = res.count;
const res_array = normalizeRows(res);
const last_insert_id = res_array?.[0] ? res_array[0]?.id : undefined;
+3 -3
View File
@@ -1,4 +1,4 @@
import dbHandler, { resolveClient } from "../db-handler";
import dbHandler, { holdEventLoop, resolveClient } from "../db-handler";
import sqlGenerator from "../../utils/sql-generator";
import sanitizeHtmlFields from "../../utils/sanitize-html-fields";
import { quoteIdentifier } from "../../utils/sql-identifier";
@@ -62,7 +62,7 @@ export default async function DbUpdate({ table, data, query, targetId, config, }
* all in one transaction. Re-fetching by id still works when the update
* changes a column used in the WHERE clause.
*/
return await CLIENT.begin(async (tx) => {
return await holdEventLoop(() => CLIENT.begin(async (tx) => {
const matched_res = await dbHandler({
query: `SELECT id FROM ${quoteIdentifier(table)} ${whereClause} FOR UPDATE`,
values: sqlQueryObj.whereValues,
@@ -111,7 +111,7 @@ export default async function DbUpdate({ table, data, query, targetId, config, }
},
db_res: res.db_res,
};
});
}));
}
catch (error) {
if (error instanceof UpdateAbort) {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@moduletrace/bun-mariadb",
"version": "1.0.23",
"version": "1.0.24",
"description": "Schema-driven MariaDB manager for Bun",
"author": "Benjamin Toby",
"license": "MIT",
+16 -1
View File
@@ -42,6 +42,21 @@ export function resolveClient(config?: BunMariaDBConfig): Bun.SQL | undefined {
return grabMariaDBClient({ config });
}
/**
* Keep the event loop alive until `fn` settles.
* An idle Bun.SQL connection does not hold the process open, so a query sent
* on it after something else released the loop (e.g. an inquirer prompt
* releasing stdin) lets Bun exit silently mid-query.
*/
export async function holdEventLoop<T>(fn: () => Promise<T>): Promise<T> {
const timer = setInterval(() => {}, 1 << 30);
try {
return await fn();
} finally {
clearInterval(timer);
}
}
/**
* Convert driver rows to plain objects.
* - BigInt -> number (or string when outside the safe integer range)
@@ -88,7 +103,7 @@ export default async function dbHandler<
throw new Error(`Couldn't grab MariaDB Client.`);
}
const res = await CLIENT.unsafe(query, values);
const res = await holdEventLoop(() => CLIENT.unsafe(query, values));
const count = res.count;
+3 -3
View File
@@ -1,4 +1,4 @@
import dbHandler, { resolveClient } from "../db-handler";
import dbHandler, { holdEventLoop, resolveClient } from "../db-handler";
import type {
BunMariaDBConfig,
DBResponseObject,
@@ -103,7 +103,7 @@ export default async function DbUpdate<
* all in one transaction. Re-fetching by id still works when the update
* changes a column used in the WHERE clause.
*/
return await CLIENT.begin(async (tx) => {
return await holdEventLoop(() => CLIENT.begin(async (tx) => {
const matched_res = await dbHandler<{ id: number }>({
query: `SELECT id FROM ${quoteIdentifier(table)} ${whereClause} FOR UPDATE`,
values: sqlQueryObj.whereValues as any,
@@ -162,7 +162,7 @@ export default async function DbUpdate<
},
db_res: res.db_res,
} as DBResponseObject;
});
}));
} catch (error: any) {
if (error instanceof UpdateAbort) {
return error.response;