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/andlibs/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.
@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/stopPlaybackQueue— ordered queue with shuffle and repeatAudioMixer— multi-layer mixing with ducking and volume automationAudioVisualizer— real-time frequency analysis and beat detectionStreamingManager— adaptive bitrate streaming with network monitoringAudioCacheManager+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 correctionPresetManager— 12 built-in presets across quick/standard/extended/pomodoro/ sleep categories, plus custom presets and favoritesBellPlayer— six built-in bell-sound constants (drawn from an 11-valueBellTypeunion) with start/end/interval/warning/preparation-end triggersAmbientPlayer— 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 trackingSessionPersistenceManager— three storage backends with auto-saveSessionAnalyticsManager— event recording and aggregate computationSessionScheduler— 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 achievementsStreakCalculator— timezone-aware with forgiveness days and freeze tokensStatisticsCalculator— aggregations over configurable date rangesAchievementManager— built-in definitions with unlock evaluationMilestoneManager— significant marker tracking with notificationsProgressExporter— multi-format data exportProgressSyncManager— 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 constructionVisualizationProvider— real-time easing and color data for UI animationGuidancePlayer— phase-synchronized audio cuesBreathingHapticManager— phase-timed haptic feedbackSessionHistoryManager— 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, suggestionsDownloadQueue— priority queue with retry, concurrency limits, and cancelStorageManager— three backends with automatic cleanupContentVersionManager— manifest-based versioning with update policiesSuggestionEngine— 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 onenabled && consentGranted, with an 11-valueAnalyticsEventNameunionAnalyticsProviderinterface —identify/track/flush, with a pluggable backend (Segment, Mixpanel, custom)MemoryAnalyticsProvider— in-memory provider for testingsummarizeSession/trackSessionSummary— derive completion ratios and emit session-summary events; only behavioural fields, keyed by ananonymousId
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.
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:
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:
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 SSRLocalStorage*— for simple browser useIndexedDB*— for larger datasets
// 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:
// 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:
// 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:
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.
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.
9. Related Domains#
| 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 |