Skip to content

feat(helpers): lire plusieurs collections Astro dans un même build (0.18.0) - #81

Merged
izo merged 2 commits into
mainfrom
feat/multi-collection
Aug 20, 2026
Merged

feat(helpers): lire plusieurs collections Astro dans un même build (0.18.0)#81
izo merged 2 commits into
mainfrom
feat/multi-collection

Conversation

@izo

@izo izo commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Débloque l'étape 2 de la convergence de mathieu-drouet.com vers le plugin (izo/mathieu-drouet.com#237). Mouvement C au sens de l'ADR-001 : le bloc qui bloque la migration est porté ici d'abord, puis consommé par le site.

Le défaut

COLLECTION_NAME est une constante de module, lue une fois à l'import depuis import.meta.env.HYPERFOCALE_COLLECTION_NAME. Le cache _seriesCache était scalaire, sans clé de collection.

Conséquence sur un site qui interroge plusieurs collections dans le même build : la première collection lue est rendue à toutes les requêtes suivantes, sans erreur ni avertissement. Sur un site bilingue, cela rend des titres anglais sur les pages françaises — le pire mode de défaillance possible, puisque rien ne le signale.

C'est le cas de mathieu-drouet.com, qui tient series (EN) et series_fr (FR) et les interroge toutes deux au même build.

Le correctif

Nom de collection Astro en dernier argument, optionnel partout :

Fonction Avant Après
getSeriesList () (collectionName?)
getSeriesBySlug (slug) (slug, collectionName?)
getSections () (collectionName?)
getSubSeries (containerId) (containerId, collectionName?)
getAllTags () (collectionName?)
getAllCollections () (collectionName?)
querySeries (options) options.collectionName

Le cache passe de Series[] \| null à Map<string, Series[]> — une lecture par collection et par build.

collectionName ne se confond pas avec collection : cette dernière filtre sur le premier segment du slug (music, fashion) à l'intérieur d'une collection. L'une choisit la collection, l'autre filtre dedans. Les deux se combinent, et un test le vérifie explicitement — la proximité des deux noms est le piège le plus probable pour un lecteur pressé.

Vérification

Contrôle Résultat
Tests existants 203 ✅, sans modification — rétrocompatibilité prouvée
Tests ajoutés 9 ✅ (tests/unit/multi-collection.test.ts)
Failable 7 des 9 échouent sur le code d'avant
npm run typecheck vert
npm run build vert

Le test qui compte le plus est celui qui compte les lectures réelles par collection. Un cache scalaire fait passer tous les tests de résultat tout en servant le mauvais corpus : seul le compte de lectures (getCollectionFetchCount, et la liste des noms demandés) prouve l'indexation. Les tests de résultat seuls auraient validé le bug.

Rétro-compatibilité

Aucun changement cassant. Tous les arguments sont optionnels et retombent sur le comportement antérieur : un site existant ne voit aucune différence. Version mineure — 0.18.0.

Hors périmètre

La sémantique collection des helpers média (matchMedia, findManifest) est inchangée : là, le mot désigne le répertoire sous public/content/, pas la collection Astro. Le plugin confond aujourd'hui les deux notions — elles coïncident sur un site mono-collection, plus sur un site multilingue. À traiter séparément si un consommateur en a besoin ; mathieu-drouet.com ne consomme pas ces helpers (système médias propre, R2 + images.json).

🤖 Generated with Claude Code

https://claude.ai/code/session_01G4BcSyfb5ikENAuHaQL5hb

izo and others added 2 commits August 20, 2026 12:27
….18.0)

Les helpers lisaient COLLECTION_NAME — une constante de module figée à
l'import — derrière un cache scalaire. Sur un site qui interroge plusieurs
collections dans le même build, la première lue était rendue à toutes les
requêtes suivantes, sans erreur ni avertissement.

Le besoin vient de mathieu-drouet.com, qui tient une collection par locale
(`series` pour l'anglais, `series_fr` pour le français) : c'est le blocage
qui arrêtait l'étape 2 de sa convergence vers le plugin
(izo/mathieu-drouet.com#237).

getSeriesList, getSeriesBySlug, getSections, getSubSeries, getAllTags et
getAllCollections acceptent désormais une collection en dernier argument ;
querySeries la reçoit via l'option `collectionName`. Le cache est indexé
par collection.

`collectionName` ne se confond pas avec l'option `collection` de
querySeries, qui filtre sur le premier segment du slug à l'intérieur d'une
collection. L'une choisit la collection, l'autre filtre dedans.

Rétrocompatible : tous les arguments sont optionnels et retombent sur le
comportement antérieur. Les 203 tests existants passent sans modification.

9 tests ajoutés, dont un qui compte les lectures réelles par collection —
un cache scalaire fait passer les tests de résultat tout en servant le
mauvais corpus, donc seul le compte de lectures prouve l'indexation.
Vérifié failable : 7 des 9 échouent sur le code d'avant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G4BcSyfb5ikENAuHaQL5hb
Complète la même version : un consommateur qui a ses propres règles de
visibilité, de tri ou de pagination ne peut hériter d'aucun helper filtrant.
`getSeriesList()` écarte les sections et les brouillons, trie par date ; qui
ne veut pas de ces règles n'avait d'autre choix que `getCollection()` en
direct — et réimplémentait le cache que ce module tient déjà.

C'est le cas de mathieu-drouet.com : il exclut les séries privées de tous ses
listings, une notion qui n'a pas vocation à remonter ici (ADR-001), et qui
l'obligeait à dupliquer toute la couche de lecture pour l'appliquer.

Le tableau rendu est celui du cache — documenté comme tel : le muter
corromprait les lectures suivantes.

3 tests ajoutés (15 au total sur ce fichier), dont un qui vérifie que le
brut contient bien ce que getSeriesList écarte, et un qui vérifie que les
deux partagent le cache.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G4BcSyfb5ikENAuHaQL5hb
@izo
izo merged commit 0b17f49 into main Aug 20, 2026
1 check passed
@izo
izo deleted the feat/multi-collection branch August 20, 2026 10:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant