# LunaTV Search Flow Architecture: How Multi-Source Video Search Works

> Explore the LunaTV search flow architecture. Learn how concurrent multi-source video search, caching, and filtering deliver aggregated results efficiently.

- Repository: [MoonTechLab/LunaTV](https://github.com/MoonTechLab/LunaTV)
- Tags: architecture
- Published: 2026-09-09

---

**LunaTV processes search queries through a pipelined architecture that authenticates users, queries multiple downstream APIs concurrently with per-site timeouts, caches results in-memory, filters yellow-listed content, and returns aggregated responses with HTTP cache headers—all orchestrated from the Next.js API route handler.**

MoonTechLab/LunaTV implements a robust **LunaTV search flow architecture** designed to aggregate video content from multiple third-party sources efficiently. This Next.js application handles `GET /api/search` requests by orchestrating authentication, parallel downstream queries, intelligent caching, and content filtering into a seamless pipeline. Understanding this architecture reveals how the system maintains low latency while managing external API dependencies.

## Entry Point and Request Validation

The search flow begins at [`src/app/api/search/route.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/api/search/route.ts), the Next.js API route handler that serves as the orchestration layer for all search operations. When a client issues `GET /api/search?q={keyword}`, the route first extracts authentication context using `getAuthInfoFromCookie()` to validate the requesting user.

The handler performs strict input validation: if the query parameter `q` is missing or empty, it returns an immediate short-lived empty result to prevent unnecessary processing. For valid requests, the route prepares for parallel execution by fetching the list of enabled downstream sources and initializing a `Promise.race` for each site call, capped with a **20-second timeout** to prevent stalled responses.

```typescript
// src/app/api/search/route.ts (simplified flow)
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const query = searchParams.get('q');
  
  if (!query) {
    return new Response(JSON.stringify({ results: [] }), {
      headers: { 'Cache-Control': 'max-age=60' }
    });
  }
  
  const authInfo = getAuthInfoFromCookie(request);
  const sites = await getAvailableApiSites(authInfo);
  
  // Parallel search with timeout protection
  const searches = sites.map(site => 
    Promise.race([
      searchFromApi(site, query),
      new Promise((_, reject) => 
        setTimeout(() => reject(new Error('Timeout')), 20000)
      )
    ])
  );
  
  const results = await Promise.allSettled(searches);
  // ... aggregation logic
}

```

## Configuration Loading and Site Resolution

Before dispatching queries, the system loads runtime configuration via [`src/lib/config.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/config.ts). The `getAvailableApiSites()` function retrieves the cached admin configuration through `getConfig()`, then filters the `SourceConfig` array based on user-specific permissions such as `enabledApis` or tag mappings.

This module also supplies critical cache policies including `SiteInterfaceCacheTime`, which determines how long downstream API responses remain valid in the local cache. The configuration layer ensures that only authorized and active sources participate in the search, preventing wasted resources on disabled or restricted endpoints.

## Parallel Downstream API Execution

The core aggregation logic resides in [`src/lib/downstream.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/downstream.ts), where `searchFromApi()` constructs the target API URL and delegates network operations to `searchWithCache()`. This architecture supports pagination through `SearchDownstreamMaxPage`, automatically fetching additional result pages when available and flattening them into a single `SearchResult` array.

Each downstream call operates independently, allowing the system to collect results from fast sources while timing out slow ones. The `searchWithCache()` function implements a **default 8-second timeout** for individual HTTP requests, ensuring that a single sluggish provider cannot degrade the overall user experience.

### Result Transformation

Raw JSON responses undergo normalization before entering the aggregation pipeline. The system extracts key metadata including `title`, `poster`, `year`, `description` (processed through `cleanHtmlTags()` to strip HTML), and `type_name`. Episodes without playable URLs are automatically discarded during this phase to ensure result quality.

```typescript
// From src/lib/downstream.ts
function transformToSearchResult(raw: any): SearchResult {
  return {
    title: raw.name,
    poster: raw.pic,
    year: parseInt(raw.year),
    description: cleanHtmlTags(raw.content),
    type_name: raw.type_name,
    episodes: raw.episodes?.filter((ep: any) => ep.url?.length > 0) || []
  };
}

```

## In-Memory Caching Strategy

LunaTV employs an aggressive caching layer implemented in [`src/lib/search-cache.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/search-cache.ts) to minimize redundant network calls. The `searchWithCache()` function first checks `getCachedSearchPage(siteKey, query, page)` for a valid entry marked with `status === 'ok'`. Cache hits return immediately without touching the downstream API.

On cache misses, the system performs the HTTP request, then stores the response using `setCachedSearchPage()`. The cache implementation features automatic expiration based on TTL values from `SiteInterfaceCacheTime` and enforces size limits through automatic cleanup of stale entries. This design ensures that popular queries receive sub-millisecond responses while protecting upstream APIs from request spikes.

```typescript
// src/lib/search-cache.ts usage pattern
const cached = getCachedSearchPage(site.key, keyword, page);
if (cached && cached.status === 'ok') {
  return cached.data;
}

// Fetch from upstream
const fresh = await fetchWithTimeout(apiUrl, 8000);
setCachedSearchPage(site.key, keyword, page, fresh);
return fresh;

```

## Content Filtering and Response Headers

After aggregating results via `Promise.allSettled()`, the route handler in [`src/app/api/search/route.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/api/search/route.ts) optionally filters the combined dataset. If `DisableYellowFilter` is false in the configuration, it removes results whose `type_name` matches entries in the `yellowWords` list managed via [`src/lib/yellow.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/yellow.ts).

The final response includes optimized HTTP cache headers. The system calculates `max-age` values through `getCacheTime()`, applying them to both standard `Cache-Control` and `CDN-Cache-Control` headers. This allows edge networks to cache successful search results, further reducing origin load and improving global response times.

## Summary

- **Authentication-first design**: Every search request validates the user via `getAuthInfoFromCookie()` before processing.
- **Concurrent downstream queries**: The architecture uses `Promise.race()` with a 20-second timeout to query multiple API sources simultaneously without blocking on slow responses.
- **Intelligent caching**: [`search-cache.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/search-cache.ts) provides in-memory storage with TTL enforcement, reducing latency for repeated queries and protecting upstream rate limits.
- **Defensive timeouts**: Per-call network timeouts (8 seconds) and overall race timeouts (20 seconds) ensure predictable performance regardless of external API health.
- **Configurable filtering**: Yellow-word filtering can be toggled via `DisableYellowFilter`, with sensitive content removed before the response reaches the client.

## Frequently Asked Questions

### How does LunaTV handle slow or unresponsive downstream APIs?

LunaTV implements a dual-timeout strategy defined in [`src/app/api/search/route.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/api/search/route.ts) and [`src/lib/search-cache.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/search-cache.ts). Each individual network call uses an 8-second timeout within `searchWithCache()`, while the orchestration layer wraps each site query in a `Promise.race()` with a 20-second ceiling. If a downstream API fails to respond within these windows, the promise rejects and `Promise.allSettled()` collects the error, allowing successful results from other sources to return to the client without delay.

### What caching mechanisms does LunaTV use for search results?

The platform utilizes an in-memory cache system implemented in [`src/lib/search-cache.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/search-cache.ts). The `getCachedSearchPage()` function checks for valid cached entries before issuing network requests, while `setCachedSearchPage()` stores successful responses with configurable TTL values from `SiteInterfaceCacheTime`. The cache automatically expires stale entries and enforces size limits, ensuring that frequently accessed queries return instantly while maintaining memory efficiency.

### Can the content filtering be disabled in the LunaTV search flow?

Yes. The yellow-word filtering mechanism can be disabled via the `DisableYellowFilter` configuration flag in the admin settings. When enabled, the filter iterates through aggregated results in [`src/app/api/search/route.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/api/search/route.ts) and removes items where `type_name` contains words from the `yellowWords` array. Setting this flag to `true` bypasses the filtering logic entirely, returning all aggregated results regardless of content classification.

### How does LunaTV determine which downstream sources to query for a specific user?

Source selection occurs in [`src/lib/config.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/config.ts) through the `getAvailableApiSites()` function. This utility loads the global configuration via `getConfig()` and filters the `SourceConfig` array based on the authenticated user's `enabledApis` preferences or tag mappings. This per-user resolution ensures that administrators can control API access granularly, restricting certain downstream sources to specific user tiers or regions while maintaining a unified search interface.