Architecture
Deux chaînes séparées
Code
Audience et observabilité ne se mélangent pas. Un événement métier ne part pas dans les traces, et
un span ne part pas chez un fournisseur d'audience. Seuls traceId et spanId circulent dans les
journaux, comme identifiants de corrélation.
Pourquoi packages/ sans monorepo
Le cahier des charges interdit de transformer un dépôt applicatif en monorepo, et ce dépôt n'en est
pas un : pnpm-workspace.yaml existe mais ne déclare aucun paquet.
Les paquets vivent donc dans packages/, autonomes, résolus par un alias @workspace/* dans
tsconfig.json. Aucune clé packages: n'est ajoutée à pnpm-workspace.yaml, aucune sémantique
d'installation ne change. Copier packages/ dans un autre projet et ajouter deux lignes d'alias
suffit à le réutiliser.
Frontière navigateur / serveur
| Entrée | Contenu | Interdits |
|---|---|---|
@workspace/analytics | Client, consentement, caviardage, adaptateurs | Aucun secret, aucune dépendance Node |
@workspace/analytics/server | Idem plus lecture des secrets | Ne jamais importer côté client |
@workspace/observability | Façade log / counter / withSpan | — |
@workspace/observability/server | SDK Node | Ne jamais importer côté client |
browserConfigFromEnv ne lit que des variables NEXT_PUBLIC_*. Les jetons d'écriture
(MATOMO_TOKEN_AUTH, RYBBIT_API_KEY) ne sont lus que par serverConfigFromEnv.
Chemin d'un événement
analytics.track('feature_used', { feature: 'recherche' })— nom et charge vérifiés à la compilation.- Consentement : refus ou mode non satisfait → rejet compté, rien ne part.
- Validation Zod : charge non conforme → rejet compté et diagnostic, rien ne part.
- Caviardage : clés interdites et motifs sensibles remplacés.
- Enveloppe :
eventId, version de schéma, horodatage, environnement, identité anonyme. - Mise en file, une file par fournisseur.
- Livraison par lots, avec réessais à délai croissant et gigue.
Aucune de ces étapes ne lève d'exception chez l'appelant. Une panne d'analytics ne doit jamais casser une requête métier.
Isolation des pannes
Une file et un compteur par fournisseur. Un fournisseur injoignable accumule, réessaie, puis abandonne au-delà du plafond — sans affecter les autres. Vérifié par le test de fumée.
Limite connue : les files sont indexées par nom de fournisseur, donc une seule instance par type.
Ce que la livraison garantit
Au mieux, avec déduplication quand le fournisseur la gère via eventId. Pas d'exactement-une-
fois. Les abandons sont comptés (droppedOverflow, droppedExpired, droppedExhausted) et
lisibles par getStats().
Réutilisation ailleurs
Dépendant du projet : core/events.ts (le catalogue) et les variables d'environnement.
Indépendant : tout le reste. Les adaptateurs ne connaissent aucun nom d'événement.