Repositories

BaseEnhancedRepository

Classe base estendibile che fornisce CRUD + RPC + cache su path e keyName fissi — punto di estensione per repository di dominio con metodi custom type-safe.

Overview

BaseEnhancedRepository<T, K> è la classe base astratta che incapsula un'istanza interna di PostgRESTRepository e ne espone l'API con path e keyName già fissati. Elimina il boilerplate (DRY) e garantisce type safety completa su entità e chiave primaria.

abstract class BaseEnhancedRepository<T extends Record<string, unknown>, K>

EnhancedPostgRESTRepository è una sottoclasse "vuota" che esiste solo per stabilizzare il nome pubblico. Estendi BaseEnhancedRepository direttamente quando hai bisogno di aggiungere metodi custom di dominio.

Quando estenderla

  • Repository di dominio con metodi specifici (findByEmail, searchByTags, getActiveOnly)
  • Service che incapsula logica di business + accesso dati su una singola aggregate root
  • Override di metodi CRUD per aggiungere logica (audit logging, validazione)
In progetti DDD, un *Repository di dominio è tipicamente una sottoclasse di BaseEnhancedRepository con path e keyName impliciti, più i metodi del Repository contract DDD (findByEmail, findActiveOnly, ecc.).

Stato protetto

CampoTipoDescrizione
keyNamekeyof TNome chiave primaria (immutabile)
pathstringPath base senza query string
queryArgsstringQuery string statica (se path contiene ?)
crudServicePostgRESTRepositoryIstanza interna che esegue le operazioni HTTP

Il metodo helper protetto getFullPath() ricostruisce il path completo (path + ?queryArgs) per ogni operazione.

EnhancedRepositoryOptions<T>

export interface EnhancedRepositoryOptions<T extends Record<string, unknown>> {
  baseUrl: string
  path: string
  keyName: keyof T
  isAuthRequired?: boolean        // default: false
  useCache?: boolean              // default: false
  cacheTTL?: number               // ms
  useBackgroundRefresh?: boolean  // default: true
  schema?: string                 // default: 'public'
}
OpzioneTipoDefaultDescrizione
baseUrlstringURL base PostgREST
pathstringPath tabella (può includere ?select=...)
keyNamekeyof TColonna chiave primaria
isAuthRequiredboolean?falseToken JWT/cookie incluso nelle richieste
useCacheboolean?falseCache read abilitata
cacheTTLnumber?undefinedTTL cache in ms
useBackgroundRefreshboolean?trueStale-while-revalidate
schemastring?'public'Schema PostgreSQL

API ereditata

Tutti i metodi di EnhancedPostgRESTRepository (CRUD, bulk, RPC, cache, prefetch) sono definiti qui. Tornano automaticamente disponibili nelle sottoclassi senza override.

Esempio: repository di dominio con metodi custom

import { BaseEnhancedRepository } from '@pzeta/postgrest'

interface User extends Record<string, unknown> {
  idutente: number
  nome: string
  email: string
  status: 'active' | 'inactive'
  ultimoaccesso: string | null
}

class UserRepository extends BaseEnhancedRepository<User, number> {
  constructor(baseUrl: string) {
    super({
      baseUrl,
      path: '/utenti',
      keyName: 'idutente',
      isAuthRequired: true,
      useCache: true,
      cacheTTL: 3_600_000,
      schema: 'app',
    })
  }

  /**
   * Trova un utente per email (caso d'uso DDD)
   */
  async findByEmail(email: string): Promise<User | undefined> {
    const utenti = await this.readAll({
      email: { operator: 'eq', value: email },
    })
    return utenti[0]
  }

  /**
   * Tutti gli utenti attivi degli ultimi 30 giorni
   */
  async findActiveRecent(): Promise<User[]> {
    const trentaGiorniFa = new Date(Date.now() - 30 * 86_400_000).toISOString()
    return await this.readAll({
      status: { operator: 'eq', value: 'active' },
      ultimoaccesso: { operator: 'gte', value: trentaGiorniFa },
      sort: { field: 'ultimoaccesso', direction: 'desc' },
    })
  }

  /**
   * Override di update: aggiunge audit log prima dell'operazione
   */
  override async update(
    key: number,
    element: Partial<User>,
  ): Promise<User | undefined> {
    await this.rpcRawPost<void>('audit_log_user_update', {
      idutente: key,
      changes: element,
    })
    return super.update(key, element)
  }
}

// Uso
const userRepo = new UserRepository('https://api.example.com')
const mario = await userRepo.findByEmail('mario@example.com')
const recenti = await userRepo.findActiveRecent()
Quando fai override di un metodo CRUD, ricorda di chiamare super.<metodo>(...) per non perdere la invalidazione cache automatica e la gestione errori centralizzata di PostgRESTRepository.

Path con query parameters statici

Il path accetta query string PostgREST (es. select, or, embedded resources) che vengono preservate su ogni operazione:

class UserWithRoleRepository extends BaseEnhancedRepository<UserWithRole, number> {
  constructor(baseUrl: string) {
    super({
      baseUrl,
      // Embedded resource PostgREST: include sempre il ruolo
      path: '/utenti?select=idutente,nome,email,ruolo(idruolo,nome)',
      keyName: 'idutente',
    })
  }
}

Cross-reference