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
mainbranch creates "stable" releases. It should receive mainly receivefixcommits. - The
devbranch is for feature development. It receivesfeatcommits 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 implementationsschema/- Database schema definitionsinterfaces/- DB interface abstractionsChannelDB.ts,ProgramDB.ts- Main database access classes
services/- Business logic layer (event handling, scheduling, media source integration)scanner/- Media scanning functionalityscheduling/- Channel scheduling logicstartup/- Startup tasks
stream/- Video streaming pipeline (HLS, concat streams, FFmpeg integration)- Organized by media source (plex/, jellyfin/, emby/, local/)
ffmpeg/- FFmpeg wrapper and pipeline builderexternal/- External API clients for Plex, Jellyfin, Embytasks/- Background tasks and fixerscli/- CLI commands (accessible viapnpm tunarr <command>)
Dependency Injection: The server uses Inversify for DI. Service registration happens in module files:
container.ts- Root container and module registrationDBModule.ts- Database servicesServicesModule.ts- Business servicesStreamModule.ts- Streaming servicesFFmpegModule.ts- FFmpeg servicesTasksModule.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
DBAccesswhich provides bothdb(Kysely) anddrizzle(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 (generatesrouteTree.gen.ts)components/- Reusable React componentshooks/- Custom React hooksstore/- Zustand state storesgenerated/- Auto-generated API client codepages/- 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 buildto generate.d.tsand.jsfiles
@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.tsextension - Run tests with
pnpm turbo testorpnpm test:watchfor watch mode - Server tests often use
@faker-js/fakerfor 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:
- Define route in
server/src/api/{domain}Api.ts - Use Fastify + Zod type provider for type-safe requests/responses
- Register in
api/index.ts - Regenerate OpenAPI spec:
cd server && pnpm generate-openapi - 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)