Architecture
This page describes how a Quark call travels from quark.For[T] to a row in
your struct. Read it when you want to know where an extension point sits — where
middleware runs, when identifiers are validated, what the dialect owns — rather
than how to call a specific method.
Everything is built from five primitives:
Clientowns the database handle, dialect, middleware, observers, cache, and migration helpers.Query[T]is the immutable typed builder for model operations.Dialectisolates database-specific SQL details.- Middleware and hooks extend the execution path.
TenantRouterinjects tenant-aware behavior.
Core principles
| Principle | Implementation |
|---|---|
| Type safety | For[T] returns a query bound to a concrete model type. |
| Immutability | Query methods clone builder state before returning the next query. |
| Database independence | Dialects own placeholders, quoting, upserts, and DDL. |
| Guarded SQL | SQLGuard validates identifiers, operators, and keywords. |
| Modular execution | Middleware, hooks, observers, and cache stores are injected through the client. |
Request lifecycle
The same path in words:
quark.For[T]parses model metadata and initializes a typed builder.- Builder methods add query state by returning cloned builders.
- An endpoint such as
List,Create,Update, orDeleteexecutes. - Write operations run validation.
- Middleware and lifecycle hooks wrap the operation.
- SQL is generated through the dialect and SQLGuard.
database/sqlexecutes the statement.- Rows are mapped into typed models.
- Preloads resolve associations with secondary queries.
- Observers and telemetry receive query events.
Native routines and events
Calls to database functions and stored procedures take a separate path from
model queries — they don't go through the model builder at all. A function that
returns rows is read through NewRoutine[T], which maps the result set into your
struct the same way a List would:
users, err := quark.NewRoutine[User](
ctx,
client,
"get_active_users",
100,
).List()
For a stored procedure with output parameters, use Call:
var processed int
err := quark.Call(
ctx,
client,
"process_billing",
"2026-05",
sql.Out{Dest: &processed},
)
Database-native events are exposed through notification helpers such as
quark.Notify.
Schema evolution
DDL follows the same rule as the rest of the SQL: the dialect owns it. Add, drop, alter, rename column, and rename table are all dialect methods. Each dialect also reports whether the engine supports transactional DDL, since that differs by engine and changes what a failed migration leaves behind.