Domain

Interfaces

Contratti del domain layer — IAuthHandler, IAuthService, IAuthStrategy, ICacheService, ICacheStrategy, ICrudRepository, IHttpClient, ILogger. Punti di estensione e DI.

Cosa contengono

Il file src/domain/interfaces/ raccoglie tutti i contratti del layer dominio. Implementare una di queste interfacce è sufficiente per integrarsi col resto della libreria senza modificare codice esistente — è il punto di estensione canonico per:

  • Strategie auth/cache custom
  • Mock per testing
  • Logger personalizzati
  • HTTP client alternativi
import type {
  IAuthHandler,
  IAuthService,
  IAuthStrategy,
  ICacheService,
  ICacheStrategy,
  ICrudRepository,
  IHttpClient,
  ILogger,
  AuthError,
  PaginatedResponse,
  RecordDataType,
} from '@pzeta/postgrest'

Tabella riepilogativa

InterfaceRuoloImplementazioni built-in
IAuthHandlerGestore auth lato HTTP client (token + refresh + 401 handling)AuthService
IAuthServiceOperazioni auth applicative (login, register, refresh, ...)AuthService
IAuthStrategyPersistenza e applicazione credenziali (Strategy Pattern)JwtAuthStrategy, CookieAuthStrategy, ApiKeyAuthStrategy, NoAuthStrategy
ICacheServiceServizio di caching con stale-while-revalidateCacheService
ICacheStrategyStorage cache (Strategy Pattern)IndexedDBCacheStrategy, LocalStorageCacheStrategy, SessionStorageCacheStrategy, MemoryCacheStrategy, NoneCacheStrategy
ICrudRepositoryOperazioni CRUD type-safePostgRESTRepository, EnhancedPostgRESTRepository
IHttpClientAstrazione HTTP (request, get, post, put, patch, delete)FetchHttpClient, AxiosHttpClient
ILoggerLogging strutturato (debug/warn/error)ConsoleLogger, NullLogger

IAuthHandler

Gestore auth utilizzato dall'HTTP client per aggiungere il token, tentare il refresh e gestire 401.

interface IAuthHandler {
  getToken(): string | null
  refreshToken(): Promise<AuthResponse | null>
  handleAuthError(error: AxiosError): Promise<boolean>
}
MetodoRuolo
getToken()Restituisce il token JWT corrente (o null) — chiamato prima di ogni richiesta
refreshToken()Rinnova il token usando il refresh token; null = utente deve ri-autenticarsi
handleAuthError(error)Decide se la richiesta originale va riprovata dopo il refresh
Anti-loop: handleAuthError deve verificare che l'errore non provenga dall'endpoint /auth/refresh stesso per evitare loop infiniti.

IAuthService + AuthError

Operazioni di dominio per l'autenticazione.

interface AuthError {
  code: string
  message: string
  timestamp: Date
  details?: unknown
}

interface IAuthService {
  login(credentials: UserCredentials): Promise<AuthResponse | null>
  logout(refreshToken: string | null): Promise<void>
  register(user: RegisterUser): Promise<CustomResponse>
  activateAccount(token: string): Promise<CustomResponse>
  forgotPassword(email: ForgotPassword): Promise<CustomResponse>
  resetPassword(data: ResetPassword): Promise<CustomResponse>
  refreshToken(token?: string, refreshToken?: string): Promise<AuthResponse | null>
  getLastError(): AuthError | null
}

AuthError è la variante con timestamp usata per errori del flusso auth (vs ApiError generico).


IAuthStrategy + AuthStrategyType

Strategy Pattern per intercambiare meccanismi di autenticazione (JWT, Cookie, ApiKey, None) o implementarne di custom.

enum AuthStrategyType {
  JWT = 'jwt',
  COOKIE = 'cookie',
  API_KEY = 'apiKey',
  NONE = 'none',
}

interface IAuthStrategy {
  readonly type: AuthStrategyType
  applyAuth(config: AxiosRequestConfig): AxiosRequestConfig
  isAuthenticated(): boolean
  saveCredentials(authResponse: AuthResponse): void
  clearCredentials(): void
  getToken(): string | null
  getRefreshToken(): string | null
  supportsRefresh(): boolean
}
MetodoRuolo
typeTipo immutabile (readonly)
applyAuth(config)Modifica la config Axios (header Authorization, withCredentials, ...)
isAuthenticated()Presenza credenziali (non verifica scadenza)
saveCredentials(auth)Persistenza dopo login/refresh riuscito
clearCredentials()Pulizia (logout)
getToken() / getRefreshToken()Access agli token correnti
supportsRefresh()true per JWT, false per ApiKey/None

ICacheService

Servizio di caching HTTP con supporto background refresh (stale-while-revalidate).

interface ICacheService {
  init(backgroundRefreshConfig?: Partial<BackgroundRefreshConfig>): void
  setAuthHandler(handler: IAuthHandler): void
  fetchWithCache<T>(
    config: AxiosRequestConfig,
    cacheKey?: string,
    cacheTTL?: number,
    skipCache?: boolean,
    skipSave?: boolean,
  ): Promise<AxiosResponse<T>>
  clearCache(key: string): void
  invalidateAll(): void
  cleanExpiredItems(): Promise<void>
}

Il metodo chiave è fetchWithCache<T> che combina lookup cache, fetch HTTP e save risultato. Vedi BackgroundRefreshConfig per il pattern stale-while-revalidate.


ICacheStrategy + CacheStrategyType

Strategy Pattern per lo storage cache. Tutte le operazioni sono asincrone per supportare backend come IndexedDB.

enum CacheStrategyType {
  INDEXEDDB = 'indexeddb',
  LOCALSTORAGE = 'localstorage',
  SESSIONSTORAGE = 'sessionstorage',
  MEMORY = 'memory',
  NONE = 'none',
}

interface ICacheStrategy {
  readonly type: CacheStrategyType
  setItem<T>(key: string, value: T, ttl?: number): Promise<void>
  getItem<T>(key: string): Promise<T | null>
  removeItem(key: string): Promise<void>
  clear(keyPrefix?: string): Promise<void>
  hasItem(key: string): Promise<boolean>
  getAllValues<T>(): Promise<T[]>
  getAllKeys(): Promise<string[]>
  cleanExpired(): Promise<number>
  getStats(): Promise<{ size: number; keys: number; expired: number }>
  supportsTTL(): boolean
  isPersistent(): boolean
}

Confronto strategie

StrategiaCapacitàPersistenzaPerformanceUse case
INDEXEDDB~50MB+PermanenteOttimaPWA, offline-first, grandi dataset
LOCALSTORAGE~5-10MBPermanenteBuona (sync)Config, preferenze utente
SESSIONSTORAGE~5-10MBSolo sessioneBuona (sync)Cache per singola sessione
MEMORYRAMNessunaMassimaDevelopment, testing
NONEDisabilitazione cache (no-op)

ICrudRepository + PaginatedResponse + RecordDataType

Operazioni CRUD type-safe con supporto a filtri PostgREST.

type RecordDataType =
  | string | number | boolean | Date | null | undefined
  | string[] | number[] | boolean[]

interface PaginatedResponse<T> {
  data: T[]
  pagination: {
    totalCount?: number
    hasMore: boolean
    nextCursor?: string
  }
}

interface ICrudRepository {
  create<T extends Record<string, unknown>>(
    path: string, element: Partial<T>, keyName?: keyof T,
  ): Promise<T | undefined>

  read<T extends Record<string, unknown>, K = unknown>(
    path: string, key: K, keyName: keyof T, forceRefresh?: boolean,
  ): Promise<T | undefined>

  update<T extends Record<string, unknown>, K = unknown>(
    path: string, key: K, element: Partial<T>, keyName: keyof T,
  ): Promise<T | undefined>

  delete<T extends Record<string, unknown>, K = unknown>(
    path: string, key: K, keyName: keyof T,
  ): Promise<T | undefined>

  createMany<T extends Record<string, unknown>>(
    path: string, elements: Partial<T>[], keyName?: keyof T,
  ): Promise<T[] | undefined>

  readAll<T extends Record<string, unknown>>(
    path: string, filters?: FilterCriteria<T>, forceRefresh?: boolean,
  ): Promise<T[] | PaginatedResponse<T> | undefined>

  updateMany<T extends Record<string, unknown>>(
    path: string, element: Partial<T>, filters?: FilterCriteria<T>, keyName?: keyof T,
  ): Promise<T[] | undefined>

  deleteMany<T extends Record<string, unknown>, K = unknown>(
    path: string, keys: K[], keyName: keyof T,
  ): Promise<T[] | undefined>
}
Quando si passano pagination.cursor o si richiede il count totale, il repository ritorna PaginatedResponse<T> con metadata. Altrimenti T[] semplice. Vedi EnhancedPostgRESTRepository per la logica di switch.

IHttpClient

Astrazione HTTP — permette di intercambiare FetchHttpClient (nativo) e AxiosHttpClient (opzionale).

interface IHttpClient {
  request<T = unknown>(config: AxiosRequestConfig): Promise<AxiosResponse<T>>
  get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>
  post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>
  put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>
  patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>
  delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>
  setAuthHandler(handler: IAuthHandler): void
  getAxiosInstance(): AxiosInstance
}

I tipi di richiesta/risposta riusano AxiosRequestConfig/AxiosResponse per uniformità anche nel client fetch (che li wrappa internamente).


ILogger

Logger iniettabile (alternativa a console.* diretto). I consumer possono passare un logger custom (es. Pino, Winston, browser logger) per controllare verbosità e destinazione.

interface ILogger {
  debug(message: string, ...args: unknown[]): void
  warn(message: string, ...args: unknown[]): void
  error(message: string, ...args: unknown[]): void
}
@since 0.2.0ILogger è disponibile dalla v0.2.0. Built-in: ConsoleLogger (delega a console.*) e NullLogger (silenzioso, per produzione/test).

Estensione — esempio strategia custom

import { AuthStrategyType, type IAuthStrategy, type AuthResponse } from '@pzeta/postgrest'
import type { AxiosRequestConfig } from 'axios'

class BearerHeaderStrategy implements IAuthStrategy {
  readonly type = AuthStrategyType.JWT
  private token: string | null = null

  applyAuth(config: AxiosRequestConfig): AxiosRequestConfig {
    if (this.token) {
      config.headers = { ...config.headers, Authorization: `Bearer ${this.token}` }
    }
    return config
  }

  isAuthenticated(): boolean { return this.token !== null }
  saveCredentials(auth: AuthResponse): void { this.token = auth.token }
  clearCredentials(): void { this.token = null }
  getToken(): string | null { return this.token }
  getRefreshToken(): string | null { return null }
  supportsRefresh(): boolean { return false }
}

// Usa la strategia custom
const client = new PostgRESTClient({
  apiUrl: 'https://api.example.com',
  customAuthStrategy: new BearerHeaderStrategy(),
})
await client.init()

Tipi correlati