# CLAUDE.md

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

## Overview

Music aggregation service that consumes third-party URLs (artist/album/track links) and returns unified data including links across 25+ streaming platforms. Deployed as Vercel serverless functions.

## Commands

```bash
yarn dev              # Local dev server (Express on port 5001, Node Inspector on 9229)
yarn build            # Compile TypeScript → public/
yarn test             # lint + types + jest (runs on pre-push)
yarn test:jest        # Jest unit tests only
yarn test:only "pandora"  # Run tests matching a pattern
yarn test:watch       # Watch mode
yarn test:lint        # ESLint
yarn test:types       # TypeScript type check
yarn format           # Prettier auto-fix
```

To refresh mock API payloads for a service:

```bash
yarn pandora          # or: yarn spotify, yarn applemusic, yarn deezer, etc.
```

## Architecture

```
api/        → Vercel serverless HTTP endpoints (lookup, search, health, upc, isrc)
http/       → Bruno API collections for manual endpoint testing
lib/
  services/ → 25+ third-party service integrations (each follows 4-layer pattern)
  lookup/   → URL/UPC/ISRC lookup orchestration across services
  search/   → Cross-service search coordination
  reducer/  → Merges results from multiple services into a single entity
  entities/ → Domain models: Artist, Album, Track, Video, Links
  utils/    → Shared matching/normalization helpers (getBestMatchingAlbum, normalizeArtistName, etc.)
  cachedFetch/ → HTTP client with LRU cache
  reporter/ → Sentry error tracking
packages/songwhip-lookup/ → Published npm client: @theorchard/songwhip-lookup (exports types/utils to consumers)
test/unit/tests/services/  → Mirrors lib/services/ structure
```

**Request pipeline (URL lookup):** Parse URL → identify service → fetch source entity → try UPC/ISRC match → search all services in parallel → score/match results → reduce into unified entity with all service links → serialize to JSON.

## Service Implementation Pattern

Each service lives at `lib/services/<service>/` with this layered structure:

```
lib/services/<service>/
├── api/           # Raw HTTP requests, auth, payload typing — must not reference code outside this folder
│   ├── lookup/    # lookupArtistApi, lookupAlbumApi, lookupTrackApi
│   ├── search/    # searchArtistApi, searchAlbumApi, searchTrackApi
│   ├── fetch.ts   # Service-specific HTTP helpers
│   └── types.ts   # TypeScript interfaces for API responses
├── mapper/        # Pure data transformation (no side effects, no API calls)
│   ├── toArtist.ts, toAlbum.ts, toTrack.ts, toLinks.ts
├── lookup/        # Business logic: calls api/ + mapper/ → returns entities
├── search/        # Business logic: calls api/ + mapper/ → returns entities
├── parseUrl.ts    # Extract entity type/ID from URL (optional)
├── health.ts      # Availability check (optional)
├── README.md      # Service documentation (required: link formats, API details, known limitations)
└── index.ts       # Default export: Service interface implementation (only file using default export)
```

**Layer naming conventions:**

- API layer: `lookupArtistApi`, `searchAlbumsApi` (Api suffix)
- Mapper layer: `toArtist`, `toAlbum`, `toTrack` (to prefix)
- Lookup/Search layer: `lookupArtist`, `searchArtist` (lookup/search prefix)

**Service interface** (`lib/services/types.ts`):

- `search(params)` — required
- `test(url): boolean` — required (URL pattern matcher)
- `lookupByUrl(params)`, `lookupByUpc(params)`, `lookupByIsrc(params)`, `health()` — optional

`index.ts` is the only file that uses a default export; all other files use named exports only.

## Testing

Tests mirror the service structure under `test/unit/tests/services/<service>/`:

```
test/unit/tests/services/pandora/
├── __payloads__/      # Mock API response fixtures (JSON)
├── lookup/            # lookupArtist.jest.ts, lookupAlbum.jest.ts, lookupTrack.jest.ts
├── search/            # searchArtist.jest.ts, etc.
├── mapper/            # toArtist.test.ts, etc.
└── parseUrl.test.ts
```

- Test files use `.jest.ts` or `.test.ts` suffix
- `cachedFetch` is mocked globally — no real network calls in tests
- Snapshots stored in `__snapshots__/` directories
- Scripts to update `__payloads__/` live in `scripts/<service>/` and are registered in `package.json`
- To run tests matching a file path pattern: `yarn test:jest -- --testPathPattern=pandora`
- `yarn test:only` matches against test _names_ (`-t` flag), not file paths

## Code Style

> For detailed code style guidance with examples, see [`.github/copilot-instructions.md`](.github/copilot-instructions.md).

- **Named exports only** everywhere except service `index.ts` files (which use default export)
- **Explicit return types** on all functions
- **`import type`** for type-only imports
- **camelCase** filenames for functions/utilities; **PascalCase** only for class files (e.g., `Artist.ts`)
- **`index.ts` files** contain only import/export statements — no logic
- **`types.ts` files** contain only type/interface declarations — no constants or functions
- **Type literals over enums**: `type Status = 'active' | 'inactive'` not `enum Status`
- **No `any`** types; use `// @ts-expect-error` instead of `// @ts-ignore`
- Services must never catch errors silently — let them propagate for higher-level handling
- **Pre-push hook** runs `yarn format && yarn test` — code must be formatted (`yarn format`) before pushing or the hook will fail
