Domain · Architecture

Meditation Libraries — Architecture

The libs/meditation/ directory contains eight platform-agnostic libraries that implement all core capabilities a meditation app needs.

9sections9 minread

On this page

Platform-agnostic meditation engine libraries. Consumed by the Tara domain but designed for reuse by any meditation application.


The meditation libraries are eight TypeScript packages that live under libs/meditation/ and form the engine layer for any meditation application in the Oshun monorepo. They handle everything a meditation app does — playing audio, running timers, guiding breathing exercises, managing sessions, tracking user progress, working offline, and recording analytics — without being tied to any particular UI framework, database, or application.

The core design decision is a clean separation between logic and application. The libs/meditation/ packages contain logic that is identical regardless of how or where the app runs: timing algorithms, audio playback, streak calculation, content versioning. The application layers — apps/tara/, libs/tara/, and wherever Lilith consumes them — supply the parts that vary by platform: REST endpoints, push notifications, database schemas, UI components, and app store metadata.

A new engineer working on Tara's meditation features will mostly interact with these eight libraries. Understanding their boundaries, shared patterns, and dependency structure makes it much easier to know where to add a new capability or trace a bug.


1. Domain Summary#

The libs/meditation/ directory contains eight platform-agnostic libraries that implement all core capabilities a meditation app needs. They are not an application domain — they have no API, no database, and no UI. They are an engine layer designed to be consumed by Tara and Lilith and any future meditation applications in the Oshun monorepo.

The key design decision is the separation of concerns:

  • libs/meditation/ handles all logic that is the same regardless of platform: timing, audio management, breathing guidance, session tracking, progress calculation, and offline management.
  • apps/tara/ and libs/tara/ handle everything platform-specific: API endpoints, UI components, database persistence, push notifications, app store integration.

2. Library Architecture#

The diagram below shows how data and control flow through the eight packages during a typical meditation session. meditation-core anchors the bottom of the stack as the shared type layer; the engine packages (player, timer, session, breathing, progress, offline) form the middle; and meditation-analytics sits alongside as a standalone tracking concern.

text
            @oshun/meditation-core
        (branded types, content/session models,
         duration/date/format/validation utilities)

                @oshun/meditation-player
              (audio playback, mixing, streaming,
               caching, visualization, background)
                        |
                        |   plays audio for
                        v
            @oshun/meditation-session
          (session lifecycle, persistence,
           analytics tracking, scheduling)
          /           |            \
         /            |             \
@oshun/meditation-   @oshun/meditation-   @oshun/meditation-
    timer                breathing             offline
 (countdown,          (patterns,          (download queue,
  bells, ambient,      guidance,           storage, versioning,
  presets, haptics)    visualization,       suggestions, network)
                       haptics, history)
         \            |             /
          \           |            /
            @oshun/meditation-progress
          (streaks, statistics, achievements,
           milestones, export, sync)

            @oshun/meditation-analytics
        (privacy-conscious event tracking,
         consent gating, session summaries)

The diagram shows conceptual data flow within an application. meditation-core is the shared type/utility layer; meditation-analytics is a small standalone event-tracking layer. At the package level, the libraries do not import each other — they share only the eventemitter3 runtime dependency (six of the eight packages) and an optional react peer dependency. The meditation-player package additionally depends on @oshun/contracts for its shared playback-rate policy. Integration between meditation packages happens at the application layer. meditation-core and meditation-analytics use neither eventemitter3 nor a React peer dependency.


3. Library Descriptions#

Each library has a well-defined responsibility. The descriptions below give a sense of what each package owns and which classes an engineer will encounter first.

@oshun/meditation-core#

Shared primitives consumed conceptually by every meditation package (the dependency is logical, not a package import):

  • Branded identifier and value types (SessionId, TrackId, ContentId, DurationSeconds, Timestamp, Percentage, ByteSize, …)
  • Content and session models (ContentItem, AudioTrack, MeditationSession, SessionResult, InterruptionRecord)
  • Literal-union vocabularies (MeditationCategory, SessionType, SessionState, PlaybackState, ContentKind, Platform)
  • Utility modules: duration, dates, format, validation

Build: @nx/js:tsc | Runtime dep: none

@oshun/meditation-player#

The most feature-rich library. Provides the complete audio playback engine with:

  • MeditationPlayer — event-driven player with load/play/pause/seek/stop
  • PlaybackQueue — ordered queue with shuffle and repeat
  • AudioMixer — multi-layer mixing with ducking and volume automation
  • AudioVisualizer — real-time frequency analysis and beat detection
  • StreamingManager — adaptive bitrate streaming with network monitoring
  • AudioCacheManager + AudioPreloader — local cache with Service Worker
  • Platform-abstracted background audio and audio session management

Build: @nx/js:tsc | Runtime dep: eventemitter3

@oshun/meditation-timer#

Cross-platform countdown timer with three phases (preparation, meditation, wind-down):

  • MeditationTimer — event-driven countdown with phase management and wall-clock drift correction
  • PresetManager — 12 built-in presets across quick/standard/extended/pomodoro/ sleep categories, plus custom presets and favorites
  • BellPlayer — six built-in bell-sound constants (drawn from an 11-value BellType union) with start/end/interval/warning/preparation-end triggers
  • AmbientPlayer — seven built-in ambient-sound constants (drawn from a 27-value sound-type union) and three curated mixes
  • Platform-abstracted background operation (Wake Lock, Notifications, Web Locks) and haptics

Build: @nx/js:tsc | Runtime dep: eventemitter3

@oshun/meditation-session#

Complete session management solution:

  • SessionManager — full lifecycle with interruption and completion tracking
  • SessionPersistenceManager — three storage backends with auto-save
  • SessionAnalyticsManager — event recording and aggregate computation
  • SessionScheduler — recurring sessions with iCal export

Build: @nx/esbuild:esbuild (dual ESM + CJS output) | Runtime dep: eventemitter3

@oshun/meditation-progress#

Progress tracking, streaks, and gamification:

  • ProgressTracker — records sessions, updates streaks, unlocks achievements
  • StreakCalculator — timezone-aware with forgiveness days and freeze tokens
  • StatisticsCalculator — aggregations over configurable date ranges
  • AchievementManager — built-in definitions with unlock evaluation
  • MilestoneManager — significant marker tracking with notifications
  • ProgressExporter — multi-format data export
  • ProgressSyncManager — multi-device sync with conflict resolution

Build: @nx/js:tsc | Runtime dep: eventemitter3

@oshun/meditation-breathing#

Breathing exercise engine with 10 built-in patterns and custom builder:

  • BreathingExercise — event-driven exercise runner (inhale/hold/exhale/rest)
  • BreathingPatternBuilder — fluent API for custom pattern construction
  • VisualizationProvider — real-time easing and color data for UI animation
  • GuidancePlayer — phase-synchronized audio cues
  • BreathingHapticManager — phase-timed haptic feedback
  • SessionHistoryManager — persist breathing session records

Build: @nx/js:tsc | Runtime dep: eventemitter3

@oshun/meditation-offline#

Offline content management with download queue and smart suggestions:

  • OfflineManager — orchestrates downloads, storage, versioning, suggestions
  • DownloadQueue — priority queue with retry, concurrency limits, and cancel
  • StorageManager — three backends with automatic cleanup
  • ContentVersionManager — manifest-based versioning with update policies
  • SuggestionEngine — behavioral scoring for download recommendations
  • Network detection with WiFi-only and metered-connection policies

Build: @nx/js:tsc | Runtime dep: eventemitter3

@oshun/meditation-analytics#

A small, dependency-free privacy-conscious event-tracking package:

  • AnalyticsClient — event tracking gated on enabled && consentGranted, with an 11-value AnalyticsEventName union
  • AnalyticsProvider interface — identify / track / flush, with a pluggable backend (Segment, Mixpanel, custom)
  • MemoryAnalyticsProvider — in-memory provider for testing
  • summarizeSession / trackSessionSummary — derive completion ratios and emit session-summary events; only behavioural fields, keyed by an anonymousId

Build: @nx/js:tsc | Runtime dep: none (no eventemitter3)


4. Common Architectural Patterns#

The six engine libraries (player, timer, breathing, session, progress, offline) share the design patterns described below, making them consistent and predictable to work with. meditation-core is a pure type/utility package and meditation-analytics is a standalone tracking package, so they participate only where noted.

4.1 Event-Driven Core#

Rather than polling state or using callbacks directly, every engine library's primary class extends EventEmitter from eventemitter3 and is parameterized by a typed event map — one named property per event with its payload type. This lets consumers subscribe to exactly the events they care about and receive fully-typed payloads without casting.

typescript
interface MeditationPlayerEvents {
  stateChange: { previousState: PlaybackState; currentState: PlaybackState };
  progress: PlaybackProgress;
  trackChange: { previousTrack: MeditationTrack | null /* ... */ };
  error: { error: PlayerError };
  // ...
}
class MeditationPlayer extends EventEmitter<MeditationPlayerEvents> {
  /* ... */
}

4.2 Platform Abstraction (Three-Tier)#

Some features — background audio, haptics, lock screen controls — require different OS APIs on web, iOS, and Android. Rather than scattering if (platform === 'web') checks through business logic, each capability is defined as an abstract base class. Three implementations are provided, plus a factory that picks the right one at runtime:

text
Abstract base class (interface definition)
  └── Web implementation (uses Web APIs)
  └── Noop implementation (for testing/SSR)
  └── Factory function (selects implementation by environment detection)

Here is a concrete example from the player package:

typescript
abstract class BackgroundAudioHandler {
  /* ... */
}
class WebBackgroundAudioHandler extends BackgroundAudioHandler {
  /* ... */
}
class NoopBackgroundAudioHandler extends BackgroundAudioHandler {
  /* ... */
}

// The factory takes an optional config object and picks Web vs Noop by
// detecting `window` / `navigator`.
function createBackgroundAudioHandler(
  config?: BackgroundAudioConfig
): BackgroundAudioHandler;

Native (React Native) implementations are provided by the consuming app, which extends the abstract base class. This means adding React Native support never requires modifying the core library.

4.3 Storage Backend Interchange#

Libraries needing persistence use an interface-based storage abstraction. The same business logic works against any backend — you swap it at the construction site, not throughout the code:

  • InMemory* — for testing and SSR
  • LocalStorage* — for simple browser use
  • IndexedDB* — for larger datasets
typescript
// Switch backends without changing business logic
const persistence = new SessionPersistenceManager({
  storage:
    process.env.NODE_ENV === 'test'
      ? new InMemorySessionStorage()
      : new IndexedDBSessionStorage('sessions-db'),
});

4.4 React Hook Factories#

React is an optional peer dependency — these libraries must also work in React Native, Vue adapters, and vanilla JavaScript environments. To avoid a hard dependency on React, the player, timer, breathing, session, progress, and offline libraries export createUse* factory functions rather than direct hooks. This pattern enables React dependency injection:

typescript
// Register React once at app startup
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
setReactHooks({ useState, useEffect, useCallback, useMemo, useRef });

// Build bound hooks from a factory (the combined factory takes the hooks object)
const hooks = { useState, useEffect, useCallback, useMemo, useRef };
export const { useBreathingExercise, useBreathingState } =
  createBreathingHooks(hooks);

The breathing, timer, and progress packages additionally export pre-bound convenience hooks (useBreathingExercise, useMeditationTimer, useStreak, …) that resolve hooks registered through their setReactHooks function, for apps that prefer the simpler import style.

4.5 Branded Types#

Raw strings and numbers are easy to mix up accidentally — passing a TrackId where a SessionId is expected, or seconds where milliseconds belong. meditation-core, meditation-session, meditation-progress, and meditation-offline use TypeScript branded types to catch these mistakes at compile time rather than at runtime:

typescript
// meditation-core uses a shared Brand helper
type SessionId = Brand<string, 'SessionId'>;
type DurationSeconds = Brand<number, 'DurationSeconds'>;
type ByteSize = Brand<number, 'ByteSize'>;

// session/progress/offline inline the same pattern
type ContentId = string & { readonly __brand: 'ContentId' };

meditation-player and meditation-timer use plain type aliases instead (type TrackId = string, type DurationSeconds = number).


5. Dependency Graph#

At the package level, the libraries do not import each other. The diagram below shows their external runtime and peer dependencies:

text
eventemitter3  (runtime dependency of six packages)
   |
   +-- @oshun/meditation-player    (peer: react ^18.0.0; also dep @oshun/contracts)
   +-- @oshun/meditation-timer     (peer: react ^18.0.0)
   +-- @oshun/meditation-session   (peer: react >=18.0.0)
   +-- @oshun/meditation-progress  (peer: typescript catalog:)
   +-- @oshun/meditation-breathing (peer: react >=18.0.0)
   +-- @oshun/meditation-offline   (peer: react >=18.0.0)

(no eventemitter3, no react peer)
   @oshun/meditation-core      — pure types and utilities
   @oshun/meditation-analytics — standalone event tracking

meditation-player is the only package with a workspace dependency (@oshun/contracts, for the shared playback-rate policy). meditation-progress declares typescript rather than react as its peer dependency, though its hooks still follow the React factory pattern. Integration between libraries occurs at the application layer (Tara's and Lilith's apps).


6. Technology Stack#

Layer Technology
Language TypeScript (ESM)
Event system eventemitter3 (six of eight packages)
Build (seven packages) @nx/js:tsc
Build (session) @nx/esbuild:esbuild (ESM output with declaration files)
Testing Vitest
React integration Optional peer dependency, hook factory pattern

7. Test Coverage#

Each package ships its own Vitest spec files. The table below maps packages to their covered areas — this is the first place to look when tracing a test failure back to its library.

Library Test files Covered areas
core core.spec.ts Branded constructors, duration/date/format/validation utilities
player player.spec.ts, queue.spec.ts, mixer.spec.ts, playback-rate.spec.ts Playback lifecycle, queue operations, mixing, playback-rate policy
timer timer.spec.ts Timer lifecycle, phases, bell/ambient events
breathing exercise.spec.ts, haptics.spec.ts Exercise lifecycle, pattern execution, vibration pacing
session 8 spec files Lifecycle, storage backends, analytics, scheduling CRUD/iCal/recurrence/triggers
progress tracker.spec.ts Progress recording, streak calculation, achievements
offline 5 spec files Manager, download queue, storage, suggestions, versioning
analytics analytics.spec.ts Event tracking, consent gating, session summaries

8. Relationship to Tara Domain#

The boundary between the meditation engine libraries and the Tara application domain exists because the meditation logic is genuinely reusable — any future meditation app in the monorepo (including Lilith's consciousness-exploration surfaces) can consume libs/meditation/ without taking on any of Tara's application concerns. Tara owns the App Store-safe meditation product; Lilith owns its broader experience surfaces. The engine lives in neither domain.

Data that crosses the boundary: session records and progress data flow upward from the engine libraries into Tara's Prisma database. Tara's API layer wraps the engine's session lifecycle, persisting results to PostgreSQL and dispatching push notifications that the engine libraries themselves cannot send. The engine libraries receive configuration from Tara (content URLs, user preferences) and return results (session results, streak data, achievement unlocks) which Tara then stores and exposes via its REST API.

text
libs/meditation/          libs/tara/             apps/tara/
(engine, platform-agnostic)  (Tara-specific libs)    (Tara apps)
         |                         |                     |
   core ────────────────────────────────────────────→  web (Next.js)
   player ─────────────────────────────────────────→  mobile (Expo)
   timer  ──────────────────── database (Prisma) ───→  api (Hono)
   session ─────────────────── content (hooks) ─────→
   progress ─────────────────────────────────────────→
   breathing ────────────────────────────────────────→
   offline ──────────────────────────────────────────→
   analytics ────────────────────────────────────────→

Tara's applications use the meditation engine libraries for all core logic and add platform-specific concerns on top: API endpoints, UI components using @tara/ui, database access through @tara/database, and analytics wiring.


Domain Relationship
Tara Primary consumer; provides the application layer (API, UI, database) that wraps the meditation engine
Arete Personal development domain; meditation progress could feed into @arete/habits habit tracking
Kuanyin Compassion and ethics domain; wellness frameworks complement meditation content