Getting started
This page takes you from zero to a working query in about 10 minutes: install, bootstrap the Dal gateway, define an entity, and run insert + read + update + delete. It assumes you already have a PostgreSQL database reachable via a connection string.
1. Install
Code
@primebrick/dal-pg is a leaf dependency — it only depends on pg,
pg-query-stream, and reflect-metadata. It must not import from
primebrick-be-v3 or primebrick-us-v3.
reflect-metadata must be imported once, at the entry point of your
process, before any entity class is loaded. The decorators use it to
register per-class metadata used by tooling (e.g. primebrick:tableName,
primebrick:keyColumn).
Code
2. Bootstrap the Dal gateway
The Dal gateway (getDal()) is a singleton that owns the pg.Pool, registers
type parsers (INT8→bigint, NUMERIC→string, DATE→string), and sets search_path,
statement_timeout, and application_name on every connection. Create it once
at process startup and reuse it for every request — zero per-request allocation.
Code
Wire graceful shutdown so the pool drains on SIGTERM:
Code
3. Define your first entity
Entities are plain TypeScript classes decorated with @Entity, @Key, and
@Column. The class maps to a table; each decorated property maps to a
column. snake_case is used everywhere — DB column, TS property, JSON
response.
Shared capability columns (audit stamps, soft-delete markers) are never
declared by hand — compose them once via the Auditable / Deletable
mixin factories on EntityBase:
Code
Auditable(EntityBase) adds created_at, created_by, updated_at,
updated_by, version; Deletable(...) adds deleted_at, deleted_by.
They are independent capabilities — an entity audited but hard-deleted uses
extends Auditable(EntityBase) alone (e.g. api_keys, role_mappings), a
deletable-but-not-audited entity uses extends Deletable(EntityBase).
Never redeclare these fields on the concrete class — the mixin owns the
column metadata and the field type in one declaration.
The version column provided by Auditable is what powers optimistic
locking — see Optimistic locking.
4. Run your first query
Code
Run it:
Code
5. What each step does
getDal(config)— creates the singleton pool, registers type parsers, and stores theonConnecthook that sets session defaults.dal.add(Entity, values, opts)— the Dal delegates to an internalRepository(pool). The Repository reads entity metadata, builds a parameterizedINSERT ... RETURNING *, and hydrates the result row into an entity instance (withbigintforINT8,Datefortimestamptz, etc.).- Connection release — the pool reclaims the connection for the next request. No per-request allocation.
6. Common pitfalls
| Pitfall | Symptom | Fix |
|---|---|---|
Forgot import "reflect-metadata" at the entry point | Decorators silently don't register; getEntityPersistenceMeta throws | Add import "reflect-metadata" as the first line of your entry point |
Forgot @Key() | update/delete throw "Entity has no @Key() column — specify matchBy" | Add @Key() to exactly one column, or pass matchBy on every write |
Auditable write missing version | MissingVersionError (ERR02, HTTP 400) | Always send the version you read back in the update payload |
| Pool exhausted under load | connectionTimeoutMillis exceeded → Error: timeout exceeded | Raise max, lower statementTimeoutMs, or audit slow queries |
bigint not serializing to JSON | TypeError: Do not know how to serialize a BigInt | Use @primebrick/sdk's Ext-JSON middleware, or Number(value) for safe values |
Next steps
- Architecture — how the layers fit together and why.
- Entities & decorators — every decorator and entity interface.
- Query DSL —
field(),Filter,Sort,Join,Project. - Repository — finders, writes, bulk ops, streaming.
- Connections & transactions — pool config,
dal.transaction, timeouts, shutdown.