What Is `downstream.ts` in LunaTV? The Core Video Source API Module Explained

The downstream.ts file in LunaTV serves as the central abstraction layer that manages all communication with external video-source APIs, handling unified search, result caching, payload normalization, and detail retrieval while shielding the application from upstream API quirks.

The LunaTV streaming application relies on src/lib/downstream.ts to aggregate content from multiple downstream video providers. This TypeScript module standardizes how the application interacts with disparate external APIs, ensuring consistent data structures and reliable performance through intelligent caching and timeout mechanisms.

Unified Search Interface with searchFromApi

The searchFromApi function in src/lib/downstream.ts constructs request URLs for configured video sources and orchestrates paginated fetching. It respects the SearchDownstreamMaxPage configuration limit, fetching the first page and conditionally pulling additional pages to aggregate comprehensive results. This implementation decouples the search logic from specific source implementations, allowing LunaTV to query multiple providers through a single, consistent interface managed within the downstream module.

Result-Level Caching Architecture

Before initiating network requests, downstream.ts checks for cached responses via getCachedSearchPage. Cache hits return instantly, eliminating redundant API calls, while misses trigger fetches with configurable timeouts. The setCachedSearchPage function stores successful responses in an in-memory or Redis cache, ensuring subsequent identical queries retrieve data locally. Failed requests are also cached as "timeout" or "forbidden" states to prevent repeated hammering of unresponsive upstream services.

Normalizing API Payloads into SearchResult Types

Raw JSON responses from external APIs vary significantly between sources. The module transforms these disparate payloads into the standardized SearchResult interface, normalizing fields such as id, title, poster, and episodes. This normalization ensures that UI components and business logic throughout LunaTV can process video metadata uniformly, regardless of which downstream provider supplied the original data.

Detail Retrieval and Special Source Handling

For fetching comprehensive video metadata, the getDetailFromApi function retrieves full episode lists, descriptions, and related content. When encountering "special" sources that require HTML scraping rather than JSON APIs, the module delegates to handleSpecialSourceDetail. This specialized handler extracts critical streaming data including m3u8 links, video titles, cover images, and metadata directly from source HTML pages, bridging the gap between modern APIs and legacy web scraping requirements.

Error Handling and Request Timeouts

Network resilience is implemented through AbortController wrappers that enforce configurable timeouts on all outbound requests. When upstream APIs fail or timeout, downstream.ts captures these states and caches the failure classification to prevent cascade failures. This defensive programming ensures LunaTV remains responsive even when individual video sources experience downtime or rate limiting.

Practical Implementation Examples

The following examples demonstrate how to interact with the downstream module in LunaTV applications:

// Searching a video source
import { searchFromApi } from '@/lib/downstream';
import { API_SITES } from '@/lib/config';

// Example: search the “zyplayer” source for “星际穿越”
const results = await searchFromApi(API_SITES.zyplayer, '星际穿越');
// `results` is an array of `SearchResult` objects ready for UI consumption
// Fetching detailed video information
import { getDetailFromApi } from '@/lib/downstream';
import { API_SITES } from '@/lib/config';

// Example: fetch detail for video id “12345” on the “ffzy” source
const detail = await getDetailFromApi(API_SITES.ffzy, '12345');
// `detail` contains episodes, titles, poster, description, etc.
// Working with the cache layer directly
import { getCachedSearchPage, setCachedSearchPage } from '@/lib/search-cache';

// Check if a previous search result is cached
const cached = getCachedSearchPage('zyplayer', '星际穿越', 1);
if (cached?.status === 'ok') {
  console.log('Cache hit', cached.data);
}

Key Files Supporting the Downstream Layer

The downstream module operates in concert with several supporting files to provide LunaTV's video aggregation capabilities:

  • src/lib/downstream.ts – Core logic for contacting downstream video APIs, caching, and normalising results.
  • src/lib/config.ts – Defines the API_SITES configurations and SearchDownstreamMaxPage setting used by the downstream module.
  • src/lib/search-cache.ts – Implements the caching layer with getCachedSearchPage and setCachedSearchPage functions.
  • src/lib/types.ts – Contains the SearchResult type definition that standardizes outputs from downstream.ts.
  • src/lib/utils.ts – Provides helper utilities like cleanHtmlTags utilized during HTML scraping operations.

Summary

  • src/lib/downstream.ts centralizes all external video source communication for LunaTV, abstracting API-specific implementations into a unified interface.
  • The searchFromApi function provides paginated, cached search capabilities across multiple configured sources while respecting SearchDownstreamMaxPage limits.
  • getCachedSearchPage and setCachedSearchPage implement intelligent result caching to minimize network requests and improve response times.
  • Raw API responses are normalized into the SearchResult type, ensuring consistent data structures throughout the application regardless of source format.
  • getDetailFromApi and handleSpecialSourceDetail handle both standard JSON APIs and HTML scraping scenarios for comprehensive metadata retrieval.
  • Error handling via AbortController and timeout caching prevents cascade failures and protects upstream services from repeated failed requests.

Frequently Asked Questions

What does the searchFromApi function do in LunaTV?

The searchFromApi function constructs request URLs for configured video sources, fetches paginated results while respecting the SearchDownstreamMaxPage limit, and returns normalized data. It serves as the primary entry point for all video search operations within the application, handling both the initial page fetch and subsequent pagination automatically to aggregate comprehensive results from downstream providers.

How does downstream.ts handle caching for video searches?

The module checks getCachedSearchPage before making network calls to retrieve previously stored results from in-memory or Redis storage. Successful API responses are cached via setCachedSearchPage for future requests, while error states like "timeout" or "forbidden" are also cached temporarily to prevent repeated failed attempts against struggling upstream services, optimizing both performance and upstream resource protection.

What is the purpose of handleSpecialSourceDetail in the downstream module?

handleSpecialSourceDetail is invoked when getDetailFromApi encounters sources that provide data through HTML rather than structured JSON APIs. This function performs web scraping to extract m3u8 streaming links, video titles, cover images, and episode metadata directly from source HTML pages, enabling LunaTV to support legacy or non-API video providers that require parsing markup rather than consuming REST endpoints.

How are API errors and timeouts managed in downstream.ts?

All network requests are wrapped in AbortController instances with configurable timeouts to prevent hanging requests. When timeouts or errors occur, the module caches the specific failure type and returns the error state to the caller. This approach prevents the application from repeatedly hammering unresponsive APIs and provides graceful degradation when individual sources fail, maintaining application stability during upstream outages.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →