PostgRESTRepository
Overview
PostgRESTRepository è l'implementazione concreta di ICrudRepository che parla HTTP verso un'istanza PostgREST. Estende CrudRepositoryBase e aggiunge:
- Operazioni RPC (GET e POST) verso
rpc/<funzione> - Caching integrato con
CacheServicee background refresh - Paginazione
limit/offsete cursor-based con headerPrefer: count=exact - Supporto multi-schema PostgreSQL via header
Accept-Profile/Content-Profile - Operazioni bulk:
createMany,updateMany,deleteMany,deleteManyByFilter - Prefetch proattivo (
prefetchRecord,prefetchRecords)
Path multipli sulla stessa istanza: invocato con path diversi su ogni chiamata.
Costruttore
new PostgRESTRepository(
baseUrl: string,
cacheTTL?: number,
_useBackgroundRefresh = true,
isAuthRequired = false,
useCache = false,
authHandler?: IAuthHandler,
schema?: string
)
| Parametro | Tipo | Default | Descrizione |
|---|---|---|---|
baseUrl | string | — | URL base dell'API PostgREST (es. https://api.example.com) |
cacheTTL | number? | undefined | TTL cache in millisecondi |
_useBackgroundRefresh | boolean | true | Refresh cache in background (stale-while-revalidate) |
isAuthRequired | boolean | false | Se true, le richieste includono token via authHandler |
useCache | boolean | false | Abilita caching delle risposte read |
authHandler | IAuthHandler? | — | Handler di autenticazione (vedi AuthStrategy) |
schema | string? | 'public' | Schema PostgreSQL da targettare |
API CRUD
| Metodo | Signature | Note |
|---|---|---|
create | create<T>(path, element, _keyName?): Promise<T | undefined> | POST con Prefer: return=representation |
createOrUpdate | createOrUpdate<T>(path, element, keyName): Promise<T | undefined> | UPSERT via on_conflict |
read | read<T, K>(path, key, keyName, forceRefresh?): Promise<T | undefined> | GET singolo, Accept: vnd.pgrst.object+json |
readAll | readAll<T>(path, filters?, forceRefresh?): Promise<T[] | PaginatedResponse<T> | undefined> | Polimorfico: array o paginato in base ai filtri |
readPaginated | readPaginated<T>(path, filters?, forceRefresh?): Promise<PaginatedResponse<T> | undefined> | Sempre risposta paginata (default limit: 20) |
update | update<T, K>(path, key, element, keyName): Promise<T | undefined> | PATCH parziale |
delete | delete<T, K>(path, key, keyName): Promise<T | undefined> | DELETE con representation |
createMany | createMany<T>(path, elements): Promise<T[] | undefined> | POST batch atomico |
updateMany | updateMany<T>(path, element, filters?): Promise<T[] | undefined> | PATCH bulk filtrato |
deleteMany | deleteMany<T, K>(path, keys, keyName): Promise<T[] | undefined> | DELETE via IN (...) |
deleteManyByFilter | deleteManyByFilter<T>(path, filters): Promise<T[] | undefined> | DELETE su criteri |
getNextPage | getNextPage<T>(path, cursor, limit?, filters?): Promise<PaginatedResponse<T> | undefined> | Paginazione cursor-based |
API RPC
| Metodo | Signature | Trasporto |
|---|---|---|
rpcGet | rpcGet<T>(path, params?): Promise<T[] | undefined> | GET rpc/<funzione>?param=... |
rpcRawGet | rpcRawGet<X>(path, params?, cached?): Promise<X> | GET con tipo di ritorno generico |
rpcPost | rpcPost<X>(path, params?): Promise<X> | POST body JSON |
rpcRawPost | rpcRawPost<X>(path, params?): Promise<X> | POST mai cachato |
Gestione cache
| Metodo | Descrizione |
|---|---|
invalidateCache<K>(path, key) | Invalida cache di un singolo record |
invalidateAllCache(path) | Invalida tutta la cache per la tabella (read/ + readAll/) |
getCount(path, filters?) | Restituisce solo il conteggio totale (header Content-Range) |
prefetchRecord(path, key, keyName) | Precarica un record in background (best-effort) |
prefetchRecords<T>(path, filters?) | Precarica una lista in background |
FilterCriteria
I metodi readAll, readPaginated, updateMany, deleteManyByFilter accettano un oggetto FilterCriteria<T> con operatori PostgREST nativi.
interface FilterCondition {
operator: 'eq' | 'neq' | 'gt' | 'lt' | 'gte' | 'lte' |
'in' | 'like' | 'ilike' | 'is' | 'not' |
'between' | 'cs' | 'cd' |
'fts' | 'plfts' | 'phfts' |
'sl' | 'sr' | 'nxl' | 'nxr' | 'adj'
value: FilterValue | FilterValue[]
}
interface FilterCriteria<T> {
[field: keyof T]: FilterCondition
sort?: { field: keyof T; direction: 'asc' | 'desc' }
pagination?: { limit?: number; offset?: number; cursor?: string }
}
Esempi rapidi:
// Uguaglianza
{ status: { operator: 'eq', value: 'active' } }
// Range numerico
{ price: { operator: 'between', value: [10, 100] } }
// Full-text search
{ content: { operator: 'fts', value: 'typescript' } }
// IN list
{ idruolo: { operator: 'in', value: [1, 2, 3] } }
// Pattern matching case-insensitive
{ email: { operator: 'ilike', value: '*@gmail.com' } }
Dettagli completi: FilterCriteria.
Paginazione
Due modalità di paginazione, entrambe restituiscono PaginatedResponse<T>:
interface PaginatedResponse<T> {
data: T[]
pagination: {
totalCount?: number
hasMore: boolean
nextCursor?: string
}
}
Offset-based (classica)
const page = await repo.readPaginated<User>('/users', {
pagination: { limit: 20, offset: 40 },
sort: { field: 'datacreazione', direction: 'desc' },
})
console.log(`Totale: ${page?.pagination.totalCount}`)
L'header Prefer: count=exact viene aggiunto automaticamente quando pagination è presente, popolando totalCount dall'header Content-Range.
Cursor-based (infinite scroll)
let cursor: string | undefined
do {
const page = cursor
? await repo.getNextPage<User>('/users', cursor, 50)
: await repo.readPaginated<User>('/users', { pagination: { limit: 50 } })
for (const u of page?.data ?? []) renderUser(u)
cursor = page?.pagination.nextCursor
} while (cursor)
RPC — chiamare funzioni PostgreSQL
PostgREST espone le funzioni PostgreSQL sotto /rpc/<nome_funzione>. Usa rpcGet per funzioni read-only, rpcPost per funzioni che modificano dati o accettano payload complessi.
// SELECT * FROM get_active_users(min_age => 18)
const users = await repo.rpcGet<User>('get_active_users', { min_age: 18 })
// Funzione che restituisce un primitivo
const count = await repo.rpcRawGet<number>('count_active_users', {}, true)
// Funzione di scrittura
const result = await repo.rpcPost<{ ok: boolean }>('process_batch', { batchId: 123 })
rpcPost non è mai cachato (cacheKey: 'no'). Se hai bisogno di caching su una funzione read-only complessa con payload nel body, valuta rpcRawGet con cached: true.Esempio completo type-safe
import { PostgRESTRepository } from '@pzeta/postgrest'
import type { FilterCriteria } from '@pzeta/postgrest'
interface User extends Record<string, unknown> {
idutente: number
nome: string
email: string
status: 'active' | 'inactive'
datacreazione: string
}
const repo = new PostgRESTRepository(
'https://api.example.com',
3_600_000, // cache TTL 1 ora
true, // background refresh
true, // auth required
true, // use cache
authHandler,
'app', // schema PostgreSQL
)
// Create
const nuovo = await repo.create<User>('/utenti', {
nome: 'Mario Rossi',
email: 'mario@example.com',
status: 'active',
})
// Read singolo
const utente = await repo.read<User, number>('/utenti', 1, 'idutente')
// ReadAll con filtri
const attivi = await repo.readAll<User>('/utenti', {
status: { operator: 'eq', value: 'active' },
sort: { field: 'datacreazione', direction: 'desc' },
})
// ReadPaginated
const pagina = await repo.readPaginated<User>('/utenti', {
pagination: { limit: 20, offset: 0 },
})
// Update
await repo.update<User, number>('/utenti', 1, { nome: 'Mario R.' }, 'idutente')
// Delete bulk
await repo.deleteMany<User, number>('/utenti', [5, 12, 23], 'idutente')
// RPC
const stats = await repo.rpcRawGet<DashboardStats>('get_dashboard_stats')
Cross-reference
FilterCriteria— operatori e struttura filtriCacheService— caching sottostante (background refresh, stampede control)AuthStrategy— strategie autenticazione (JWT, Cookie, NGINX, ApiKey)EnhancedPostgRESTRepository— wrapper semplificato per singola entitàRestError— errori tipizzati PostgREST
EnhancedPostgRESTRepository
Wrapper semplificato di PostgRESTRepository con path e keyName fissi nel costruttore — API minimale type-safe per singola entità.
RepositoryFactory
Factory statica per creare EnhancedPostgRESTRepository con preset enterprise — JWT, Cookie, Cached 24h, Realtime, Public — applicando DEFAULT_REPOSITORY_CONFIG.