Add --sql-preconnect CLI flag for PostgreSQL startup connections (#21035)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: jarred-sumner-bot <220441119+jarred-sumner-bot@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
This commit is contained in:
jarred-sumner-bot
2025-07-14 15:05:30 -07:00
committed by GitHub
parent 70ebe75e6c
commit e9ccc81e03
6 changed files with 130 additions and 0 deletions

View File

@@ -274,6 +274,23 @@ If no connection URL is provided, the system checks for the following individual
| `PGPASSWORD` | - | (empty) | Database password |
| `PGDATABASE` | - | username | Database name |
## Runtime Preconnection
Bun can preconnect to PostgreSQL at startup to improve performance by establishing database connections before your application code runs. This is useful for reducing connection latency on the first database query.
```bash
# Enable PostgreSQL preconnection
bun --sql-preconnect index.js
# Works with DATABASE_URL environment variable
DATABASE_URL=postgres://user:pass@localhost:5432/db bun --sql-preconnect index.js
# Can be combined with other runtime flags
bun --sql-preconnect --hot index.js
```
The `--sql-preconnect` flag will automatically establish a PostgreSQL connection using your configured environment variables at startup. If the connection fails, it won't crash your application - the error will be handled gracefully.
## Connection Options
You can configure your database connection manually by passing options to the SQL constructor:

View File

@@ -308,6 +308,28 @@ pub const Run = struct {
}
}
do_postgres_preconnect: {
if (this.ctx.runtime_options.sql_preconnect) {
const global = vm.global;
const bun_object = vm.global.toJSValue().get(global, "Bun") catch |err| {
global.reportActiveExceptionAsUnhandled(err);
break :do_postgres_preconnect;
} orelse break :do_postgres_preconnect;
const sql_object = bun_object.get(global, "sql") catch |err| {
global.reportActiveExceptionAsUnhandled(err);
break :do_postgres_preconnect;
} orelse break :do_postgres_preconnect;
const connect_fn = sql_object.get(global, "connect") catch |err| {
global.reportActiveExceptionAsUnhandled(err);
break :do_postgres_preconnect;
} orelse break :do_postgres_preconnect;
_ = connect_fn.call(global, sql_object, &.{}) catch |err| {
global.reportActiveExceptionAsUnhandled(err);
break :do_postgres_preconnect;
};
}
}
switch (this.ctx.debug.hot_reload) {
.hot => JSC.hot_reloader.HotReloader.enableHotModuleReloading(vm),
.watch => JSC.hot_reloader.WatchReloader.enableHotModuleReloading(vm),

View File

@@ -378,6 +378,7 @@ pub const Command = struct {
debugger: Debugger = .{ .unspecified = {} },
if_present: bool = false,
redis_preconnect: bool = false,
sql_preconnect: bool = false,
eval: struct {
script: []const u8 = "",
eval_and_print: bool = false,

View File

@@ -106,6 +106,7 @@ pub const runtime_params_ = [_]ParamType{
clap.parseParam("--title <STR> Set the process title") catch unreachable,
clap.parseParam("--zero-fill-buffers Boolean to force Buffer.allocUnsafe(size) to be zero-filled.") catch unreachable,
clap.parseParam("--redis-preconnect Preconnect to $REDIS_URL at startup") catch unreachable,
clap.parseParam("--sql-preconnect Preconnect to PostgreSQL at startup") catch unreachable,
clap.parseParam("--no-addons Throw an error if process.dlopen is called, and disable export condition \"node-addons\"") catch unreachable,
clap.parseParam("--unhandled-rejections <STR> One of \"strict\", \"throw\", \"warn\", \"none\", or \"warn-with-error-code\"") catch unreachable,
clap.parseParam("--console-depth <NUMBER> Set the default depth for console.log object inspection (default: 2)") catch unreachable,
@@ -589,6 +590,10 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
ctx.runtime_options.redis_preconnect = true;
}
if (args.flag("--sql-preconnect")) {
ctx.runtime_options.sql_preconnect = true;
}
if (args.flag("--no-addons")) {
// used for disabling process.dlopen and
// for disabling export condition "node-addons"

View File

@@ -0,0 +1,84 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
describe("--sql-preconnect", () => {
test("should attempt to preconnect to PostgreSQL on startup", async () => {
let connectionAttempts = 0;
const { promise, resolve } = Promise.withResolvers<void>();
await using server = Bun.listen({
port: 0,
hostname: "127.0.0.1",
socket: {
open(socket) {
connectionAttempts++;
socket.end();
if (connectionAttempts >= 1) {
resolve();
}
},
data() {},
close() {},
},
});
const testDir = tempDirWithFiles("sql-preconnect-test", {
"index.js": `console.log("Script executed");`,
});
const proc = Bun.spawn({
cmd: [bunExe(), "--sql-preconnect", "index.js"],
env: {
...bunEnv,
DATABASE_URL: `postgres://127.0.0.1:${server.port}/MY_DATABASE`,
},
cwd: testDir,
});
await promise;
proc.kill();
await proc.exited;
expect(connectionAttempts).toBeGreaterThan(0);
});
test("should not connect when flag is not used", async () => {
let connectionAttempts = 0;
await using server = Bun.listen({
port: 0,
hostname: "127.0.0.1",
socket: {
open(socket) {
connectionAttempts++;
socket.end();
},
data() {},
close() {},
},
});
const testDir = tempDirWithFiles("sql-no-preconnect", {
"index.js": `console.log("Normal script executed");`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: {
...bunEnv,
DATABASE_URL: `postgres://127.0.0.1:${server.port}/MY_DATABASE`,
},
cwd: testDir,
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
expect(exitCode).toBe(0);
expect(stdout).toContain("Normal script executed");
expect(connectionAttempts).toBe(0); // No connection should be attempted without the flag
});
});

View File

@@ -98,6 +98,7 @@ test/integration/sass/sass.test.ts
test/integration/sharp/sharp.test.ts
test/integration/svelte/client-side.test.ts
test/integration/typegraphql/src/typegraphql.test.ts
test/cli/run/sql-preconnect.test.ts
test/internal/bindgen.test.ts
test/js/bun/bun-object/deep-match.spec.ts
test/js/bun/bun-object/write.spec.ts