PrimeBrickPrimeBrick
  • Docs
  • Contact
  • MIT License
  • Documentation
  • MCP Server
  • API Catalog
  • Services
  • Libraries
PrimeBrickPrimeBrick

© 2026 PrimeBrick. MIT License. v3.8.0

github

@primebrick/dal-pg

@primebrick/dal-pg is a shared Data Access Layer library for Primebrick v3. It provides a type-driven, metadata-based Repository for PostgreSQL. Entities are plain TypeScript classes decorated with @Entity, @Column, @Key, @Unique, @AuditableField, @DeletableField, and @CloneField. The Repository reads entity metadata at runtime to generate parameterized SQL.

What it gives you

  • Type-safe entities — plain TS classes + decorators, no schema files to keep in sync.
  • Metadata-driven SQL — Repository reads entity metadata at runtime and emits parameterized SQL (RETURNING * on every write).
  • Query DSL — composable field(), Filter, Sort, Join, Project expressions, type-checked at compile time.
  • Bulk operations — addMany, upsertMany, updateMany using a TEMP TABLE strategy (atomic, SQL-injection safe).
  • Soft delete — opt in via @DeletableField; finders exclude soft-deleted rows by default.
  • Audit trail — @AuditTrail() + AuditPort produce field-level deltas fire-and-forget.
  • Optimistic locking — automatic for auditable entities; stable ERR01/ERR02/ERR03 codes.
  • Clone — Repository.clone() copies a record by UUID, resetting audit/unique fields.
  • Streaming — findAll({ stream: true }) returns an AsyncIterable backed by a pg cursor.
  • Dal gateway — getDal() singleton owns the pool, registers type parsers (INT8→bigint, NUMERIC→number), and sets search_path/statement_timeout/application_name on every connection.
  • Framework-agnostic errors — DalError carries a stable code string; consumers map it to HTTP/NATS at their own boundary.

Architecture at a glance

The Dal gateway owns the connection pool and is the recommended entry point. The Repository is the low-level engine — it accepts any Queryable (pool or pooled client), which is how it participates in transactions via dal.withClient().

Hello World

A minimal end-to-end example: define an entity, bootstrap the Dal gateway, and perform one insert + one read.

Code
import "reflect-metadata"; import { Entity, Column, Key, Unique, AuditableField, AuditableFieldType, getDal, } from "@primebrick/dal-pg"; // 1. Define an entity — plain TS class + decorators. @Entity("customers") export class CustomerEntity { @Key() @Column({ pgType: "bigint" }) id!: bigint; @Unique() @Column({ pgType: "uuid" }) uuid!: string; @Column({ pgType: "text" }) name!: string; @AuditableField(AuditableFieldType.CREATED_AT) @Column({ pgType: "timestamptz" }) created_at!: Date; @AuditableField(AuditableFieldType.CREATED_BY) @Column({ pgType: "text" }) created_by!: string; @AuditableField(AuditableFieldType.UPDATED_AT) @Column({ pgType: "timestamptz" }) updated_at!: Date; @AuditableField(AuditableFieldType.UPDATED_BY) @Column({ pgType: "text" }) updated_by!: string; @AuditableField(AuditableFieldType.VERSION) @Column({ pgType: "integer" }) version!: number; } // 2. Bootstrap the Dal gateway once at process startup. const dal = getDal({ connectionString: process.env.DATABASE_URL!, schema: "myapp", statementTimeoutMs: 30_000, }); // 3. Insert — RETURNING * gives you the full hydrated row. const created = await dal.add(CustomerEntity, { name: "Alice", }, { actor: "system" }); // created.uuid, created.id, created.version === 1, created.created_at, ... // 4. Read by UUID — throws NotFoundError by default if no row matches. const found = await dal.findByUUID(CustomerEntity, created.uuid); console.log(found.name); // "Alice" // 5. Graceful shutdown on SIGTERM. process.on("SIGTERM", async () => { await dal.close(); process.exit(0); });

Where to go next

If you want to…Read
Get a service running end-to-endGetting started
Understand the layers and design decisionsArchitecture
Define entities with all decoratorsEntities & decorators
Build type-safe queriesQuery DSL
Use the Repository API (finders, writes, bulk, streaming)Repository
Manage the pool, transactions, timeouts, shutdownConnections & transactions
Track field-level changesAudit trail
Prevent lost updatesOptimistic locking
Copy records by UUIDClone
See what changed per releaseChangelog
Look up a specific symbolAPI reference

Next steps

  • Getting started — install, configure, and run your first query.
  • Architecture — how the layers fit together.
Last modified on July 26, 2026
On this page
  • What it gives you
  • Architecture at a glance
  • Hello World
  • Where to go next
  • Next steps
TypeScript