Anime tracking app built as a portfolio project. Browse a curated catalog, track what you're watching, organize personal lists, and rate completed entries.
The name is short for Shiroyasha ("white demon", Gintama). 夜叉 (yasha) on its own is a class of nature spirits in Buddhist folklore.
I wanted to build something non-trivial end-to-end with the patterns I'd otherwise only get to use in pieces at work: bounded contexts, CQRS with a retained event log, a real outbox-driven projection pipeline, distributed tracing, and a cohesive TanStack frontend. The anime catalog gave me a domain that's familiar but rich enough to stress those patterns - bidirectional relations, search, per-member tracking state, admin workflows - without the complexity becoming the project.
- CQRS backend with a retained event log - aggregate state is the current-state row; aggregates emit domain events, and a Postgres outbox + Kafka feed a Typesense search index and a Postgres-materialized activity projection. Most reads are live, strongly-consistent SELECTs over the write tables (views); search and activity are the async, eventually-consistent projections. See apps/backend/docs/data-layer.md and ADR-0001.
- Bounded contexts with no cross-module FKs - cleanup happens via integration events, and the import boundary is enforced by the
package.jsonimports map plus oxlint. See apps/backend/docs/modules.md. - Read access split by consistency - a query-side
Reader(eventually consistent, the seam a cache drops into) vs a write-sideReferenceChecker(strongly consistent, transaction-bound, for command-side invariants); projectors read neither and fold events purely. See docs/adr/0030-reader-query-side-checker-write-side.md. - Distributed tracing + metrics via an embedded, opt-in OpenTelemetry SDK (@yasha/observability) - auto-instrumentation plus custom application metrics, exporting OTLP only when a collector endpoint is configured.
- Custom DI container (@yasha/di) with auto-discovery and a cradle pattern that survives erasable-only TypeScript transpilation.
- Branded IDs everywhere - HTTP, domain, repositories all share the same
MediaId/TrackedMediaIdbrands; cross-aggregate confusion is a compile error. - Session-backed refresh tokens with reuse detection and cross-tab refresh dedup via
navigator.locks. See apps/frontend/docs/auth.md. - TanStack everywhere on the frontend - Router (file-based, codegen), Query (factories + invalidation), Form (field-level subscriptions), all with Zod validators.
For the longer-form rationale on each of these - what was chosen, why, and what was given up - read the architecture decision records.
Backend: Fastify, Drizzle, Postgres (write side, event log, materialized projections), Kafka (@platformatic/kafka), Typesense (search), Valkey (cache), OpenTelemetry (opt-in OTLP export), custom DI container.
Frontend: React 19, Vite, TanStack Router/Query/Form, Zod, Tailwind, shadcn-style components.
Tooling: TypeScript, npm workspaces + Turborepo, oxfmt, ls-lint, knip, Vitest, Playwright.
- Node and npm - exact versions are pinned in package.json
engines(enforced byengine-strict=truein .npmrc); the Node version is also in .node-version for version managers and CI. Use whichever version manager you prefer. - Docker - the local backing-services stack (Postgres, Kafka, Typesense, Valkey, Mailpit) runs via Docker Compose. See dev-env/dev-env.md.
npm installnpm run build:dev- both apps import workspace packages through theirdist/, which is gitignored, so neither dev server can start on a fresh clone until the packages are built once.cp apps/backend/.env.example apps/backend/.env- Bring up the local backing services (Docker stack) - see dev-env/dev-env.md.
- In separate terminals:
npm run dev -w @yasha/backend- starts the backend (server, worker, and scheduler in one process).npm run dev -w @yasha/frontend- starts Vite on port 3000.
- Open http://localhost:3000.
After changing a package outside the app you are running, rebuild it (npm run build:dev -w @yasha/<package>) or leave npm run build:dev:watch -w @yasha/<package> running.
- CLAUDE.md - working conventions, doc index.
- docs/adr/ - architectural decisions: what, why, and what was given up.
- docs/monorepo.md - workspace layout, turbo,
-wusage. - dev-env/dev-env.md - the backing-services dev surface (compose stack, testcontainers, scripts).
- docs/code-standards.md - naming and import conventions.
- docs/verification.md - what to run after a change, and what each check covers.
- docs/writing.md - prose, code comments, and docs conventions.
- apps/backend/docs/ - module structure, domain, data layer, HTTP, testing.
- apps/frontend/docs/ - routing, data fetching, forms, auth.
- docs/pr-workflow.md - worktrees, branches, commits, and the review loop.
- docs/issues.md - the issue tracker and its triage labels.
- docs/skills.md - how the agent skills in
.agents/skills/are owned and vendored.
The agent workflow in .agents/skills/ is based on Matt Pocock's skills (MIT). The skills were copied in and are maintained here; skills-lock.json records anything still tracked upstream.
MIT © Maciej Cieślar