Guide

Queries and filters

Compose database-independent constraints, sorting, offset and cursor pagination.

Queries use logical metadata field names. The adapter validates those fields before translating the query.

Query parameters

interface QueryParams {
  where?: QueryFilter
  limit?: number
  offset?: number
  orderBy?: {
    field: string
    direction: 'asc' | 'desc'
  }[]
}

The Query Engine adds populate?: string[] for explicit relation loading.

Conditions

interface FilterCondition {
  field: string
  operator: FilterOperator
  value?: unknown
}
OperatorTranslationValue
eqequalsscalar
nenot equalscalar
inin arraynon-empty array
notInnot in arraynon-empty array
gt, gtegreater comparisonscomparable
lt, ltelower comparisonscomparable
isNullnull checkfalse means IS NOT NULL; otherwise IS NULL

Conditions declared together are combined with AND.

const activeAdmins = await engine.findMany('users', {
  where: {
    conditions: [
      { field: 'active', operator: 'eq', value: true },
      { field: 'role', operator: 'eq', value: 'admin' }
    ]
  }
})

Boolean groups

const users = await engine.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' }
            ]
          }
        ]
      }
    ],
    not: {
      conditions: [
        { field: 'status', operator: 'eq', value: 'suspended' }
      ]
    }
  }
})

Nested and, or, and not groups compile recursively.

Collection operators

{
  conditions: [
    {
      field: 'id',
      operator: 'in',
      value: ['user-1', 'user-2']
    }
  ]
}
in and notIn require arrays containing at least one value. Invalid inputs are rejected before SQL execution.

Null checks

// IS NULL
{ field: 'deletedAt', operator: 'isNull' }

// IS NOT NULL
{ field: 'deletedAt', operator: 'isNull', value: false }

Sorting and offset pagination

const page = await engine.findMany('users', {
  orderBy: [
    { field: 'createdAt', direction: 'desc' },
    { field: 'email', direction: 'asc' }
  ],
  limit: 25,
  offset: 50
})

A limit or offset below zero is rejected. limit: 0 is valid.

For large or frequently changing datasets, prefer cursor pagination.

Reusable filters

import type { QueryFilter } from '@opensya/persistence'

export function byId(id: string): QueryFilter {
  return {
    conditions: [
      { field: 'id', operator: 'eq', value: id }
    ]
  }
}

Safe mutations

hasFilterConstraints() recursively checks whether a filter contains an actual condition. Update and delete operations reject ineffective filters at both engine and adapter levels.

await engine.updateMany('users', {}, { active: false })
// UnsafeMutationError
An empty conditions array, empty nested groups, or a not containing no condition does not count as a safe filter.