How a call on a model becomes a statement on a connection, and what wraps that
path. This is the map; the guides describe each piece.The path of one call#
The solid path is what every model call takes; the dotted boxes are the pieces
that decide which connection, whether to hit the database at all, and
what runs around the call.The same path in time order, for one User.findAll({ where: { name: 'Ada' } })
on a define() model:The query event is the cheapest way to watch that path from outside. It
fires once per statement with the SQL as sent to the driver (placeholders, not
values) and how long it took:import type { Prorm } from 'ts-prorm-orm';
export function logSlowQueries(prorm: Prorm, thresholdMs = 200) {
prorm.on('query', (event: { sql: string; duration: number; model?: string; type?: string }) => {
if (event.duration >= thresholdMs) {
console.warn(`[slow ${event.duration}ms] ${event.model ?? '-'} ${event.type ?? ''} ${event.sql}`);
}
});
}
Recorded events for a create() and a findAll() on SQLite:{"sql":"INSERT INTO \"users\" (\"name\") VALUES (?)","duration":0,"model":"User","type":"INSERT"}
{"sql":"SELECT * FROM \"users\" WHERE \"name\" = ?","duration":0,"model":"User"}
The pieces#
Prorm (src/prorm.ts) is the instance you construct: it holds the
options, the dialect once connect() has built it, the model registry
(define(), model(), hasModel()), the global hooks, and
transaction(). It is an EventEmitter, which is how query and connection
events reach logging and metrics.Models (src/models) turn FindOptions into a call on the dialect's
builders and turn rows back into instances: attribute coercion, virtual and
external fields, scopes, paranoid mode, associations and eager loading, and the
lifecycle hooks. See Models and
Querying.The WHERE guard (src/query-builders/expression-render.ts) sits between a
model and the dialect: every where is rewritten into shapes the builder
renders and checked, and a condition the builder would drop throws before the
statement is sent. See Query safety.Dialects (src/dialects/<name>/index.ts) are one class per engine,
implementing the Dialect contract in src/dialects/dialect.ts: connect,
query, the statement builders, DDL, type mapping (getDataTypeSql /
mapNativeType, see Type mapping), transaction
capabilities and, for engines that have them, schema objects. Most of the 77
extend one of the base builders (PostgreSQL, MySQL, SQLite, SQL Server,
Oracle, …) and override what differs. DIALECT_LOADERS in
src/dialects/loaders.ts is a Record<SqlDialect, loader>, so a dialect added
to the enum without a loader fails the build; each loader require()s its
module when called, so importing the library does not load fifty drivers.A slice of the family tree — the core engines implement the contract, and the
compatible and managed engines subclass the core one they speak:engineOf() (src/dialect-names.ts) maps a managed variant to the engine
it runs — 'cloudsqlpostgres' → 'postgres', 'rdsmysql' → 'mysql' — and
leaves every other name alone. Feature tables keyed by engine (partial
indexes, schema-object families, transaction capabilities, the codegen
catalogs) look dialects up through it, so a managed service inherits its
engine's features without being listed in every table. Deliberately not
mapped: rdsdata (its builders forward to an inner PostgreSQL or MySQL
dialect), kusto (a read-only T-SQL subset, not SQL Server) and spannerpg
(Spanner's own feature set behind a PostgreSQL grammar).Connection pool (src/connection-pool.ts) hands out driver connections
with sizing, validation, health checks and events (acquire, release,
timeout, health-check, …). A transaction takes one connection for its
lifetime, and transactionScope (an AsyncLocalStorage in
src/transaction-support.ts) is how a nested transaction() or — with
transactionContext: true — an ordinary query finds it. See
Connection pooling and
Transactions.Replica manager (src/replica-manager.ts) routes reads to replicas by
round-robin, least-connections or lowest-lag, with dialect-specific lag
detection, and sends writes and transactions to the primary. See
Replication.Connection manager (src/connection-manager.ts) keeps several named
Prorm instances so one application can talk to more than one database, and
resolves FindOptions.using. See Multiple databases.Cache (src/cache) answers a read before the dialect is asked, with an
in-process L1 and an optional Redis L2. See Caching.Hooks, validation, audit and compliance run around the call: validators
before a write, lifecycle hooks before and after, the audit logger after, and
the compliance modules (field encryption, masking, the query firewall) at the
points they declare. See Hooks,
Validation, Audit logging
and Compliance.Beyond SQL#
The same Prorm front door serves two other families through the dialect
registry:Graph dialects (src/graph): six compilers — Cypher, Gremlin, Dgraph,
OrientDB, AQL and SPARQL — registered under 14 names (GRAPH_DIALECTS in
src/graph/index.ts; Neptune shares the Gremlin compiler, and the six
SPARQL triplestores share one compiler). See Graph databases. Store adapters (src/nosql): 105 adapter directories for object, key-value,
document, search, queue, log and vector stores, each a class you construct
directly (import { S3Store } from 'ts-prorm-orm/nosql'). See
Store adapters. Counts on this page#
SQL dialects: 77 — ls -d src/dialects/*/ | wc -l is 79, minus cloud-auth
and store-key, which are shared helpers; DIALECT_LOADERS has the same 77
entries (grep -c "lazy('" src/dialects/loaders.ts).
Graph dialect names: 14 — the keys of GRAPH_DIALECTS in src/graph/index.ts.
Store adapters: 105 — ls -d src/nosql/*/ | wc -l is 107, minus shared (helpers)
and promql (a query compiler used by the Prometheus-family stores, not a store).
Modified at 2026-10-04 00:40:16