PostgrestMfeService
Cos'è
PostgrestMfeService è il service di basso livello per consumare PostgREST da un microfrontend. Wrappa un MFEHttpClient ottenuto da useMfeContext() ed espone i verbi HTTP nella forma idiomatica PostgREST (filtro tramite querystring, header Prefer, Accept: application/vnd.pgrst.object+json per singletons, RPC tramite /rpc/{nome}).
Per la maggior parte dei casi d'uso CRUD è preferibile estendere BaseMfePostgrestService; usa PostgrestMfeService direttamente solo per chiamate ad-hoc, RPC sciolti o quando hai bisogno di controllo fine sull'URL.
Signature
class PostgrestMfeService {
constructor(http: MFEHttpClient, logger?: ILogger)
get<T>(resource: string, query?: PostgrestQuery): Promise<{ data: T[]; totalRecords: number }>
getOne<T>(resource: string, query?: PostgrestQuery): Promise<T>
post<T>(resource: string, body: Record<string, unknown>): Promise<{ data: T[] }>
patch<T>(resource: string, body: Record<string, unknown>, query?: PostgrestQuery): Promise<{ data: T[] }>
delete(resource: string, query?: PostgrestQuery): Promise<void>
rpc<T>(functionName: string, params: Record<string, unknown>): Promise<T>
}
Costruttore
| Parametro | Tipo | Default | Descrizione |
|---|---|---|---|
http | MFEHttpClient | — | HTTP client fornito dalla shell host (via useMfeContext().http) |
logger | ILogger | new ConsoleLogger() | Logger per warning sui parametri riservati e per gli errori PostgREST |
Tipo PostgrestQuery
interface PostgrestQuery {
select?: string // Vertical filtering (es. 'id,name,email')
filters?: Record<string, string> // Filtri PostgREST grezzi (es. { name: 'ilike.*foo*' })
order?: string // Ordinamento (es. 'createdAt.desc')
limit?: number
offset?: number
}
I valori di filters sono stringhe PostgREST grezze, non oggetti FilterCondition tipizzati. Costruiscile manualmente nel formato operator.value (es. eq.42, ilike.*mario*, in.(a,b,c)).
select, order, limit, offset, on_conflict in filters vengono ignorate con un warning loggato — usa le proprietà dedicate di PostgrestQuery.API
get<T>(resource, query?) — lista paginata
Esegue una GET /resource ritornando l'array di record + il conteggio totale estratto da Content-Range (header Prefer: count=exact aggiunto automaticamente quando query.limit è definito).
const { data, totalRecords } = await service.get<Cliente>('anagrafica', {
filters: { codtipoanagrafica: 'eq.CLI', flagattivo: 'eq.true' },
order: 'denominazione.asc',
limit: 25,
offset: 50,
})
// data: Cliente[]
// totalRecords: 1240
getOne<T>(resource, query?) — singolo record
Imposta Accept: application/vnd.pgrst.object+json: PostgREST risponde con oggetto invece di array (errore 406 se 0 o > 1 risultati).
const cliente = await service.getOne<Cliente>('anagrafica', {
filters: { idanagrafica: 'eq.42' },
})
post<T>(resource, body) — INSERT
Esegue una POST /resource con header Prefer: return=representation per ricevere il record creato.
const { data } = await service.post<Cliente>('anagrafica', {
denominazione: 'ACME SRL',
codtipoanagrafica: 'CLI',
})
const creato = data[0]
patch<T>(resource, body, query?) — UPDATE
const { data } = await service.patch<Cliente>(
'anagrafica',
{ denominazione: 'ACME SPA' },
{ filters: { idanagrafica: 'eq.42' } },
)
delete(resource, query?) — DELETE
await service.delete('anagrafica', { filters: { idanagrafica: 'eq.42' } })
rpc<T>(functionName, params) — stored procedure
Invoca POST /rpc/{functionName} con il body JSON dei parametri (convenzione PostgREST: prefisso p_).
const metrics = await service.rpc<PipelineMetrics>('get_crm_pipeline_metrics', {
p_idutente: 42,
p_anno: 2026,
})
Classe PostgrestApiError
Errore custom estende Error: viene lanciato su qualsiasi fallimento HTTP/PostgREST.
class PostgrestApiError extends Error {
readonly code?: string // SQLSTATE PostgreSQL (es. '23505')
readonly details?: string | null
readonly hint?: string | null
}
Il service esegue il mapping di SQLSTATE noti in messaggi italiani user-facing:
| Codice | Messaggio |
|---|---|
23505 | "Il valore inserito è già presente nel sistema" |
23503 | "Riferimento a un elemento inesistente" |
23514 | "Il valore inserito non è valido" |
23502 | "Campo obbligatorio mancante" |
42P01, 42703 | "Risorsa non disponibile" |
42883 | "Operazione non supportata" |
P0001, P0002 | Messaggio originale del backend (eccezioni RAISE custom) |
| altri | "Operazione non riuscita. Contattare l'assistenza se il problema persiste." |
Try/catch tipizzato
import { PostgrestApiError } from '@pzeta/postgrest/mfe'
try {
await service.post<Cliente>('anagrafica', payload)
} catch (err) {
if (err instanceof PostgrestApiError) {
if (err.code === '23505') {
toast.add({ severity: 'warn', detail: 'Cliente già esistente' })
} else {
toast.add({ severity: 'error', detail: err.message })
}
}
}
Esempio completo
import { useMfeContext } from '@pzeta/mfe-contracts'
import { PostgrestMfeService } from '@pzeta/postgrest/mfe'
const { http } = useMfeContext()
const postgrest = new PostgrestMfeService(http)
// Lista con conteggio paginazione server-side
const { data, totalRecords } = await postgrest.get<Fattura>('documenti', {
select: 'iddocumenti,numerodocumento,datadocumento,idanagrafica',
filters: { codtipodocumento: 'eq.FAT', datadocumento: 'gte.2026-01-01' },
order: 'datadocumento.desc',
limit: 50,
})
// RPC
const pipeline = await postgrest.rpc<PipelineMetrics>('get_crm_pipeline_metrics', {
p_idanagrafica: 42,
})
Tipi correlati
BaseMfePostgrestService— wrapper CRUD type-safe sopraPostgrestMfeServiceMFEHttpClientApiError— value object alla base diPostgrestApiErrorConsoleLogger/NullLogger
BaseMfePostgrestService
Classe astratta CRUD per service di dominio nei microfrontend — controparte MFE di BasePostgrestService, wrappa PostgrestMfeService e usa MFEHttpClient dalla shell host.
sanitizePostgrestSearch
Utility pura per sanitizzare input utente prima di usarli in filtri PostgREST like/ilike — rimuove caratteri speciali che potrebbero alterare la semantica del filtro o causare query non valide.