Repositories

RepositoryUtils

Utility statiche per gestione errori PostgREST tipizzati (RestError), parsing path URL, generazione filter hash deterministici e costruzione cache key.

Overview

RepositoryUtils è una classe statica che centralizza la logica comune ai repository: gestione errori HTTP, normalizzazione path, hash deterministici dei filtri per cache key. Include la classe RestError — l'errore tipizzato che ogni repository PostgREST lancia in caso di fallimento.

import { RepositoryUtils, RestError } from '@pzeta/postgrest'
import type { ApiError } from '@pzeta/postgrest'

RestError

Errore Error-subclass arricchito con i metadati strutturati di PostgREST. Tutti i metodi CRUD/RPC lo lanciano quando la richiesta HTTP fallisce.

export class RestError extends Error {
  status: number         // HTTP status (400, 404, 409, 500...)
  code: string           // PostgreSQL/PostgREST error code o 'HTTP_ERROR'
  details: string | null // Dettagli aggiuntivi (es. constraint violato)
  hint: string | null    // Suggerimento per la risoluzione
}
CampoTipoEsempio
name'RestError'
messagestring'duplicate key value violates unique constraint'
statusnumber409
codestring'23505' (Postgres) / 'PGRST116' (PostgREST) / 'HTTP_ERROR'
detailsstring | null'Key (email)=(mario@example.com) already exists.'
hintstring | null'Use ON CONFLICT or check uniqueness before insert'

Try/catch tipizzato

import { RestError } from '@pzeta/postgrest'

try {
  await userRepo.create({ email: 'mario@example.com', ... })
} catch (error) {
  if (error instanceof RestError) {
    switch (error.code) {
      case '23505':
        notify.error(`Email già registrata: ${error.details}`)
        break
      case '23503':
        notify.error('Riferimento non valido — controlla i campi collegati')
        break
      case '42501':
        notify.error('Permesso negato per questa operazione')
        break
      default:
        notify.error(`Errore ${error.status}: ${error.message}`)
        if (error.hint) console.info('Hint:', error.hint)
    }
  } else {
    notify.error('Errore di rete — riprova')
  }
}

Codici errore comuni

Tabella di riferimento dei code più frequenti restituiti da PostgREST/PostgreSQL:

CodeStatus HTTPSignificatoCausa tipica
23505409Unique violationInserimento duplicato su colonna UNIQUE
23503409Foreign key violationFK puntata a record inesistente o cancellazione di parent con figli
23502400Not null violationCampo obbligatorio mancante
23514400Check constraint violationValore non soddisfa CHECK
42501403Insufficient privilegePermesso negato (RLS / GRANT)
42P01404Undefined tableTabella/vista inesistente o schema sbagliato
42703400Undefined columnColonna inesistente nel select=
PGRST116406No rows foundAccept: vnd.pgrst.object+json con 0 risultati
PGRST301401JWT expired/invalidToken scaduto o malformato
PGRST302401Anonymous role disabledRichiesta senza JWT su endpoint privato
HTTP_ERRORqualsiasiErrore HTTP senza struttura ApiErrorProxy / network / 5xx generici
In applicazioni Vue PZeta, mappa i code in messaggi italiani user-facing centralizzati. Usa error.details per il debug developer (console) e error.message come fallback UI.

API RepositoryUtils

handleError(err): never

Trasforma un errore Axios in RestError tipizzato. Lancia sempre — usato internamente da ogni metodo CRUD di PostgRESTRepository.

try {
  await axios.get('/api/users')
} catch (error) {
  RepositoryUtils.handleError(error) // throws RestError
}

Logica interna:

  1. TypeGuards.hasHttpResponse(err) → estrae status e data
  2. Se data ha code + message → lancia RestError strutturato
  3. Altrimenti → lancia RestError con code: 'HTTP_ERROR'
  4. Errori di rete senza response → throw new Error('Errore di rete imprevisto')

parsePath(path): [basePath, queryArgs]

Separa path base e query string:

const [base, args] = RepositoryUtils.parsePath('/utenti?select=idutente,nome')
// base => '/utenti'
// args => 'select=idutente,nome'

buildFullPath(path, queryArgs?, newParams?): string

Combina path base con parametri esistenti e nuovi (merge con sovrascrittura su chiavi duplicate):

RepositoryUtils.buildFullPath('/utenti', 'select=idutente,nome', {
  status: 'eq.active',
})
// => '/utenti?select=idutente%2Cnome&status=eq.active'

generateFilterHash(filters?): string

Genera un hash deterministico compatto per usare i filtri come cache key. Le chiavi sono ordinate alfabeticamente, pagination e sort ricevono formato dedicato.

RepositoryUtils.generateFilterHash({
  name: { operator: 'eq', value: 'Mario' },
  pagination: { limit: 20, offset: 0 },
})
// => 'name[operator:eq,value:Mario]|p[l:20]'

assertDefined<T>(value, message): asserts value is T

Assertion function per narrowing TypeScript:

RepositoryUtils.assertDefined(user, 'Utente non trovato')
// Da qui TypeScript sa che user: User (non undefined)
console.log(user.nome)

Metodi deprecati

MetodoStatoSostituto
generateCacheKey(operation, path, key?)@deprecatedUsa template literal ${operation}/${path}/${key}
buildHeaders(options)@deprecatedCostruzione inline negli specifici metodi
I metodi deprecati saranno rimossi nella prossima major version. Non usarli in nuovo codice.

ApiError

Re-export type dalla domain layer — rappresenta lo shape standard di una risposta errore PostgREST nel body JSON:

interface ApiError {
  code: string
  message: string
  details?: string | null
  hint?: string | null
}

RepositoryUtils.handleError() la valida runtime via TypeGuards.hasProperties prima di costruire un RestError.

Cross-reference