OpenSya Persistence separates the description of a data model from the execution of database operations.
Application service
│
▼
QueryEngine
┌────┼──────────────┐
▼ ▼ ▼
Registry Hooks RelationResolver
│ │
└──────┬───────┘
▼
DatabaseAdapter
│
▼
Database
Stores TableMetadata, rejects duplicate logical names, validates the complete schema, and becomes immutable after lock().
Provides application-facing reads and mutations. It coordinates defaults, hooks, validation, transactions, safe targeting, and relation population.
Stores ordered callbacks for create, update, and delete operations. Before hooks may transform data; after hooks run before the transaction commits.
Resolves explicitly requested direct relations by batching key values and grouping related rows in memory.
Defines the storage contract: reads, inserts, updates, deletes, transactions, runtime table construction, and schema introspection.
Compares declared metadata with the schema returned by DatabaseAdapter.introspect().
Removes hidden and context-sensitive fields from base entities and populated relations.
Produces before/after snapshots and field changes through a transaction-scoped writer.
Collects business events inside application transactions, stores them in an outbox, and publishes them through an injectable processor.
Unknown logical table names fail through registry.getOrThrow().
The engine forwards filters, sorting, limit, and offset.
When populate is present, the resolver loads each named direct relation.
Hidden and contextual fields are removed, including inside populated relations. The entity type is inferred from the registered table metadata.
Reads do not run lifecycle hooks and are not automatically transactional.
Defaults → before hooks → known-field check → complete validation → insert → after hooks.
Safe-filter check → load current rows → before hooks → merge and validate touched fields → update → after hooks.
Safe-filter check → resolve target when needed → before hooks → delete → after hooks.
All mutation steps execute inside adapter.transaction(). When configured,
audit entries use that same adapter. Application transactions additionally
flush collected Domain Events to the outbox before the outer commit.
| Property | Used by | Example |
|---|---|---|
TableMetadata.name | Engine and adapter lookup | users |
TableMetadata.collectionName | Physical table construction and consistency matching | app_users |
ColumnMetadata.name | Entities, filters, and sorting | createdAt |
ColumnMetadata.columnName | PostgreSQL column construction and consistency matching | created_at |
The Query Engine does not import Drizzle types. A custom adapter can target another store if it preserves the contract and semantics.