Files
NewTube/server/providers/feature-flags.mjs
T
bruno 665a0f0ebd
CI / build-and-test (push) Successful in 14m43s
feat(providers): phases 7.3/7.4/7.6/8.1 — provenance, health, NDJSON, contrat unique
7.3: capturedAt/source au registre + 6 adaptateurs + module provenance.ts + ?debug=1 (search-transport.mjs). 7.4: ProviderHealthService + badge source degradee. 7.6: squelettes par provider + snapshots progressifs + transport NDJSON /api/search. 8.1: ProviderAdapter unifie (search enveloppe + channelContent/channelMeta/capabilities) via getProviderAdapter + test de contrat offline.
2026-09-30 07:57:11 -04:00

98 lines
3.6 KiB
JavaScript

/**
* Phase 8.3 — Feature flags par provider (`FF_<PROVIDER>`).
*
* Motivation : certain upstreams sont fragiles hors de notre contrôle. Rumble
* est derrière Cloudflare et peut casser du jour au lendemain ; Odysee dépend
* d'un backend NA. Désactiver un provider ne doit pas exiger un redéploiement
* ni un revert de code.
*
* Trois états, pas deux — c'est la distinction qui compte en exploitation :
* - unset : le provider est actif (comportement historique) ;
* - truthy : actif ;
* - falsy : DÉSACTIVÉ, exclu du fan-out et signalé `errors.<id> = disabled_by_ff`.
*
* Un flag falsy **signale** au lieu de disparaître en silence : une colonne vide
* sans explication ressemble à « aucune correspondance », ce qui envoie l'utilisateur
* (et le support) chercher au mauvais endroit.
*
* Convention de nommage : `FF_YT`, `FF_DM`, `FF_TW`, `FF_PT`, `FF_OD`, `FF_RU`
* (ids courts, cf. `provider-ids.ts`).
*/
/** Valeurs explicitement fausses. Tout le reste est vrai (unset inclus). */
const FALSY = new Set(['0', 'false', 'off', 'no', 'disabled']);
/**
* Lit un flag depuis l'environnement, sans cache : le lire à chaque appel permet
* de changer une variable sans redémarrer (utile en local, et ça évite un cache
* qui masquerait un changement en CI).
*
* @param {string} key
* @returns {{ set: boolean, enabled: boolean, raw: string|null }}
*/
function readFlag(key) {
const raw = process.env[key];
if (raw === undefined || raw === null || String(raw).trim() === '') {
// Unset = actif, et on le dit explicitement pour ne pas confondre
// « pas configuré » et « désactivé » dans les logs.
return { set: false, enabled: true, raw: null };
}
return { set: true, enabled: !FALSY.has(String(raw).trim().toLowerCase()), raw: String(raw) };
}
/**
* État d'un provider au regard des feature flags.
* @param {string} providerId id court ('yt', 'ru', …)
*/
export function providerFlag(providerId) {
const id = String(providerId || '').trim().toLowerCase();
const key = `FF_${id.toUpperCase()}`;
const { set, enabled, raw } = readFlag(key);
return { provider: id, flag: key, set, enabled, raw };
}
/** `true` si le provider est désactivé par un feature flag. */
export function isProviderDisabled(providerId) {
return !providerFlag(providerId).enabled;
}
/**
* Filtre une liste de providers, en retirant les désactivés.
*
* @template {string} T
* @param {T[]} providerIds
* @returns {{ enabled: T[], disabled: Array<{provider: string, flag: string}> }}
*/
export function partitionEnabledProviders(providerIds) {
const list = Array.isArray(providerIds) ? providerIds : [];
const enabled = [];
const disabled = [];
for (const id of list) {
const flag = providerFlag(id);
if (flag.enabled) enabled.push(id);
else disabled.push({ provider: flag.provider, flag: flag.flag });
}
return { enabled, disabled };
}
/**
* Applique les flags à une liste déjà validée, et renvoie les `errors` à fusionner
* dans la réponse de fan-out. Utilisé par `/api/search` ET `/api/suggest` pour
* qu'un provider désactivé se comporte pareil partout.
*
* @template {string} T
* @param {T[]} providerIds
* @returns {{ providerIds: T[], errors: Record<string, {message: string, code: string}> }}
*/
export function applyProviderFlags(providerIds) {
const { enabled, disabled } = partitionEnabledProviders(providerIds);
const errors = {};
for (const d of disabled) {
errors[d.provider] = {
message: `Provider désactivé par le feature flag ${d.flag}`,
code: 'disabled_by_ff',
};
}
return { providerIds: enabled, errors };
}