pnpm add @opensya/persistence drizzle-orm pg
pnpm add -D @types/pg
Persistence currently ships a Drizzle PostgreSQL adapter. Your physical tables must already exist through migrations.
Use defineTable() instead of annotating the object with TableMetadata. The
helper validates the shape while preserving literal names for type inference.
import { defineTable } from '@opensya/persistence'
export const usersMetadata = defineTable({
name: 'users',
collectionName: 'users',
columns: [
{
name: 'id',
columnName: 'id',
type: 'uuid',
nullable: false,
primaryKey: true,
unique: true,
default: () => crypto.randomUUID(),
validators: []
},
{
name: 'email',
columnName: 'email',
type: 'string',
nullable: false,
primaryKey: false,
unique: true,
validators: [
{
name: 'email-format',
validate(value) {
return typeof value === 'string' && value.includes('@')
? { valid: true }
: { valid: false, message: 'Enter a valid email address.' }
}
}
]
},
{
name: 'createdAt',
columnName: 'created_at',
type: 'timestamp',
nullable: false,
primaryKey: false,
unique: false,
default: () => new Date(),
validators: []
}
],
relations: [],
tableValidators: []
})
import {
createDrizzleAdapter,
createHooksRegistry,
createMetadataRegistry,
createQueryEngine
} from '@opensya/persistence'
import { usersMetadata } from './users.metadata.js'
const registry = createMetadataRegistry(usersMetadata)
registry.lock()
const adapter = createDrizzleAdapter(db)
for (const table of registry.getAll()) {
adapter.buildTable(table)
}
const hooks = createHooksRegistry()
export const engine = createQueryEngine(registry, adapter, hooks)
buildTable() builds the runtime Drizzle objects. It does not execute DDL.
You can also keep registration chainable:
const registry = createMetadataRegistry()
.register(usersMetadata)
.register(postsMetadata)
registry.lock()
const user = await engine.create('users', {
email: 'john@example.com'
})
// user is inferred as:
// { id: string; email: string; createdAt: Date }
const saved = await engine.findOne('users', {
where: {
conditions: [
{ field: 'id', operator: 'eq', value: user.id }
]
}
})
No User interface or explicit generic is required. If an application needs a
custom projection, explicit generics remain supported.
const user = await engine.findOne<PublicUser>('users', { where })