Repository overview
How the PharmaFlow monorepo is split across server features, client apps, and shared packages, and why.
PharmaFlow is a bun workspaces monorepo with turbo task running. Every app lives under apps/* and every shared library under packages/*.
Layout
| Path | Package | What lives there |
|---|---|---|
apps/server |
@pharmaflow/server |
Hono API, one folder per feature in src/features |
apps/register |
@pharmaflow/register |
Next.js point of sale register, English only |
apps/console |
Rust and Tauri | Offline desktop shell that hosts the register UI and owns the local mirror, outbox, and sync engine |
apps/marketing |
@pharmaflow/marketing |
Public marketing site with changelog, terms, privacy, and security pages |
apps/knowledgebase |
@pharmaflow/knowledgebase |
This Blume documentation site plus the generated openapi.json |
packages/database |
@pharmaflow/database |
Drizzle schema under schema/ combined into schema.sql, drizzle migrations, translator, multitenancy, and seed scripts |
packages/shared |
@pharmaflow/shared |
Money, pagination, and response helpers, plus the minor unit currency helpers in src/currency.ts |
apps/console has no package.json. It is a Rust and Tauri crate (apps/console/Cargo.toml, apps/console/tauri.conf.json) that wraps the register build and adds the offline engine described next.
What the console adds
apps/console is not a second UI. Its Rust layer owns offline trading:
- Local mirror -
apps/console/src/db.rscreates thepharmaflow.dbfile in the app data dir fromapps/console/sql/local-schema.sql, keeps_device,_sync_state,_outbox, and_offline_sessionsas internal tables, and reconciles missing mirror columns on launch. - Mirror schema pipeline -
apps/console/scripts/build-mirror-schema.tsregeneratesapps/console/sql/local-schema.sqlfrompackages/database/schema.sqlthrough the overlays inapps/console/scripts/mirror-overlays.tsand the manifestapps/console/sql/mirror-overlays.json.packages/databaseexposes the same run as itsschema:buildandschema:checkscripts. - Outbox -
apps/console/src/outbox/queue.rsfreezesclient_mutation_id,tax_policy, and the currency fields at queue time, deducts mirror stock earliest expiry first, and records the draw instock_json. - Sync engine -
apps/console/src/sync/push.rsbatches 50 outbox rows toPOST /api/v1/sync/push,apps/console/src/sync/pull.rsfollows thesince_revcursor,apps/console/src/sync/bootstrap.rsloads the snapshot, andapps/console/src/sync/apply.rs(reconcile_accepted_sale) rekeys an accepted sale to the server id. - Offline identity -
apps/console/src/identity/offline_auth.rsverifies a stored password hash and honours theoffline_expires_atwindow, so a till keeps selling while disconnected.
The server feature pattern
Each domain is a folder under apps/server/src/features with a route.ts that mounts one router. Shared concerns live under apps/server/src/core: core/auth/ for JWT (token.ts), passwords (password.ts), the middleware (middleware.ts), and login throttling (login-attempts.ts), core/config/env.ts for environment reads, core/database/ for backend selection, transactions, and tenant scoping (db.ts, tx.ts, tenancy.ts), core/common/ for HTTP helpers, validation, time, and sync visibility (http.ts, validate.ts, time.ts, visibility.ts), core/audit/ for the audit log and outbox (audit.ts, outbox.ts), core/logging/logger.ts, core/openapi/ (document.ts, components.ts), and core/storage/storage.ts for S3.
The mount table lives in apps/server/src/app.ts: it composes the Hono instance and mounts every feature under apiV1, for example apiV1.route("/sales", saleRoutes), then serves the whole router at /api/v1. The Bun entry apps/server/src/index.ts only builds the default exported server object with port and fetch and runs ensureLoginAttemptsTable() from apps/server/src/core/auth/login-attempts.ts on startup.
Where to look next
- Start with Architecture for the mental model.
- Go to Sales and Inventory for the two core domains.
- Read Offline sync before you touch the console.
- Keep the API reference open beside the code; every route file maps to one tag there.