Files
tunarr/CLAUDE.md
2026-02-16 16:32:11 -05:00

7.8 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Tunarr is a live TV channel creation platform that allows users to create custom TV channels using media from local files or remote media servers such as Plex, Jellyfin, or Emby servers. It's a TypeScript monorepo using pnpm workspaces and Turbo for task running.

Repository Structure

This is a monorepo with four main packages:

  • server (@tunarr/server): Node.js backend (Fastify server, SQLite database)
  • web (@tunarr/web): React frontend (Vite + Material-UI)
  • types (@tunarr/types): Shared TypeScript types and Zod schemas
  • shared (@tunarr/shared): Utility functions shared between server and web

Code Style

  • Never cast types using as any

Development Setup

Package Manager & Task Runner

  • Use pnpm (v10.28.0) for all dependency management - never use npm or yarn
  • Use Turbo for running tasks across the monorepo
  • Node.js version: 22 (specified in package.json engines)

Version Control

  • The main branch creates "stable" releases. It should receive mainly receive fix commits.
  • The dev branch is for feature development. It receives feat commits and other large changes

Common Commands

# Install dependencies
pnpm i

# Start all dev servers (backend on :8000, frontend on :5173/web)
pnpm turbo dev

# Build all packages
pnpm turbo build

# Run tests across all packages
pnpm turbo test

# Run single test file
cd server && pnpm test path/to/test.ts

# Format code
pnpm fmt

# Lint changed files only
pnpm lint-changed

# Run the typechecker
pnpm turbo typecheck

# Server-specific commands
cd server
pnpm dev              # Start server with tsx watch
pnpm debug            # Start server with debugger
pnpm kysely           # Run Kysely CLI for database operations
pnpm tunarr           # Run CLI commands (see src/cli/commands.ts)
pnpm generate-openapi # Generate OpenAPI spec

# Web-specific commands
cd web
pnpm dev              # Start Vite dev server
pnpm bundle           # Build production bundle
pnpm generate-client  # Generate API client from OpenAPI spec
pnpm regen-routes     # Regenerate TanStack Router routes

Architecture

Server Architecture

Framework & Dependencies:

  • Fastify for HTTP server with Zod-based type provider
  • Inversify for dependency injection (IoC container in container.ts)
  • Kysely + Drizzle ORM for database access (dual ORM approach, migrating to Drizzle)
  • better-sqlite3 for SQLite database
  • Meilisearch for search functionality
  • Zod for schema validation

Key Directories:

  • api/ - Fastify route handlers organized by domain (channels, plex, jellyfin, etc.)
  • db/ - Database layer with both Kysely and Drizzle implementations
    • schema/ - Database schema definitions
    • interfaces/ - DB interface abstractions
    • ChannelDB.ts, ProgramDB.ts - Main database access classes
  • services/ - Business logic layer (event handling, scheduling, media source integration)
    • scanner/ - Media scanning functionality
    • scheduling/ - Channel scheduling logic
    • startup/ - Startup tasks
  • stream/ - Video streaming pipeline (HLS, concat streams, FFmpeg integration)
    • Organized by media source (plex/, jellyfin/, emby/, local/)
  • ffmpeg/ - FFmpeg wrapper and pipeline builder
  • external/ - External API clients for Plex, Jellyfin, Emby
  • tasks/ - Background tasks and fixers
  • cli/ - CLI commands (accessible via pnpm tunarr <command>)

Dependency Injection: The server uses Inversify for DI. Service registration happens in module files:

  • container.ts - Root container and module registration
  • DBModule.ts - Database services
  • ServicesModule.ts - Business services
  • StreamModule.ts - Streaming services
  • FFmpegModule.ts - FFmpeg services
  • TasksModule.ts - Bindings for tasks

Other modules are files ending with Module.ts

To inject dependencies, use the @injectable() decorator and constructor injection with @inject(KEYS.ServiceName). Keys are defined in types/inject.ts.

Database: The codebase uses both Kysely (legacy) and Drizzle ORM (current/future):

  • Schema is defined in db/schema/ using Drizzle
  • Most queries still use Kysely but new code should use Drizzle
  • Access via DBAccess which provides both db (Kysely) and drizzle (Drizzle) instances
  • Database migrations are in migration/

Web Architecture

Framework & Libraries:

  • React 18 with TypeScript
  • Vite for bundling and dev server
  • Material-UI (MUI) v7 for components
  • TanStack Router for routing (file-based routes in routes/)
  • TanStack Query for data fetching and caching
  • Zustand for global state management
  • React Hook Form for form handling
  • Zod for form/data validation

Key Directories:

  • routes/ - File-based routing (generates routeTree.gen.ts)
  • components/ - Reusable React components
  • hooks/ - Custom React hooks
  • store/ - Zustand state stores
  • generated/ - Auto-generated API client code
  • pages/ - Legacy page components (being migrated to routes/)

API Integration: The web app uses a generated API client (generated/) created from the server's OpenAPI spec. Regenerate with pnpm generate-client when server API changes.

Shared Packages

@tunarr/types:

  • Type definitions and Zod schemas shared between server and web
  • Organized by domain: plex/, jellyfin/, emby/, api/, schemas/
  • Build with pnpm build to generate .d.ts and .js files

@tunarr/shared:

  • Utility functions for both server and web
  • Notable: Custom DSL parser for scheduling/filtering (uses Chevrotain)

Testing

  • Vitest is used for testing across all packages
  • Test files use .test.ts extension
  • Run tests with pnpm turbo test or pnpm test:watch for watch mode
  • Server tests often use @faker-js/faker for test data generation

Code Style

  • Prettier for formatting (config in root package.json)
  • ESLint for linting (eslint 9.x with flat config)
  • Husky + lint-staged for pre-commit hooks
  • Import aliases: Use @/ for server, web has configured path aliases

Important Implementation Notes

When Working with Database Code:

  • Prefer Drizzle ORM for new queries over Kysely
  • Both ORMs access the same SQLite database via DBAccess
  • Database interfaces are in db/interfaces/ - implement these for new DB access classes
  • Use dependency injection to get database instances
  • All database schema changes should have migrations generated via pnpm drizzle-kit generate

When Working with Streaming:

  • Streaming logic is complex - see stream/ directory
  • Sessions are managed by SessionManager
  • Different stream types: VideoStream, ConcatStream, DirectStreamSession
  • FFmpeg pipeline is built using builder pattern in ffmpeg/builder/

When Working with External Media Sources:

  • External API clients are in external/ (Plex, Jellyfin, Emby)
  • Canonicalization logic converts external media to Tunarr's internal format
  • Media scanning is handled by scanner services in services/scanner/
  • Media source libraries are refreshed via MediaSourceLibraryRefresher

When Adding API Endpoints:

  1. Define route in server/src/api/{domain}Api.ts
  2. Use Fastify + Zod type provider for type-safe requests/responses
  3. Register in api/index.ts
  4. Regenerate OpenAPI spec: cd server && pnpm generate-openapi
  5. Regenerate web client: cd web && pnpm generate-client

When Working with NFO Files:

  • NFO parsing is in server/src/nfo/
  • Different parsers for different media types (TV shows, movies, etc.)
  • Uses Zod schemas defined in NfoSchemas.ts

Build & Deployment

  • Server builds to single executable using @yao-pkg/pkg
  • Web builds static assets with Vite
  • macOS app bundle support in macos/ directory
  • Docker images available (chrisbenincasa/tunarr)