Aller au contenu principal

packages/lib — la frontière des SDK tiers

Règle cardinale : chaque SDK tiers (S3, Mistral, Redis, Auth0, …) vit derrière un port dans packages/lib. Le code applicatif (apps/web, apps/api) importe le port, jamais le SDK. Ça sort le lock-in et le churn des vendeurs hors de la logique métier : changer de fournisseur devient une édition de config, pas une réécriture.

Anatomie d'une capability

Sous packages/lib/src/<capability>/ :

FichierRôleVisibilité
port.tsInterface(s) + types domaine — la seule surface vue par les clientsexporté
adapter.<vendor>.tsImplémentation concrète avec le SDK. Un fichier par vendeur.jamais ré-exporté
index.tsFactory create<Capability>(config) ; switch sur config.driverexporté
errors.ts (option)Erreurs normalisées traversant la frontièreexporté

Les capabilities actuelles

Capability auth — opérations Management API

En plus de la vérification JWT et du lookup de profil, le port auth expose désormais listUsers({ page, perPage, query }) (annuaire paginé Auth0) et setUserBlocked({ sub, blocked }) (suspension / réactivation). Les deux passent par la Management API et réutilisent le token M2M mis en cache. Voir Authentification pour la signature complète.

Les 7 règles (résumé)

  1. package.json#exports n'expose que des sous-chemins par capability (@pambe/lib/cache, @pambe/lib/storage, …). Jamais de ré-export d'un module SDK.
  2. Les factories prennent un objet de config par injection — elles ne lisent pas process.env elles-mêmes. L'app construit la config depuis getEnv().
  3. Les types SDK ne fuient pas : entrées/sorties via des types locaux de port.ts.
  4. Multi-vendeur ⇒ config en union discriminée sur driver. Ajouter un vendeur = nouvel adapter.<vendor>.ts + variante ; zéro changement côté client.
  5. Les erreurs traversant la frontière sont normalisées (StorageNotFoundError, …), pas les erreurs natives du SDK.
  6. Nouveau SDK ⇒ il arrive avec port.ts + adapter.*.ts + index.ts dans le même changeset. Pas de ré-export « temporaire ».
  7. Les SDK vendeurs n'apparaissent pas dans apps/*/package.json. S'ils y sont, la frontière a été contournée.
Exemple — le swap de vendeur de cache
import { createCache, type CacheConfig } from "@pambe/lib/cache"

const config: CacheConfig =
getEnv().NODE_ENV === "production"
? { driver: "upstash", url, token }
: { driver: "redis", url: "redis://localhost:6379" }

const cache = await createCache(config) // l'app ne voit que le type `Cache`

Changer de vendeur par environnement est une édition de config, jamais de code.