Meditation and mindfulness application domain. Named after the Buddhist bodhisattva of compassion and liberation.
Tara is the Oshun platform's consumer-facing meditation product — a fully-featured mindfulness app available on the web (installable PWA) and as native iOS and Android applications. It solves the problem of making evidence-based meditation practices accessible across devices: a person who meditates on their phone during a commute, on a laptop at home, and offline on a flight should have a consistent, high-quality experience everywhere.
The domain is structured around a deliberate layering decision: all
platform-agnostic meditation logic (audio playback, breathing patterns, timers,
session tracking, offline storage, progress aggregation) lives in the
libs/meditation/ libraries, which know nothing about Tara's database schema,
authentication, or billing. Tara's own libraries (libs/tara/) and applications
(apps/tara/) sit on top of this engine and add the platform-specific concerns
— a Hono API, a Next.js web app, an Expo mobile app, and a suite of UI
components, analytics, and configuration libraries. This boundary means the
meditation engine can in principle be reused by other products without dragging
Tara's backend along.
A new engineer joining Tara needs to understand two things immediately: (1) the
libs/meditation/ libraries are the engine — they own audio, breathing, timing,
sessions, progress, and offline storage; and (2) libs/tara/ and apps/tara/
are the product — they own the API, the UI, billing, analytics, configuration,
and the Drizzle/Prisma database schemas.
1. Domain Summary#
| Type | Count |
|---|---|
| Applications | 3 |
Domain libraries (libs/tara/) |
8 |
Meditation engine libraries (libs/meditation/) |
8 |
2. System Architecture#
The diagram below shows the full request path from a user's device down to storage. Every client (mobile, web, future admin) talks exclusively to the Hono API over HTTPS. The API handles business logic and writes to PostgreSQL; it uses Redis for session caching. Audio files are never streamed through the API — premium audio is delivered via time-limited CloudFront signed URLs that the API generates, keeping audio delivery latency at CDN speed rather than API speed.
┌───────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
├─────────────────┬─────────────────┬───────────────────── │
│ Mobile App │ Web App │ Admin Dashboard │
│ (Expo/RN) │ (Next.js 14) │ (Future) │
│ iOS + Android │ SSR + PWA │ │
└────────┬────────┴────────┬────────┴──────────────┬────────┘
│ │ │
│ HTTPS / WSS │ │
▼ ▼ ▼
┌───────────────────────────────────────────────────────────┐
│ API LAYER │
│ Hono 4 OpenAPIHono Server (Port 3001, OpenAPI 3.1) │
│ 16 /api/v1 route groups: auth, users, meditations, │
│ courses, teachers, collections, search, sessions, │
│ progress, achievements, favorites, history, downloads, │
│ subscription, notifications, analytics(+dashboard); │
│ plus an inline /api/v1/recommendations handler │
└─────────┬──────────────────────────────┬──────────────────┘
│ │
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ DATA LAYER │ │ CACHE LAYER │
│ PostgreSQL │ │ Redis │
│ (Drizzle ORM) │ │ - Session cache │
│ - Users / Auth │ │ (@oshun/cache) │
│ - Content │ └────────────────────┘
│ - Sessions │
│ - Subscriptions │
│ - Analytics │
└────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ STORAGE LAYER │
│ AWS S3 ──────────▶ AWS CloudFront │
│ Avatar uploads Signed-URL audio delivery, │
│ (@aws-sdk/client-s3) signed cookies, static assets │
└───────────────────────────────────────────────────────────┘
3. Project Structure#
The directory tree below shows how code is organized across applications and
libraries. The apps/tara/ tree contains the three runnable applications; the
libs/tara/ tree contains the eight domain libraries consumed by those
applications. Static content assets and documentation live alongside the apps
but are not build targets themselves.
oshun/
├── apps/tara/
│ ├── api/ # Hono backend API server
│ │ ├── src/
│ │ │ ├── app.ts # Hono app setup, middleware registration
│ │ │ ├── index.ts # Server entry point
│ │ │ ├── db/ # Database schema and query builders
│ │ │ ├── middleware/ # Auth, rate limiting, error handling
│ │ │ ├── routes/ # Route handlers by domain area
│ │ │ └── services/ # Business logic services
│ │ └── Dockerfile
│ │
│ ├── web/ # Next.js 14 PWA
│ │ └── src/
│ │ ├── app/ # App Router pages
│ │ │ └── [locale]/ # i18n routing
│ │ ├── components/ # React components
│ │ ├── features/ # Feature-scoped modules
│ │ ├── hooks/ # Custom React hooks
│ │ ├── lib/ # Utilities and API client
│ │ └── i18n/ # Localization strings
│ │
│ ├── mobile/ # Expo React Native
│ │ └── src/
│ │ ├── screens/ # App screens
│ │ ├── components/ # React Native components
│ │ ├── services/ # Native services (audio, notifications)
│ │ ├── store/ # Zustand state stores
│ │ └── hooks/ # Custom hooks
│ │
│ └── content/ # Static content assets
│ ├── meditations/ # Audio files
│ ├── courses/ # Course definitions
│ ├── teachers/ # Teacher profiles
│ └── sounds/ # Ambient sounds and bells
│
└── libs/tara/
├── ui/ # Cross-platform UI component library + tokens
├── content/ # Content types, API client, cache, search, hooks
├── api-client/ # Typed API client + generated OpenAPI types
├── config/ # Runtime config, environment parsing, feature flags
├── features/ # Feature-state selectors, rituals, taxonomies
├── database/ # Prisma schema, generated client, seed scripts
├── analytics/ # Event tracking, providers, experiments, flags
└── monitoring/ # Error tracking, breadcrumbs, performance
Two independent database schemas exist.
@tara/databaseships a separate Prisma schema used by tooling and the library layer. The@tara/apiruntime defines and migrates its own Drizzle schema (apps/tara/api/src/db/, migrations inapps/tara/api/drizzle/). These are two distinct schemas — neither is a subset of the other. Both are documented fully inspecifications.md(§4 for Drizzle, §7 for Prisma).
4. Layer Responsibilities#
API Layer (apps/tara/api)#
The Hono OpenAPIHono server is the single backend for all Tara clients. It
owns:
- Authentication and authorization (JWT issued in-domain,
@oshun/authprimitives, Google/Apple OAuth) - Content catalog endpoints (meditations, courses, teachers, collections, search)
- User session creation, update, and history
- Progress aggregation and streak calculation
- Subscription billing: Stripe checkout, customer portal, and the Stripe webhook; iOS App Store and Android Google Play receipt verification
- Analytics event ingestion, experiment assignment, and feature-flag evaluation
- Avatar uploads to S3 and CloudFront signed-URL generation for premium audio
The API does not contain audio streaming — audio files are served directly from CloudFront via signed URLs generated by the API. This keeps the API stateless with respect to media and eliminates the cost and latency of proxying large audio files through the application server.
Web Application (apps/tara/web)#
Next.js 14 with App Router and server-side rendering. Key architectural choices:
- TanStack Query manages server state with caching, background refetch, and optimistic updates
- Zustand manages client-only UI state (timer running state, sound mixer volumes, player state)
- next-intl handles i18n with locale-based URL routing (
/en/,/es/, etc.) - Meditation engine libraries (
@oshun/meditation-*) are used directly in the browser for audio playback, timer management, and breathing exercises - Service Worker (via
@oshun/meditation-offline) caches audio files for offline PWA use
Mobile Application (apps/tara/mobile)#
Expo 51 React Native app targeting iOS and Android. Key choices:
- Expo AV wraps
@oshun/meditation-playerfor native audio - Expo Notifications delivers streak alerts and session reminders
- RevenueCat manages in-app purchases across platforms
- Expo Router 3.5 provides file-based navigation
- Maestro E2E testing for critical user flows
5. Meditation Engine Integration#
The eight @oshun/meditation-* libraries handle all platform-agnostic logic and
form the foundation on which Tara is built. The boundary between these libraries
and the Tara applications is explicit: engine libraries receive platform
adapters at initialization time and never import anything Tara-specific. This is
why the same breathing exercise engine code runs in both the browser and React
Native without changes.
@tara/web and @tara/mobile each declare direct dependencies on
@oshun/meditation-player and @oshun/meditation-breathing; the remaining
engine libraries are part of the engine surface but accessed through the two
declared dependencies.
| Library | Integration point |
|---|---|
@oshun/meditation-core |
Shared primitives, content models, date/duration/format utilities |
@oshun/meditation-player |
Web: Web Audio API adapter; Mobile: Expo AV adapter |
@oshun/meditation-timer |
Shared logic; Web and mobile background handlers differ |
@oshun/meditation-breathing |
Shared; haptics use Web Vibration API / Expo Haptics |
@oshun/meditation-session |
Session lifecycle, persistence, scheduling |
@oshun/meditation-progress |
Streaks, statistics, achievements, milestones, export, sync |
@oshun/meditation-offline |
Web: IndexedDB + Service Worker; Mobile: Expo FileSystem |
@oshun/meditation-analytics |
Privacy-conscious session analytics primitives |
Each library uses platform-abstracted factory functions so the calling code looks identical regardless of platform — only the factory argument changes:
// Web
const player = createBrowserOfflineManager();
const bgHandler = createBackgroundAudioHandler('web');
// Mobile (React Native)
const player = createMobileOfflineManager();
const bgHandler = createBackgroundAudioHandler('native');
6. State Management Architecture#
Tara uses three distinct state management approaches for three different kinds of state: server data, ephemeral UI state, and offline/durable client state. Mixing these up is a common source of bugs; the architecture keeps them clearly separated.
Server State (TanStack Query — Web)#
All API data (meditation catalog, session history, user progress) is managed by TanStack Query. Cache keys are namespaced by resource type and ID. Optimistic updates are used for favorites toggling and session rating submission, so interactions feel instant even before the API confirms the change.
Client State (Zustand — Both Platforms)#
UI-only state that does not need server persistence is held in Zustand stores:
- Active player state (current track, position, playback status)
- Timer configuration and running state
- Sound mixer volumes and active sounds
- Breathing exercise state
Offline State (Meditation Libraries)#
@oshun/meditation-offline and @oshun/meditation-session maintain local state
with multiple storage backends, chosen per platform:
- Web: IndexedDB for downloaded content metadata; Service Worker cache for audio files
- Mobile: Expo FileSystem for audio files; AsyncStorage for metadata
7. Authentication and Authorization#
Authentication uses JWT tokens issued in-domain by @tara/api
(apps/tara/api/src/auth/), built on @oshun/auth primitives. The flow from
initial login to protected-resource access proceeds as follows:
- Client submits credentials to
POST /api/v1/auth/login. - API validates the submitted password against the stored
passwordHashin the PostgreSQLuserstable. - API returns a
{ accessToken, refreshToken }pair in atokenPairSchemaresponse. - Access token (default 15-minute TTL,
TARA_ACCESS_TOKEN_TTL) is attached to all subsequent API requests in theAuthorization: Bearer <token>header. - When the access token expires, the client exchanges its refresh token at
POST /api/v1/auth/refreshfor a new pair. Refresh token rotation uses token families (refresh_tokens.family) to detect replay attacks: using a token from an already-rotated family invalidates the entire family. - Google and Apple OAuth flows are also supported for social sign-in.
Premium content authorization is layered on top: the access token's claims include the user's subscription tier. Content endpoints check the tier claim before returning premium audio URLs — unauthenticated or free-tier users receive HTTP 403 for premium content rather than a signed URL.
8. Design Patterns#
Hook Factory Pattern (Meditation Libraries)#
All meditation engine libraries export createUse* factory functions rather
than direct React hooks. This design allows React dependency injection and
avoids requiring React as a hard dependency in the engine libraries, which must
also work in non-React environments (e.g., Node.js service workers or test
runners).
// In the app's setup file
import { setReactHooks } from '@oshun/meditation-player';
import { useState, useEffect, useCallback, useRef } from 'react';
setReactHooks({ useState, useEffect, useCallback, useRef });
// Then use the bound hooks
const { usePlayer, usePlaybackProgress } = createPlayerHooks();
Platform Abstraction (Three-tier)#
Libraries needing platform APIs follow a three-tier pattern: abstract base class
→ concrete web implementation → noop/native stub → factory function that selects
the right implementation for the current environment. This enables the same
library code to run in browser, React Native, and test environments without
if (Platform.OS === 'ios') scattered throughout business logic.
Branded Types#
Session IDs, content IDs, and other identifiers use TypeScript branded types
(e.g., type SessionId = string & { _brand: 'SessionId' }) to prevent
accidental ID type confusion at compile time. Passing a MeditationId where a
SessionId is expected is a compile error, not a runtime bug.
9. Technology Stack Summary#
| Layer | Technology |
|---|---|
| API | Hono 4 (@hono/zod-openapi), TypeScript, Drizzle ORM, PostgreSQL, Redis |
| Web | Next.js 14, React 18, Zustand, TanStack Query, Tailwind CSS, Framer Motion |
| Mobile | Expo 51, React Native, Expo AV, Expo Router, RevenueCat |
| Meditation engine | TypeScript (libs/meditation/*), IndexedDB, Service Worker |
| Build | Nx, esbuild, tsc |
| Testing | Vitest, Playwright (web E2E), Maestro (mobile E2E), k6 (load) |
| Auth | JWT issued in-domain, @oshun/auth primitives, Google/Apple OAuth |
| Billing / IAP | Stripe (web), RevenueCat / App Store / Google Play (mobile) |
| Storage | AWS S3 (avatar uploads) + AWS CloudFront (signed-URL audio delivery) |
10. Related Domains#
The table below explains not just what related domains exist but why the boundaries are drawn where they are.
| Domain | Relationship and boundary rationale |
|---|---|
libs/meditation/ |
Platform-agnostic engine libraries consumed entirely by Tara. The boundary exists so the engine can be reused by other products without coupling to Tara's backend. |
| Arete | Habit tracking and goal setting complement the meditation practice loop. Arete owns habit streaks and goals; Tara owns session streaks. Cross-domain data flows via shared user IDs. |
| Kuanyin | Compassion and ethics domain provides wellness frameworks that align with Tara's mindfulness mission. Kuanyin owns the framework definitions; Tara references them as content metadata. |