diff --git a/apps/readest-app/docs/architecture.md b/apps/readest-app/docs/architecture.md new file mode 100644 index 00000000..79e27b72 --- /dev/null +++ b/apps/readest-app/docs/architecture.md @@ -0,0 +1,581 @@ +# Readest Architecture + +This document gives a system-level view of Readest: how the pieces fit together, +which side of the wire each piece runs on, and what each module is responsible +for. It complements [`code-layout.md`](./code-layout.md), which focuses on the +directory layout. Read this one first if you want to understand the system; read +that one when you need to find a specific file. + +The diagrams use [Mermaid](https://mermaid.js.org/) and render natively on +GitHub. + +## 1. High-level picture + +Readest is a single TypeScript/React codebase (`apps/readest-app`) compiled into +multiple targets: + +- a **desktop app** (Windows / macOS / Linux) via Tauri v2 +- a **mobile app** (Android / iOS) via Tauri v2 mobile +- a **web app** running on Next.js / Cloudflare Workers (OpenNext) at + [web.readest.com](https://web.readest.com) +- two **side surfaces**: a "Send to Readest" browser extension + (`apps/readest-app/extension/send-to-readest`) and a Windows thumbnail + shell extension (`apps/readest-app/extensions/windows-thumbnail`) + +The same React UI runs in all targets. What differs is the **host shell** under +the UI and the **set of services** that the UI binds to at runtime — see +section 4. + +```mermaid +flowchart LR + subgraph Clients + Desktop["Desktop app
(Tauri shell + React UI)"] + Mobile["Mobile app
(Tauri Android/iOS + React UI)"] + Web["Web app
(Next.js + React UI)"] + Ext["Browser extension
(Send to Readest)"] + WinExt["Windows shell ext
(thumbnail provider)"] + end + + subgraph Backend["Readest backend (Next.js routes + Cloudflare Worker)"] + AppApi["src/app/api/*
(App Router)"] + PagesApi["src/pages/api/*
(Pages Router)"] + RuntimeCfg["/runtime-config.js
(server-injected config)"] + Worker["workers/send-email
(Cloudflare Worker)"] + end + + subgraph Cloud["External services"] + Supabase["Supabase
(auth + Postgres)"] + S3["Object storage
(S3 / R2)"] + Stripe["Stripe
(billing)"] + AI["AI providers
(OpenAI / Ollama / ...)"] + Trans["Translators
(DeepL / Google / Azure / Yandex)"] + Meta["Metadata providers
(Google Books / Open Library)"] + Dict["Dictionary sources
(Wikipedia / Wiktionary / StarDict)"] + OPDS["OPDS catalogs / Calibre"] + Hardcover["Hardcover GraphQL"] + Readwise["Readwise"] + TTS["Edge TTS"] + IAP["Apple / Google IAP"] + end + + Desktop --> Backend + Mobile --> Backend + Web --> Backend + Ext --> PagesApi + WinExt -.reads files.-> Desktop + + PagesApi --> Supabase + PagesApi --> S3 + PagesApi --> Trans + AppApi --> Supabase + AppApi --> Stripe + AppApi --> AI + AppApi --> Meta + AppApi --> OPDS + AppApi --> Hardcover + AppApi --> TTS + AppApi --> IAP + + Web -.direct.-> Dict + Desktop -.direct.-> Dict + Mobile -.direct.-> Dict + Web -.direct.-> Readwise + Desktop -.direct.-> Readwise +``` + +The `Backend` box is **the same code on all clients**. In the web target it is +deployed as a Cloudflare Worker (via `@opennextjs/cloudflare` and +`wrangler.toml`). In the Tauri targets the same routes are served by a Next.js +runtime, but most clients hit the production deployment over HTTPS. + +## 2. Process boundaries + +There are three runtimes in play: + +```mermaid +flowchart TB + subgraph Browser["Web runtime (browser / Tauri webview)"] + UI["React UI
(src/app, src/components, src/hooks, src/store)"] + Domain["Shared domain layer
(src/services, src/utils, src/libs)"] + Foliate["foliate-js
(packages/foliate-js)"] + SW["Service worker (sw.ts)"] + TursoWasm["Turso WASM
(replica DB in browser)"] + end + + subgraph Native["Tauri native host (Rust)"] + TauriCore["src-tauri/src/lib.rs
(commands, dir_scanner, transfer_file, clip_url, discord_rpc)"] + Plugins["Tauri plugins
(fs, dialog, http, oauth, deep-link, opener, updater,
native-bridge, native-tts, turso, webview-upgrade)"] + end + + subgraph Server["Next.js server (Worker / Node)"] + Routes["App Router + Pages Router routes"] + Mw["middleware.ts
(CORS + COOP/COEP)"] + RuntimeRoute["app/runtime-config.js
(server-rendered config script)"] + end + + UI --> Domain + Domain --> Foliate + UI --> SW + Domain --> TursoWasm + + Domain -- "@tauri-apps/api invoke()" --> TauriCore + TauriCore --> Plugins + + Domain -- "fetch(/api/...)" --> Routes + Browser -- "