Databases
Prerequisites
@org/fluffy-chainsaw-dbinstalled, plus your own Postgres driver (pg>= 8) as a peer dependency.- Every database must be bound to an instance in
fluffy-chainsaw.yaml(see below).
The marker
import { db } from "@org/fluffy-chainsaw-db";
const pool = await db.connect("users");
// build time: "database `users` must exist"
// runtime: resolves the descriptor and returns a ready pg.Pool (throws if not wired)
Returns a Connection:
interface Connection {
auth: "password" | "proxy" | "gcp-iam";
host: string;
port: number;
user: string;
database: string;
sslmode: string; // libpq: "disable" | "require" | "verify-ca" | "verify-full"
password?: string; // ONLY for auth: "password"
url?: string; // postgres:// DSN, ONLY for auth: "password"
connector?: { // present for proxy/IAM; else null
kind: string; // e.g. "cloudsql"
instanceConnectionName?: string; // "proj:region:instance"
iamAuthn?: boolean;
} | null;
}
Handle both auth shapes — branch on conn.auth:
auth |
password / url |
connector |
How to connect |
|---|---|---|---|
"password" |
set | null |
use them directly |
"proxy" / "gcp-iam" |
absent by design | set | a proxy/connector mints a short-lived IAM token; never expect a DSN |
Connecting
import { db } from "@org/fluffy-chainsaw-db";
const pool = await db.connect("users"); // a ready pg.Pool — auth handled inside
Works unchanged locally (fluffy-chainsaw local, password descriptor) and deployed (Cloud SQL IAM connector — no password anywhere). Pass pool tuning as a second arg: db.connect("users", { max: 10 }). pool.end() also releases the IAM connector.
Raw descriptor (opting out)
If you need to construct your own pg client or use a different driver/ORM, import db (or db as core) and sslOf from @org/fluffy-chainsaw. The raw core db.connect(name) returns the Connection descriptor.
Branch on conn.auth to handle both auth shapes:
auth |
password / url |
connector |
How to connect |
|---|---|---|---|
"password" |
set | null |
use them directly |
"proxy" / "gcp-iam" |
absent by design | set | a proxy/connector mints a short-lived IAM token; never expect a DSN |
If running locally or in CI using the Cloud SQL Auth Proxy sidecar, the proxy listens at 127.0.0.1:5432 and handles authentication under the hood.
fluffy-chainsaw.yaml
defaults:
databases:
instance: app-db # bind every database to one server; override per-db to split
databases:
users:
extensions: [pgcrypto] # string list
orders: {} # all fields optional except the instance binding
instances: # operator-declared substrate — no code marker
app-db:
tier: db-custom-1-3840 # all optional; strong defaults fill the rest
database_version: POSTGRES_16
availability_type: REGIONAL # ZONAL | REGIONAL
# existing: true # reference a server you already run, don't provision
Field reference (anything else fails validation):
databases.<name>—instance(string, required via default or per-db),existing(bool),extensions(string list),migrations.run(string — the paved schema-change path, see migrations.md).instances.<name>—existing,tier,region,database_version,availability_type(ZONAL|REGIONAL),disk_size,disk_autoresize,deletion_protection.
A runner gets access by listing the database under uses.databases — see
runners.md.
Using a database another app owns
existing: true on a database marks it owned elsewhere: it is looked up at deploy
(fails loudly if missing) and granted onto, but never created, altered, or dropped
by this app. Same for an existing: true instance — the first app to declare it
provisions the server; every other app sets existing: true to reference it without
taking ownership. Destroying a referencing app never touches the shared instance.
Schema changes
Don't run DDL from app code. Declare databases.<name>.migrations.run and let
fluffy-chainsaw migrate apply your migrations as a dedicated migrator identity — the
runner identity serving traffic then holds no DDL rights at all. Full guide:
migrations.md.
Gotchas
- Placement is explicit, never inferred. Unbound database →
E_DB_UNBOUND; binding to an undeclared instance →E_INSTANCE_UNDECLARED; an instance nothing binds to →E_INSTANCE_ORPHANED. All failfluffy-chainsaw scan. - A yaml database no
db.connectmatches →E_NOT_IN_CODE. sslOf(sslmode)translates libpq sslmode forpg:disable→false,require→{rejectUnauthorized:false},verify-full→{rejectUnauthorized:true}.