Adapters

Drizzle adapter

PostgreSQL query execution, runtime table construction, transactions, and schema introspection.

DrizzleAdapter implements DatabaseAdapter for Drizzle's asynchronous PostgreSQL database.

Create an adapter

import { createDrizzleAdapter } from '@opensya/persistence'

const adapter = createDrizzleAdapter(db)

The database must support Drizzle's select, insert, update, delete, execute, and transaction APIs.

Runtime tables

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.

Query operations fail when the logical table has not been built. Runtime table construction does not execute DDL or migrations.

Column mapping

MetadataPostgreSQL builderRuntime validation
uuiduuidstring
stringtextstring
texttextstring
integerintegerinteger number
bigintbigint({ mode: 'bigint' })bigint
booleanbooleanboolean
timestamptimestampDate
datedatestring
jsonjsonany value
decimalnumericstring or number

The adapter maps primary keys, nullability, uniqueness, and static or factory defaults to the corresponding Drizzle builder methods.

Query translation

The adapter compiles QueryFilter recursively:

  • conditions at the same level are joined with AND;
  • nested and groups use Drizzle and();
  • nested or groups use or();
  • not wraps the compiled nested expression;
  • operators map to 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
})

Adapter-level guards

Before executing SQL, the adapter verifies:

  • filter, sort, insert, and update fields exist on the built table;
  • limit and offset are not negative;
  • in and notIn receive non-empty arrays;
  • update and delete filters contain at least one effective constraint.

These checks also apply when the adapter is used directly. Direct calls do not, however, run Query Engine validators or lifecycle hooks.

Transactions

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.

PostgreSQL introspection

const actualSchema = await adapter.introspect()

The adapter queries information_schema directly. Introspection does not depend on the tables previously passed to buildTable().

Discover tables

Reads base tables from information_schema.tables where table_schema = 'public'.

Discover columns

Reads column name, SQL data type, nullability, and ordinal position from information_schema.columns.

Discover keys and indexes

Reads primary keys, unique indexes and standalone indexes from PostgreSQL catalogs, preserving composite field order and uniqueness.

Produce metadata

Returns TableMetadata[] with physical names used as both logical and collection names.

Introspected SQL types

PostgreSQL data_typeMetadata type
uuiduuid
character varyingstring
texttext
integerinteger
bigintbigint
booleanboolean
timestamp with or without time zonetimestamp
datedate
json, jsonbjson
numericdecimal
any unrecognized typetext
Foreign keys and relations are returned as empty arrays. Defaults, check constraints and custom validators are not introspected. Unknown SQL types deliberately fall back to text so consistency checking reports drift instead of crashing.

When to use the adapter directly

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.