Getting started

Create a typed persistence runtime and connect it to a database through an adapter.

This guide shows how to create a typed OpenSya Persistence runtime, register your metadata and connect it to a database through an adapter.

OpenSya Persistence is database-independent. PostgreSQL with Drizzle is used throughout this guide because it is the first official adapter, but the core runtime is not coupled to PostgreSQL or Drizzle. Additional adapters can target databases such as MySQL, SQLite or MongoDB.

Install

Terminal
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.

Define a table

Use defineTable() instead of annotating the object with TableMetadata. The helper validates the shape while preserving literal names for type inference.

users.metadata.ts
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: []
})

Create the runtime

persistence.ts
import {
  createPostgreAdapter,
  createHooksRegistry,
  createMetadataRegistry,
  createQueryEngine
} from '@opensya/persistence'
import { usersMetadata } from './users.metadata.js'

const registry = createMetadataRegistry(usersMetadata)
registry.lock()

const adapter = createPostgreAdapter(db)
const hooks = createHooksRegistry()
export const engine = createQueryEngine({ registry, adapter, hooks })

await engine.schema.createTables()

createTables() creates missing physical tables through the active adapter and builds the runtime table objects used by queries. The operation is idempotent and does not delete existing tables or data.

You can also keep registration chainable:

const registry = createMetadataRegistry()
  .register(usersMetadata)
  .register(postsMetadata)

registry.lock()

Execute typed operations

users.service.ts
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 })

Next

Metadata and registry
Learn inference, indexes, audit settings and registry rules.
Queries and filters
Compose filters, sorting and safe mutations.
Production playground
Run the complete package against PostgreSQL.