@avelon/postgres
@avelon/postgres is the Postgres database driver for Avelon. It compiles the frozen QueryIR into parameterized SQL, runs it through Bun's SQL client, and maps vendor failures into the framework error taxonomy. Reach for this package when your application needs transactions, deep relation loads, upserts with returning, or an independent Postgres connection beside Supabase.
Installation
bun add @avelon/postgres
Set a connection URL in the environment or pass one when constructing the driver:
export POSTGRES_URL=postgresql://postgres:avelon@127.0.0.1:5432/avelon
Basic Usage
import { createPostgresDatabase } from '@avelon/postgres'
import type { QueryIR } from '@avelon/core'
const db = createPostgresDatabase({
url: process.env.POSTGRES_URL,
instance: 'primary',
})
const latest: QueryIR = {
table: 'posts',
mode: 'select',
select: ['id', 'title'],
where: [{ kind: 'null', column: 'published_at', negated: true }],
relations: [],
order: [{ column: 'published_at', direction: 'desc' }],
limit: 10,
}
const result = await db.execute<{ id: string; title: string }>(latest)
Capabilities
| Capability | Value | Notes |
|---|---|---|
transactions | true | Interactive transactions through db.transaction() |
rowSecurity | false | Ward-to-RLS compilation belongs to @avelon/supabase |
maxRelationDepth | 8 | Application-side nested loads; measured against nested assay fixtures |
fullTextSearch | false | No portable search() surface in v1 |
upsert | true | Requires explicit conflict target and update list |
returning | true | Write queries may project rows |
windowFunctions | true | Informational; available through raw() |
jsonOperators | true | Informational; available through raw() |
Query Compilation
The driver validates IR shape, rejects unknown public-schema identifiers, normalizes predicates, and compiles positional SQL. Empty AND is true, empty OR is false, empty IN matches nothing unless negated, and a constant-false ward or where short-circuits without a database round trip.
import { compilePostgres, combinedPredicate } from '@avelon/postgres'
import type { QueryIR } from '@avelon/core'
const query: QueryIR = {
table: 'assay_users',
mode: 'select',
select: ['id'],
where: [{ kind: 'compare', column: 'age', op: '>=', value: 18 }],
ward: { kind: 'compare', column: 'age', op: '<', value: 30 },
relations: [],
order: [],
}
const compiled = compilePostgres(query, combinedPredicate(query))
// compiled.text binds age thresholds as $1 and $2
Transactions
import { createPostgresDatabase } from '@avelon/postgres'
const db = createPostgresDatabase()
await db.transaction(async (tx) => {
await tx.execute({
table: 'assay_users',
mode: 'insert',
select: [],
where: [],
relations: [],
order: [],
values: { id: 'u1', email: 'one@example.test', name: 'One', age: 20, nickname: null },
})
})
Migrations
Migrations are driver-owned SQL pairs registered on the driver instance.
import { createPostgresDatabase } from '@avelon/postgres'
const db = createPostgresDatabase({
migrations: [
{
id: '20260804_create_posts',
up: [
'CREATE TABLE posts (id text PRIMARY KEY, title text NOT NULL)',
],
down: ['DROP TABLE posts'],
},
],
})
await db.plan()
await db.apply()
await db.status()
Error Mapping
Vendor SQLSTATE values never leave the driver. Unique violations become Conflict, unknown tables/columns/routines become Invalid, and connection failures become Unavailable. Every other code becomes DriverFault.
| SQLSTATE | Framework error | Meaning |
|---|---|---|
23505 | Conflict | unique_violation |
42P01 | Invalid | undefined_table |
42703 | Invalid | undefined_column |
42883 | Invalid | undefined_function |
08006 | Unavailable | connection_failure |
Live Conformance
Fixture provisioning is owned by this package. Reset the assay schema, then run the shared database suite against a live Postgres:
export POSTGRES_URL=postgresql://postgres:avelon@127.0.0.1:5432/avelon_test
bun run fixtures:reset
bun test
Method Reference
| Method | Signature | Description | |
|---|---|---|---|
createPostgresDatabase | (options?: PostgresDatabaseOptions) => PostgresDatabase | Constructs a driver from options or POSTGRES_URL / DATABASE_URL. | |
postgresDatabaseCapabilities | { transactions: true, rowSecurity: false, maxRelationDepth: 8, fullTextSearch: false, upsert: true, returning: true, windowFunctions: true, jsonOperators: true } | Exact capability declaration for the Postgres driver. | |
PostgresDatabaseOptions | interface | Connection URL, instance name, and optional migrations. | |
PostgresDatabase.execute | (query: QueryIR) => Promise<QueryResult> | Validates, compiles, and executes one query IR operation. | |
PostgresDatabase.rpc | (routine: string, args: Readonly<Record<string, unknown>>) => Promise<T> | Invokes a Postgres routine; missing routines raise Invalid. | |
PostgresDatabase.transaction | (callback: (tx: DatabaseTransaction) => Promise<T>) => Promise<T> | Runs the callback atomically and returns its result. | |
PostgresDatabase.plan | () => Promise<MigrationPlan> | Returns pending migration identifiers and SQL steps. | |
PostgresDatabase.apply | () => Promise<readonly MigrationStatus[]> | Applies pending migrations inside transactions. | |
PostgresDatabase.rollback | (steps?: number) => Promise<readonly MigrationStatus[]> | Rolls back the newest applied migration batches. | |
PostgresDatabase.status | () => Promise<readonly MigrationStatus[]> | Lists applied and pending migration states. | |
PostgresDatabase.raw | () => SQL | Returns the Bun SQL client at the vendor boundary. | |
PostgresDatabase.resetFixtures | () => Promise<void> | Recreates assay tables and assay_echo for live conformance. | |
PostgresDatabase.close | () => Promise<void> | Closes the underlying SQL client pool. | |
compilePostgres | (ir: QueryIR, predicate?: Predicate) => CompiledSql | Compiles IR to parameterized SQL without executing it. | |
compileSqlPredicate | (predicate: Predicate, bind: (value: unknown) => string) => string | Compiles a normalized predicate to a SQL boolean expression. | |
CompiledSql | interface | Parameterized text plus positional parameters. | |
normalizePredicate | (predicate: Predicate) => Predicate | Applies empty-list and constant identities. | |
combinedPredicate | `(ir: Pick<QueryIR, 'where' \ | 'ward'>) => Predicate` | ANDs where and ward, then normalizes. |
mapPostgresError | (error: unknown, operation: string) => never | Maps vendor failures into framework errors. | |
POSTGRES_ERROR_MAP | readonly { sqlstate, framework, meaning }[] | SQLSTATE values this driver maps into the taxonomy. | |
validateQueryIR | (ir: QueryIR, maxRelationDepth: number) => void | Rejects malformed IR before compilation. | |
assertIdentifier | (value: unknown, path: string) => asserts value is string | Rejects identifiers that are not simple SQL names. | |
assertQueryAgainstSchema | (cache: SchemaCache, query: QueryIR) => void | Rejects unknown public-schema tables and columns. | |
loadSchemaCache | (sql: SQL) => Promise<SchemaCache> | Loads public base tables and their columns. | |
SchemaCache | type | Map of table name to column set. | |
planMigrations | (sql: SQL, migrations: readonly PostgresMigration[]) => Promise<MigrationPlan> | Builds a pending plan from registered migrations and history. | |
applyMigrations | (sql: SQL, migrations: readonly PostgresMigration[]) => Promise<readonly MigrationStatus[]> | Applies pending migrations inside transactions. | |
rollbackMigrations | (sql: SQL, migrations: readonly PostgresMigration[], steps?: number) => Promise<readonly MigrationStatus[]> | Rolls back the newest applied migration batches. | |
statusMigrations | (sql: SQL, migrations: readonly PostgresMigration[]) => Promise<readonly MigrationStatus[]> | Returns applied/pending status for every registered migration. | |
PostgresMigration | interface | Driver-owned id, up, and down SQL pair. | |
resetAssayFixtures | (sql: SQL) => Promise<void> | Provisions empty assay fixtures on a SQL client. | |
ASSAY_FIXTURE_SQL | string | SQL that drops and recreates the database conformance fixtures. |
Testing
Use the shared database conformance suite with a live database. The package ships fixture reset helpers so tests do not rely on hand-maintained schema.
import { databaseSuite } from '@avelon/conformance/suites'
import { createPostgresDatabase } from '@avelon/postgres'
databaseSuite({
name: 'live postgres',
create: async () => {
const driver = createPostgresDatabase()
await driver.resetFixtures()
return driver
},
})
Unit tests cover SQL compilation and error mapping without a network round trip. Live tests require Postgres and fail closed when the database is unreachable.