Facade & Services

BasePostgrestService

Classe base astratta per service applicativi PostgREST. Wrapper di alto livello su EnhancedPostgRESTRepository con API CRUD omogenea, error messages italiani e helper RPC.

Cos'è

BasePostgrestService<T, K> è la classe base astratta che ogni service di dominio dovrebbe estendere quando integra PostgREST in un progetto PZeta. Fornisce:

  • API CRUD omogenea (getAll, getById, create, update, delete)
  • Error messages user-facing in italiano configurabili via entityName
  • Helper protetto callRpc<R>() per stored procedure PostgREST
  • Accesso al client sottostante via this.client (per HTTP low-level) e al repository via this.repo (per query avanzate)
Promossa da vue-magazzino per garantire un pattern uniforme tra tutti i progetti che integrano @pzeta/postgrest. Per la variante MFE basata su MFEHttpClient vedi BaseMfePostgrestService in @pzeta/postgrest/mfe.

Contratto

abstract class BasePostgrestService<
  T extends Record<string, unknown>,
  K extends string | number = number,
> {
  protected readonly repo: EnhancedPostgRESTRepository<T, K>
  protected readonly client: PostgRESTClient
  protected readonly entityName: string

  constructor(
    client: PostgRESTClient,
    endpoint: string,
    primaryKey: keyof T & string,
    entityName?: string,
  )

  getAll(filters?: FilterCriteria<T>): Promise<T[]>
  getById(id: K): Promise<T>
  create(data: Partial<T>): Promise<T>
  update(id: K, data: Partial<T>): Promise<T>
  delete(id: K): Promise<void>

  protected callRpc<R>(rpcPath: string, params: Record<string, unknown>): Promise<R>
}

Costruttore

ParametroTipoRequiredDescrizione
clientPostgRESTClientClient PostgREST già inizializzato (await client.init())
endpointstringPath della risorsa PostgREST (es. /clienti, /v_fatture_lista)
primaryKeykeyof T & stringNome della colonna chiave primaria (es. idcliente)
entityNamestringNome user-facing per error messages (default: 'Record')

API CRUD

MetodoFirmaNote
getAll(filters?: FilterCriteria<T>) => Promise<T[]>Delega a repo.readAll()
getById(id: K) => Promise<T>Lancia Error: {entityName} non trovato: {id} se non esiste
create(data: Partial<T>) => Promise<T>Lancia Error: Impossibile creare {entityName} se fallisce
update(id: K, data: Partial<T>) => Promise<T>Lancia Error: Impossibile aggiornare {entityName}: {id} se fallisce
delete(id: K) => Promise<void>Non lancia su record non esistente
callRpc<R>(rpcPath, params) => Promise<R>Protetto — esposto solo a classi figlie
Error messages italiani: i messaggi di errore sono pensati per essere catturati e mostrati direttamente all'utente finale. Il template è {Entita} non trovato, Impossibile creare {entita}, Impossibile aggiornare {entita}: {id}. Il parametro entityName viene normalizzato a lowercase per create/update.

Esempio — Service di dominio

import { BasePostgrestService, type PostgRESTClient } from '@pzeta/postgrest'

interface Cliente {
  idcliente: number
  ragionesociale: string
  email: string
  pivacf: string
}

export class ClientiService extends BasePostgrestService<Cliente, number> {
  constructor(client: PostgRESTClient) {
    super(client, '/clienti', 'idcliente', 'Cliente')
  }

  // Metodo di dominio aggiuntivo — usa `this.repo` per query avanzate
  async cercaPerRagioneSociale(ragione: string): Promise<Cliente[]> {
    return this.getAll({
      ragionesociale: { operator: 'ilike', value: `%${ragione}%` },
      sort: { field: 'ragionesociale', direction: 'asc' },
    })
  }

  // RPC custom — usa `this.callRpc<R>` per stored procedure
  async aggiornaSaldo(idcliente: number, importo: number): Promise<number> {
    return this.callRpc<number>('/rpc/aggiorna_saldo_cliente', {
      p_idcliente: idcliente,
      p_importo: importo,
    })
  }
}

Utilizzo nel layer applicativo

const client = new PostgRESTClient({ apiUrl: 'https://api.example.com' })
await client.init()
await client.login({ email: 'admin@pzeta.it', password: '...' })

const clientiService = new ClientiService(client)

try {
  const trovati = await clientiService.cercaPerRagioneSociale('rossi')
  const cliente = await clientiService.getById(42)
  await clientiService.update(42, { email: 'nuovo@example.com' })
} catch (err) {
  // err.message → "Cliente non trovato: 99"
  showErrorToast((err as Error).message)
}

Integrazione con Dependency Injection

BasePostgrestService è compatibile out-of-the-box con qualsiasi container DI (Inversify, tsyringe, Pinia, ecc.). Esempio Inversify:

import { injectable, inject } from '@inversify/core'

@injectable()
export class ClientiService extends BasePostgrestService<Cliente, number> {
  constructor(@inject('PostgRESTClient') client: PostgRESTClient) {
    super(client, '/clienti', 'idcliente', 'Cliente')
  }
}

In Vue con Pinia:

import { defineStore } from 'pinia'

export const useClientiStore = defineStore('clienti', () => {
  const service = new ClientiService(usePostgRESTClient())
  return { service }
})

Convenzione naming RPC PostgREST

Per callRpc<R> rispetta la convenzione PostgREST:

  • Path: /rpc/{nome_funzione}
  • Parametri: prefisso p_ (es. p_idcliente, p_importo)
  • Ritorno: tipizza con il type parameter R
// Stored procedure: aggiorna_ticket(p_idticket int, p_stato text) RETURNS json
const result = await this.callRpc<{ ok: boolean; idticket: number }>(
  '/rpc/aggiorna_ticket',
  { p_idticket: 42, p_stato: 'chiuso' },
)

BasePostgrestService vs BaseMfePostgrestService

AspettoBasePostgrestServiceBaseMfePostgrestService
Package@pzeta/postgrest@pzeta/postgrest/mfe
HTTP clientPostgRESTClient (gestisce auth/cache internamente)MFEHttpClient (auth gestita dalla shell host)
AuthStrategie native (JWT, Cookie, ApiKey, None)Token gestito dalla shell
CacheCacheService internoDelegata alla shell / no cache
Use caseApp standalone, backend, microserviziMicrofrontend integrati in shell

Tipi correlati