feat(providers): phases 7.3/7.4/7.6/8.1 — provenance, health, NDJSON, contrat unique
CI / build-and-test (push) Successful in 14m43s

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.
This commit is contained in:
2026-09-30 07:57:11 -04:00
parent 37681c4f53
commit 665a0f0ebd
87 changed files with 8850 additions and 550 deletions
+58 -1
View File
@@ -1,17 +1,74 @@
export type ProviderId = 'yt' | 'dm' | 'tw' | 'pt' | 'od' | 'ru';
import type { ProviderId } from '../shared/providers/provider-ids';
import type { ChannelRef } from '../shared/providers/channel-ref';
export type { ProviderId, ChannelRef };
/**
* Contrat `Suggestion` v2 — doit rester aligné avec le JSDoc de
* `server/providers/registry.mjs` (vérifié par `npm run test:contract`).
*
* v1 (inchangé, rétrocompatible) : title, id, duration, isShort, thumbnail,
* uploaderName, url, type.
* v2 (additif) : compteurs, dates ISO, identité de chaîne, avatars, et flags.
*
* Règle : un champ absent est `undefined`, jamais `0` ni `''`. Une durée nulle
* n'existe pas — elle casse les règles de classification (voir §2.2 du plan).
*/
export interface SuggestionItemV1 {
title: string;
id: string;
/** Secondes, strictement > 0. `undefined` = durée inconnue. */
duration?: number;
isShort?: boolean;
thumbnail?: string;
uploaderName?: string;
url?: string;
type?: string;
// --- v2 ---
/** Vues (> 0). */
views?: number;
/**
* Phase 3.8 / 7.3 — spectateurs EN DIRECT (Twitch `viewer_count`).
* Distinct de `views` : ne jamais afficher comme un compteur de vues.
*/
viewers?: number;
likes?: number;
dislikes?: number;
/** ISO 8601. Jamais « il y a 2 mois ». */
publishedAt?: string;
channelId?: string;
channelExternalId?: string;
channelHandle?: string;
channelUrl?: string;
uploaderAvatar?: string;
channelAvatarUrl?: string;
slug?: string;
channel?: string;
/** 'vod' | 'live' | 'clip' (Twitch). */
kind?: string;
game?: string;
/** BCP-47. */
language?: string;
hasSubtitles?: boolean;
isLive?: boolean;
embeddable?: boolean;
description?: string;
tags?: string[];
/** Libellés bruts, pour afficher « valeur approchée » plutôt que « — ». */
durationRaw?: string;
viewCountRaw?: string;
/**
* Phase 6 — identité de chaîne normalisée. `channelExternalId` reste lu en
* repli (transition non cassante) mais `channelRef` est la forme canonique :
* `scheme` dit comment lire `value`.
*/
channelRef?: ChannelRef;
}
export interface SearchResponseV1 {
/** Version du contrat `Suggestion` (2 depuis la phase 0). */
v?: number;
q: string;
providers: ProviderId[];
groups: Record<ProviderId, SuggestionItemV1[]>;