Logging & Utils

FilterUtils

Utility statica per convertire FilterCriteria type-safe in query parameters PostgREST, parsare l'header Content-Range e costruire headers per operazioni bulk e paginate.

Cos'è

FilterUtils è una classe static-only (senza stato) che funge da ponte fra il modello tipato del package — FilterCriteria<T> — e la sintassi querystring nativa di PostgREST. Tutti i repository e i service della libreria la usano internamente; viene esposta pubblicamente per casi avanzati (build URL ad-hoc, paginazione custom, integrazione con MFE).

import { FilterUtils } from '@pzeta/postgrest'

API

MetodoFirmaScopo
buildQueryParams<T>(filters?: FilterCriteria<T> | MetadataOnlyFilter<T>) => Record<string, string>Converte un FilterCriteria in query params PostgREST (filtri AND, OR, sort, paginazione, select verticale)
buildPaginationHeaders(preferCount = false) => Record<string, string>Costruisce Prefer: count=exact per ricevere il conteggio totale nell'header Content-Range
extractPaginationInfo(response: AxiosResponse) => { totalCount?, hasMore, nextCursor? }Estrae informazioni di paginazione dagli headers Content-Range e next-range
parseContentRange(header?: string | null) => numberParser dell'header Content-Range (0-9/4242, 0-9/*0)
hasPagination<T>(filters?) => booleanVerifica se i criteri includono limit, offset o cursor validi
buildBulkDeleteParams<T>(filters?: FilterCriteria<T>) => Record<string, string>Query params per DELETE massiva (ignora sort/pagination/vertical)
buildBulkUpdateParams<T>(filters?: FilterCriteria<T>) => Record<string, string>Alias di buildBulkDeleteParams per PATCH massivo
buildBulkOperationHeaders(returnRepresentation = false) => Record<string, string>Headers per bulk POST/PATCH (Prefer: return=representation o return=minimal)

buildQueryParams — il caso d'uso principale

Trasforma un FilterCriteria<T> in un oggetto Record<string, string> pronto per essere passato a URLSearchParams, Axios params, o Fetch URL:

import { FilterUtils } from '@pzeta/postgrest'

interface User {
  iduser: number
  name: string
  status: 'active' | 'inactive'
  createdAt: Date
}

const params = FilterUtils.buildQueryParams<User>({
  name: { operator: 'ilike', value: '%mario%' },
  status: { operator: 'eq', value: 'active' },
  sort: { field: 'createdAt', direction: 'desc' },
  pagination: { limit: 20, offset: 0 },
})

// Risultato:
// {
//   name: 'ilike.%25mario%25',
//   status: 'eq.active',
//   order: 'createdAt.desc',
//   limit: '20',
//   offset: '0',
// }

// → URL finale: ?name=ilike.%25mario%25&status=eq.active&order=createdAt.desc&limit=20&offset=0

I valori sono URL-encoded automaticamente (encodeURIComponent); Date viene serializzato come ISO string; gli array per in.(...) e between sono gestiti nativamente.

Tabella operatori: FilterOperator → PostgREST syntax

FilterOperatorValore esempioPostgREST querystringNote
eq'active'?col=eq.activeUguaglianza esatta
neq'deleted'?col=neq.deletedDiverso da
gt100?col=gt.100Maggiore di
lt100?col=lt.100Minore di
gte18?col=gte.18Maggiore o uguale
lte5?col=lte.5Minore o uguale
like'%john%'?col=like.%25john%25Pattern case-sensitive
ilike'%mario%'?col=ilike.%25mario%25Pattern case-insensitive
in['a','b','c']?col=in.(a,b,c)Valore in insieme
cs'{ts}'?col=cs.%7Bts%7DArray/range contains
cd'{ts,js}'?col=cd.%7Bts%2Cjs%7DArray/range contained by
between[10, 100]?col=and=(gte.10,lte.100)Range chiuso (composto AND)
isnull?col=is.nullConfronto NULL
notnull?col=not.nullNOT NULL
fts'typescript'?col=fts.typescriptFull-text search
plfts'ts prog'?col=plfts.ts%20progPlain-to-tsquery
phfts'clean arch'?col=phfts.clean%20archPhrase-to-tsquery
sl'[1,10]'?col=sl.%5B1%2C10%5DStrictly left of (range)
sr'[1,10]'?col=sr.%5B1%2C10%5DStrictly right of (range)
nxl'[1,10]'?col=nxl.%5B1%2C10%5DDoes not extend to left
nxr'[1,10]'?col=nxr.%5B1%2C10%5DDoes not extend to right
adj'[1,10]'?col=adj.%5B1%2C10%5DAdjacent to (range)
La sintassi nativa PostgREST per like/ilike usa l'asterisco (*) come wildcard, mentre il valore SQL grezzo usa %. FilterUtils non sostituisce automaticamente: passa il pattern come lo vuoi sul server ('%mario%' se il backend accetta SQL standard, '*mario*' se l'API espone wildcard PostgREST).

Filtri OR (orConditions)

Gli operatori si possono combinare in gruppi OR:

const params = FilterUtils.buildQueryParams<User>({
  status: { operator: 'eq', value: 'active' },
  orConditions: [{
    or: [
      { field: 'role', condition: { operator: 'eq', value: 'admin' } },
      { field: 'role', condition: { operator: 'eq', value: 'moderator' } },
    ],
  }],
})
// → ?status=eq.active&or=(role.eq.admin,role.eq.moderator)

Paginazione e Content-Range

PostgREST ritorna il conteggio totale solo se richiesto via header Prefer: count=exact:

const response = await axios.get('/users', {
  params: FilterUtils.buildQueryParams({ pagination: { limit: 20 } }),
  headers: FilterUtils.buildPaginationHeaders(true),
})

const info = FilterUtils.extractPaginationInfo(response)
// info.totalCount = 150 (da header Content-Range: "0-19/150")
// info.hasMore = true se presente header next-range

Per parsing manuale dell'header (es. da Fetch):

const total = FilterUtils.parseContentRange(response.headers.get('Content-Range'))
// "0-19/150" → 150
// "0-19/*"   → 0
// null       → 0

Bulk DELETE / PATCH

Per operazioni massive, le proprietà sort, pagination, vertical sono ignorate (non hanno senso semantico):

const params = FilterUtils.buildBulkDeleteParams<User>({
  status: { operator: 'eq', value: 'inactive' },
  lastLogin: { operator: 'lt', value: new Date('2024-01-01') },
})
// → ?status=eq.inactive&lastLogin=lt.2024-01-01T00%3A00%3A00.000Z

const headers = FilterUtils.buildBulkOperationHeaders(true)
// { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Prefer': 'return=representation' }

await axios.patch('/users', { archived: true }, { params, headers })

Pattern: combinare con URLSearchParams

buildQueryParams ritorna un oggetto piatto: usalo con URLSearchParams per costruire URL manualmente:

const params = FilterUtils.buildQueryParams({ name: { operator: 'ilike', value: '%foo%' } })
const url = `/users?${new URLSearchParams(params).toString()}`
// → /users?name=ilike.%25foo%25
buildQueryParams ritorna valori già URL-encoded. Non passare a encodeURIComponent nuovamente: causa double-encoding.

Tipi correlati