mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
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:
committed by
GitHub
parent
70ebe75e6c
commit
e9ccc81e03
@@ -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:
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
84
test/cli/run/sql-preconnect.test.ts
Normal file
84
test/cli/run/sql-preconnect.test.ts
Normal 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
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user