FilterCriteria
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
| Operatore | Significato | Esempio PostgREST |
|---|---|---|
eq | Uguale a | name=eq.John |
neq | Diverso da | status=neq.deleted |
gt | Maggiore | age=gt.18 |
gte | Maggiore o uguale | quantity=gte.10 |
lt | Minore | price=lt.100 |
lte | Minore o uguale | rating=lte.5 |
Operatori per stringhe
| Operatore | Significato | Esempio |
|---|---|---|
like | Pattern matching case-sensitive (wildcard %) | name=like.*john* |
ilike | Pattern matching case-insensitive | email=ilike.*@gmail.com |
Operatori per array/insiemi
| Operatore | Significato | Esempio |
|---|---|---|
in | Valore in un insieme | status=in.(active,pending) |
cs | Contains (array contains) | tags=cs.{typescript} |
cd | Contained by | tags=cd.{typescript,javascript} |
Operatori per NULL/booleani
| Operatore | Significato | Esempio |
|---|---|---|
is | Confronto con null/true/false | deleted_at=is.null |
not | Negazione di is | active=not.is.null |
Operatori per range
| Operatore | Significato | Esempio |
|---|---|---|
between | Range inclusivo | date=between.2024-01-01,2024-12-31 |
sl | Strictly left of | range=sl.[1,10] |
sr | Strictly right of | range=sr.[1,10] |
nxl | Does not extend to the left | range=nxl.[1,10] |
nxr | Does not extend to the right | range=nxr.[1,10] |
adj | Adiacente a | range=adj.[1,10] |
Full-Text Search
| Operatore | Significato | Esempio |
|---|---|---|
fts | FTS semplice (to_tsquery) | content=fts.typescript |
plfts | Plain to tsquery | content=plfts.typescript programming |
phfts | Phrase to tsquery | content=phfts.clean architecture |
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
}
| Campo | Tipo | Required | Descrizione |
|---|---|---|---|
field | keyof T | string | ✓ | Campo entità o path relazione (es. 'category.name') |
direction | 'asc' | 'desc' | ✓ | Direzione |
nullsFirst | boolean | — | true/false/undefined (default DB) |
recursive | boolean | — | Ordinamento 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
ICrudRepository—readAlleupdateManyaccettanoFilterCriteria<T>PaginatedResponse— formato ritornato quando si usapaginationPostgRESTClient.repository()— factory del repository
CacheMetadata
Value objects immutabili per la gestione cache — elementi cached, metadati temporali, snapshot stato, configurazione stale-while-revalidate e risposte HTTP cached.
Interfaces
Contratti del domain layer — IAuthHandler, IAuthService, IAuthStrategy, ICacheService, ICacheStrategy, ICrudRepository, IHttpClient, ILogger. Punti di estensione e DI.