MFE Integration

PostgrestMfeService

Service PostgREST a basso livello per microfrontend basato su MFEHttpClient — espone get, getOne, post, patch, delete, rpc con mapping automatico di SQLSTATE in messaggi italiani.

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

ParametroTipoDefaultDescrizione
httpMFEHttpClientHTTP client fornito dalla shell host (via useMfeContext().http)
loggerILoggernew 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)).

Le chiavi 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:

CodiceMessaggio
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, P0002Messaggio 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