Domain

FilterCriteria

Tipo principale per query PostgREST type-safe — combina filtri sui campi con metadati di query (operatori, OR conditions, sort, paginazione, vertical filtering).

Cos'è

FilterCriteria<T> è il tipo principale per costruire query type-safe verso PostgREST. Combina:

  • Filtri sui campi dell'entità T (uno o più, in AND implicito)
  • OR conditions per query con logica alternativa
  • Sort (singolo o multiplo)
  • Pagination (offset o cursor based)
  • Vertical filtering (selezione campi e resource embedding)
type FilterCriteria<T> = Partial<Record<keyof T, FilterCondition>> & FilterMetadata<T>

Tutto type-checked: passare un campo inesistente o un operatore errato produce errore di compilazione.

FilterOperator — operatori PostgREST

type FilterOperator =
  | 'eq' | 'neq' | 'gt' | 'lt' | 'gte' | 'lte'
  | 'like' | 'ilike'
  | 'in' | 'is' | 'not'
  | 'between'
  | 'cs' | 'cd' | 'sl' | 'sr' | 'nxl' | 'nxr' | 'adj'
  | 'fts' | 'plfts' | 'phfts'

Operatori di confronto

OperatoreSignificatoEsempio PostgREST
eqUguale aname=eq.John
neqDiverso dastatus=neq.deleted
gtMaggioreage=gt.18
gteMaggiore o ugualequantity=gte.10
ltMinoreprice=lt.100
lteMinore o ugualerating=lte.5

Operatori per stringhe

OperatoreSignificatoEsempio
likePattern matching case-sensitive (wildcard %)name=like.*john*
ilikePattern matching case-insensitiveemail=ilike.*@gmail.com

Operatori per array/insiemi

OperatoreSignificatoEsempio
inValore in un insiemestatus=in.(active,pending)
csContains (array contains)tags=cs.{typescript}
cdContained bytags=cd.{typescript,javascript}

Operatori per NULL/booleani

OperatoreSignificatoEsempio
isConfronto con null/true/falsedeleted_at=is.null
notNegazione di isactive=not.is.null

Operatori per range

OperatoreSignificatoEsempio
betweenRange inclusivodate=between.2024-01-01,2024-12-31
slStrictly left ofrange=sl.[1,10]
srStrictly right ofrange=sr.[1,10]
nxlDoes not extend to the leftrange=nxl.[1,10]
nxrDoes not extend to the rightrange=nxr.[1,10]
adjAdiacente arange=adj.[1,10]
OperatoreSignificatoEsempio
ftsFTS semplice (to_tsquery)content=fts.typescript
plftsPlain to tsquerycontent=plfts.typescript programming
phftsPhrase to tsquerycontent=phfts.clean architecture
PostgREST documenta anche wfts (websearch_to_tsquery), ma @pzeta/postgrest v1.0.4 non lo espone nell'enum FilterOperator. Usa plfts o phfts come alternative type-safe.

FilterCondition

Condizione singola operatore + valore.

interface FilterCondition {
  operator: FilterOperator
  value: FilterValue | FilterArrayValue
}

type FilterValue = string | number | boolean | Date | null
type FilterArrayValue = (string | number | boolean | Date)[]
const eq: FilterCondition = { operator: 'eq', value: 'active' }
const range: FilterCondition = { operator: 'between', value: [10, 100] }
const inList: FilterCondition = { operator: 'in', value: ['draft', 'pending'] }
const search: FilterCondition = { operator: 'fts', value: 'typescript' }

OrConditionGroup<T>

Gruppo di condizioni in OR logico (almeno una soddisfatta).

interface OrConditionGroup<T> {
  or: {
    field: keyof T
    condition: FilterCondition
  }[]
}
interface User { id: number; status: string; role: string }

const orGroup: OrConditionGroup<User> = {
  or: [
    { field: 'status', condition: { operator: 'eq', value: 'active' } },
    { field: 'role', condition: { operator: 'eq', value: 'admin' } },
  ],
}

PaginationOptions

interface PaginationOptions {
  limit: number
  offset?: number   // offset-based
  cursor?: string   // cursor-based (alternativa per dataset grandi)
}
// Prima pagina di 20
const p1: PaginationOptions = { limit: 20, offset: 0 }

// Seconda pagina
const p2: PaginationOptions = { limit: 20, offset: 20 }

// Cursor-based (performance costanti)
const cursor: PaginationOptions = { limit: 50, cursor: 'eyJpZCI6MTAwfQ==' }

SortOptions<T>

interface SortOptions<T> {
  field: keyof T | string
  direction: 'asc' | 'desc'
  nullsFirst?: boolean
  recursive?: boolean // ordinamento su relazioni embedded
}
CampoTipoRequiredDescrizione
fieldkeyof T | stringCampo entità o path relazione (es. 'category.name')
direction'asc' | 'desc'Direzione
nullsFirstbooleantrue/false/undefined (default DB)
recursivebooleanOrdinamento su campi di relazioni embedded

VerticalFilterOptions

Vertical filtering (selezione campi) + resource embedding (relazioni FK).

interface VerticalFilterOptions {
  select?: string[]
  embed?: Record<string, unknown>
}
// Selezione campi
const select: VerticalFilterOptions = { select: ['id', 'name', 'email'] }

// Con embedding di relazioni FK
const withRelations: VerticalFilterOptions = {
  select: ['id', 'title', 'content'],
  embed: {
    author: { select: ['name', 'avatar'] },
    comments: { select: ['text', 'createdAt'], limit: 10 },
  },
}

// Inner join
const innerJoin: VerticalFilterOptions = {
  select: ['*'],
  embed: { 'profile!inner': {} },
}

FilterMetadata<T>

Container di tutti i metadati (escluse le condizioni sui campi).

interface FilterMetadata<T> {
  orConditions?: OrConditionGroup<T>[]
  sort?: SortOptions<T>[] | SortOptions<T>
  pagination?: PaginationOptions
  vertical?: VerticalFilterOptions
}

Helper

createFilterFromMetadata<T>(metadata)

function createFilterFromMetadata<T>(metadata: MetadataOnlyFilter<T>): FilterCriteria<T>

Conversione type-safe da MetadataOnlyFilter<T> (alias di FilterMetadata<T>) al tipo completo FilterCriteria<T>. Utile quando si vuole applicare solo sort/paginazione senza filtri sui campi.

import { createFilterFromMetadata } from '@pzeta/postgrest'

const metadata = {
  sort: { field: 'createdAt', direction: 'desc' as const },
  pagination: { limit: 10 },
}

const filter = createFilterFromMetadata<User>(metadata)
const users = await userRepo.readAll(filter)

isValidFilterOperator(operator)

const isValidFilterOperator = (operator: string): operator is FilterOperator

Type guard runtime per validare operatori. Case-sensitive: 'LIKE' ritorna false.

const userInput = 'eq'

if (isValidFilterOperator(userInput)) {
  // userInput è ora tipizzato come FilterOperator
  const condition: FilterCondition = { operator: userInput, value: 'active' }
}

isValidFilterValue(value)

const isValidFilterValue = (value: unknown): value is FilterValue | FilterArrayValue

Verifica che un valore sia compatibile con i filtri (string, number, boolean, Date, null, o array di questi tipi).

isValidFilterValue('active')        // true
isValidFilterValue(42)              // true
isValidFilterValue(new Date())      // true
isValidFilterValue(null)            // true
isValidFilterValue(['a', 'b'])      // true
isValidFilterValue({ key: 'v' })    // false
isValidFilterValue(undefined)       // false

formatSortOption<T>(option)

const formatSortOption = <T>(option: SortOptions<T>): string

Converte SortOptions nella sintassi PostgREST (field.direction[.nullsfirst|nullslast]).

formatSortOption({ field: 'name', direction: 'asc' })
// "name.asc"

formatSortOption({ field: 'price', direction: 'desc', nullsFirst: false })
// "price.desc.nullslast"

formatSortOption({ field: 'updatedAt', direction: 'desc', nullsFirst: true })
// "updatedAt.desc.nullsfirst"

buildVerticalSelect(options)

const buildVerticalSelect = (options?: VerticalFilterOptions): string | undefined

Costruisce la stringa per il parametro select di PostgREST combinando vertical filtering e resource embedding.

buildVerticalSelect({ select: ['id', 'name', 'email'] })
// "id,name,email"

buildVerticalSelect({
  select: ['id', 'title'],
  embed: { author: { select: ['name'] }, comments: {} },
})
// 'id,title,author({"select":["name"]}),comments'

buildVerticalSelect()  // undefined
buildVerticalSelect({}) // undefined

Esempio finale — FilterCriteria<User> type-safe

import type { FilterCriteria } from '@pzeta/postgrest'

interface User {
  id: number
  name: string
  email: string
  age: number
  status: string
  createdAt: Date
}

const filter: FilterCriteria<User> = {
  // Filtri sui campi (AND implicito)
  status: { operator: 'eq', value: 'active' },
  age: { operator: 'gte', value: 18 },
  name: { operator: 'ilike', value: '%mario%' },

  // OR conditions
  orConditions: [{
    or: [
      { field: 'email', condition: { operator: 'ilike', value: '%@gmail.com' } },
      { field: 'email', condition: { operator: 'ilike', value: '%@pzeta.it' } },
    ],
  }],

  // Sort multiplo
  sort: [
    { field: 'createdAt', direction: 'desc' },
    { field: 'name', direction: 'asc' },
  ],

  // Paginazione
  pagination: { limit: 20, offset: 0 },

  // Selezione campi
  vertical: {
    select: ['id', 'name', 'email', 'status'],
  },
}

const users = await userRepo.readAll(filter)

FilterableFields<T>

Helper type che estrae solo i campi filtrabili (compatibili con FilterValue o FilterArrayValue).

interface User {
  id: number
  name: string
  metadata: object  // ❌ non filtrabile
  tags: string[]
}

type Filterable = FilterableFields<User>
// 'id' | 'name' | 'tags'

Tipi correlati