DrizzleAdapter implements DatabaseAdapter for Drizzle's asynchronous PostgreSQL database.
import { createDrizzleAdapter } from '@opensya/persistence'
const adapter = createDrizzleAdapter(db)
The database must support Drizzle's select, insert, update, delete, execute, and transaction APIs.
for (const metadata of registry.getAll()) {
adapter.buildTable(metadata)
}
buildTable() converts each TableMetadata into a Drizzle pgTable and indexes it by the logical metadata name.
| Metadata | PostgreSQL builder | Runtime validation |
|---|---|---|
uuid | uuid | string |
string | text | string |
text | text | string |
integer | integer | integer number |
bigint | bigint({ mode: 'bigint' }) | bigint |
boolean | boolean | boolean |
timestamp | timestamp | Date |
date | date | string |
json | json | any value |
decimal | numeric | string or number |
The adapter maps primary keys, nullability, uniqueness, and static or factory defaults to the corresponding Drizzle builder methods.
The adapter compiles QueryFilter recursively:
AND;and groups use Drizzle and();or groups use or();not wraps the compiled nested expression;eq, ne, inArray, notInArray, comparisons, isNull, and isNotNull.await adapter.findMany('users', {
where: {
and: [
{
conditions: [
{ field: 'active', operator: 'eq', value: true }
]
},
{
or: [
{
conditions: [
{ field: 'role', operator: 'eq', value: 'admin' }
]
},
{
conditions: [
{ field: 'role', operator: 'eq', value: 'owner' }
]
}
]
}
]
},
orderBy: [{ field: 'createdAt', direction: 'desc' }],
limit: 20,
offset: 0
})
Before executing SQL, the adapter verifies:
limit and offset are not negative;in and notIn receive non-empty arrays;These checks also apply when the adapter is used directly. Direct calls do not, however, run Query Engine validators or lifecycle hooks.
await adapter.transaction(async tx => {
const user = await tx.insert('users', input)
await tx.insert('profiles', {
id: crypto.randomUUID(),
userId: user.id
})
})
The callback receives a new adapter bound to the Drizzle transaction while sharing the previously built table map.
const actualSchema = await adapter.introspect()
The adapter queries information_schema directly. Introspection does not depend on the tables previously passed to buildTable().
Reads base tables from information_schema.tables where table_schema = 'public'.
Reads column name, SQL data type, nullability, and ordinal position from information_schema.columns.
Reads primary keys, unique indexes and standalone indexes from PostgreSQL catalogs, preserving composite field order and uniqueness.
Returns TableMetadata[] with physical names used as both logical and collection names.
PostgreSQL data_type | Metadata type |
|---|---|
uuid | uuid |
character varying | string |
text | text |
integer | integer |
bigint | bigint |
boolean | boolean |
| timestamp with or without time zone | timestamp |
date | date |
json, jsonb | json |
numeric | decimal |
| any unrecognized type | text |
text so consistency checking reports drift instead
of crashing.Use QueryEngine for domain operations. Use the adapter directly for infrastructure code that intentionally needs lower-level access, such as schema inspection or transaction-scoped operations inside hooks.