Skip to content

Repository files navigation

Yasha

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.

Why this exists

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.

Highlights

  • 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.json imports 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-side ReferenceChecker (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 / TrackedMediaId brands; 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.

Tech stack

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.

Prerequisites

  • Node and npm - exact versions are pinned in package.json engines (enforced by engine-strict=true in .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.

Getting started

  1. npm install
  2. npm run build:dev - both apps import workspace packages through their dist/, which is gitignored, so neither dev server can start on a fresh clone until the packages are built once.
  3. cp apps/backend/.env.example apps/backend/.env
  4. Bring up the local backing services (Docker stack) - see dev-env/dev-env.md.
  5. 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.
  6. 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.

Further reading

Credits

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.

License

MIT © Maciej Cieślar

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages