Comprehensive technical reference for the meditation engine libraries (
libs/meditation/*). Every type, enum, union value, constant, hook factory, storage backend, and configuration default below is taken directly from the package source.
This document is the authoritative technical reference for the eight meditation
engine libraries. It is written for engineers who need to know exact type
shapes, enum values, default configuration numbers, and primary API signatures —
the detail level required to implement a feature, write a test, or debug a type
error. For a feature-oriented overview of what each library can do, see
features.md. For the architectural rationale and design patterns shared across
packages, see architecture.md.
Each section covers one package in the order they are typically layered: core types first, then the primary engine libraries (player, timer, breathing, session, progress, offline), then the analytics package. Cross-cutting patterns (event system, platform abstraction, storage backends, React hooks, branded types) are collected in §11. Configuration examples are in §12, build and test commands in §13, and acceptance criteria in §14.
1. Domain Overview#
The table below captures the key metadata for the meditation domain as a whole.
All eight packages share the scope:meditation Nx project tag, which means
pnpm nx run-many --target=test --projects=tag:scope:meditation runs all
meditation tests in one command.
| Property | Value |
|---|---|
| Scope identifier | meditation — every package is tagged scope:meditation |
| Package count | 8 |
| Application count | 0 (engine layer; no app, API, database, or UI) |
| Language | TypeScript (ESM) |
| Build executor | @nx/js:tsc for 7 packages; @nx/esbuild:esbuild for meditation-session |
| Project tags | All 8 packages: ["scope:meditation", "layer:core", "type:lib"] |
| Runtime dep | eventemitter3 (player, timer, breathing, session, progress, offline) |
| Peer dep | react >=18.0.0 optional (player, timer, breathing, session, offline); progress declares typescript catalog: |
meditation-core and meditation-analytics have no eventemitter3 dependency
and no React peer dependency — they are pure type/utility and analytics
packages.
2. Package Inventory#
The table below maps each package to its filesystem path, Nx build executor, and
the subpath exports it exposes for consumers. Fine-grained subpath exports
(e.g., @oshun/meditation-player/hooks) allow consumers to import only what
they need without pulling in unrelated code.
| Package | Path | Build executor | Subpath exports |
|---|---|---|---|
@oshun/meditation-core |
libs/meditation/core |
@nx/js:tsc |
., ./types, ./duration, ./dates, ./format, ./validation |
@oshun/meditation-player |
libs/meditation/player |
@nx/js:tsc |
., ./player, ./queue, ./mixer, ./playback-rate, ./hooks |
@oshun/meditation-timer |
libs/meditation/timer |
@nx/js:tsc |
., ./timer, ./presets, ./bells, ./ambient, ./hooks |
@oshun/meditation-breathing |
libs/meditation/breathing |
@nx/js:tsc |
., ./exercise, ./patterns, ./builder, ./guidance, ./visualization, ./haptics, ./hooks, ./history |
@oshun/meditation-session |
libs/meditation/session |
@nx/esbuild:esbuild |
., ./types, ./manager, ./persistence, ./analytics, ./scheduling, ./hooks |
@oshun/meditation-progress |
libs/meditation/progress |
@nx/js:tsc |
., ./tracker, ./streaks, ./statistics, ./achievements, ./milestones, ./export, ./hooks, ./sync |
@oshun/meditation-offline |
libs/meditation/offline |
@nx/js:tsc |
., ./types, ./manager, ./queue, ./storage, ./versioning, ./suggestions, ./hooks |
@oshun/meditation-analytics |
libs/meditation/analytics |
@nx/js:tsc |
. |
meditation-session builds with @nx/esbuild:esbuild configured for ["esm"]
format with declaration: true. The meditation-player and meditation-timer
packages additionally declare a publishConfig block that remaps main,
types, and exports to ./dist/*.js paths for a published build.
3. @oshun/meditation-core — Shared Primitives#
meditation-core is the shared type and utility layer. It has no runtime
dependencies and no eventemitter3 usage. All other packages depend on it
conceptually (not via package imports) — they reference its branded types and
utility functions at compile time.
3.1 Branded Types#
core/src/types.ts defines a generic brand helper and the shared
identifier/value types. Branded types prevent accidental misuse — a function
that accepts SessionId will reject a plain string at compile time even
though both are strings at runtime.
type Brand<TValue, TBrand extends string> = TValue & {
readonly __brand: TBrand;
};
The following branded types are defined:
| Brand | Base | Meaning |
|---|---|---|
SessionId |
string |
Meditation session identifier |
TrackId |
string |
Audio track identifier |
ContentId |
string |
Content item identifier |
TeacherId |
string |
Teacher/narrator identifier |
CourseId |
string |
Course identifier |
DurationSeconds |
number |
Duration in seconds |
DurationMilliseconds |
number |
Duration in milliseconds |
Timestamp |
number |
Unix timestamp (ms) |
Percentage |
number |
Value 0–100 |
ByteSize |
number |
Size in bytes |
DateString |
string |
YYYY-MM-DD calendar date |
TimeString |
string |
HH:MM time of day |
UrlString |
string |
Validated URL |
LocaleCode |
string |
xx or xx-YY locale code |
3.2 Enums and Literal Unions#
The following literal-union vocabularies are shared across packages. Using these centrally prevents the string values from drifting between packages.
| Type | Values |
|---|---|
Platform |
web, ios, android, desktop, server |
SubscriptionTier |
free, premium, lifetime |
ExperienceLevel |
beginner, intermediate, advanced |
MeditationCategory |
sleep, stress, focus, anxiety, morning, evening, breathwork, body-scan, visualization, gratitude, self-compassion, relationships, work, creativity, general |
SessionType |
timer, breathing, guided, ambient, body-scan, visualization, mindfulness, sleep, focus, custom |
SessionState |
idle, preparing, active, paused, completing, completed, cancelled, interrupted |
PlaybackState |
idle, loading, buffering, playing, paused, stopped, ended, error |
ContentKind |
meditation, course, lesson, teacher, program, ambient-sound, breathing-pattern |
3.3 Content and Session Models#
These are the primary data models shared across the engine. Downstream packages (session, progress, offline) define their own local shapes where they need additional fields, but these core models establish the common vocabulary.
ContentVersion —
{ version: string; checksum?: string; publishedAt: Timestamp; expiresAt?: Timestamp }.
LocalizedText — { locale: LocaleCode; value: string }.
ContentMetadata — title, optional description, language: LocaleCode,
category: MeditationCategory, tags: string[], optional durationSeconds,
optional teacherId, optional imageUrl, premium: boolean,
appStoreSafe: boolean, updatedAt: Timestamp.
ContentItem —
{ id: ContentId; kind: ContentKind; slug: string; metadata: ContentMetadata; version: ContentVersion }.
AudioTrack — id: TrackId, optional contentId, title, durationSeconds,
url: UrlString, mimeType: string, optional sizeBytes, optional
waveform: number[], updatedAt: Timestamp.
MeditationSession — id, type: SessionType, state: SessionState,
startedAt: Timestamp, optional endedAt, optional targetDurationSeconds,
elapsedSeconds, optional contentId, optional trackId, optional metadata.
SessionResult — sessionId, status (one of completed / partial /
cancelled / interrupted), startedAt, endedAt, elapsedSeconds, optional
targetDurationSeconds, completionPercentage: Percentage.
PauseRecord —
{ pausedAt: Timestamp; resumedAt?: Timestamp; reason?: string }.
InterruptionRecord — interruptedAt, optional resolvedAt,
resumed: boolean, and a reason from: phone-call, alarm, notification,
app-background, low-battery, connection-lost, user-action, system,
unknown.
DateRange — { startDate: DateString; endDate: DateString }.
PageRequest / PageResult<TItem> — pagination helpers; PageResult carries
items, totalCount, limit, offset, hasMore.
3.4 Utility Functions#
The utility modules are pure functions with no side effects. They are tree-shaken individually via subpath exports so consumers only pay for what they import.
| Module | Exports |
|---|---|
duration.ts |
seconds, milliseconds, minutesToSeconds, secondsToMilliseconds, millisecondsToSeconds, clampPercentage, progressPercentage, splitSeconds |
dates.ts |
timestamp, dateString, isTimeString, compareDateStrings, dateRange, addDays |
format.ts |
formatDuration (M:SS or H:MM:SS), formatMinutes (N min), slugify, titleCase |
validation.ts |
assertNonEmptyString, localeCode, urlString (accepts http:/https:/file:), ensureFiniteNumber, ensureIntegerRange |
Constructor-style helpers throw RangeError for invalid input (e.g. seconds
rejects negative or non-finite numbers; dateString rejects non-calendar
dates).
4. @oshun/meditation-player — Audio Player Engine#
4.1 Track and Content Types#
These types define what a track looks like from the player's perspective. A
MeditationTrack carries multiple AudioSource entries for different quality
levels so the adaptive streaming system can switch between them.
| Type | Values / Shape |
|---|---|
TrackId |
string (plain alias, not branded in this package) |
AudioContentType |
meditation, music, ambient, guided, sleep, breathing, soundscape |
AudioQuality |
low, medium, high, lossless |
AudioFormat |
mp3, aac, m4a, ogg, wav, flac |
AudioSource — url, format: AudioFormat, quality: AudioQuality, optional
bitrate, size, cached, cacheExpiry.
ContentMarker — id, time (seconds), type (chapter / cue /
instruction / transition / custom), label, optional data.
TrackMetadata — title, optional description, teacher, category,
duration (seconds), coverImageUrl, waveform: number[], tags, language,
markers: ContentMarker[].
MeditationTrack — id, type: AudioContentType, metadata: TrackMetadata,
sources: AudioSource[], optional preferredSourceIndex, isPremium,
isOfflineAvailable, updatedAt.
4.2 Player State#
The player state types describe what the MeditationPlayer can report at any
moment. PlayerState is the full snapshot; PlaybackProgress is the sub-type
used for time-based progress events.
| Type | Values |
|---|---|
PlaybackState |
idle, loading, buffering, playing, paused, stopped, ended, error |
RepeatMode |
off, one, all |
ShuffleMode |
off, on |
PlayerErrorCode is a real TypeScript enum with members UNKNOWN,
NETWORK_ERROR, FORMAT_NOT_SUPPORTED, DECODE_ERROR, SOURCE_NOT_FOUND,
PLAYBACK_ABORTED, OUTPUT_DEVICE_ERROR, DRM_ERROR, PERMISSION_DENIED,
AUDIO_CONTEXT_ERROR, CROSSFADE_ERROR, INVALID_STATE.
PlayerError — code: PlayerErrorCode, message, optional cause, optional
trackId, timestamp, recoverable: boolean.
PlaybackProgress — position, duration,
buffered: Array<[number, number]>, percentage, remaining (all seconds
except percentage).
PlayerState — full state: playbackState, currentTrack, progress,
volume, muted, playbackRate, repeatMode, shuffleMode, error,
seeking, crossfading.
4.3 Playback Rate (playback-rate.ts)#
The playback rate policy lives in @oshun/contracts and is re-exported by the
player. It is the canonical, enforced rule for how fast narration can be played
— not just a suggestion. normalizePlaybackRate throws PlaybackRateRangeError
for any rate outside the 0.85×–1.25× window unless clamp is explicitly passed.
PlaybackRate is re-exported from @oshun/contracts/tara/playback-rate. The
quality-preserving policy is the canonical control surface:
QUALITY_PRESERVING_PLAYBACK_RATE_POLICY = {
minRate: 0.85,
maxRate: 1.25,
step: 0.05,
decimalPlaces: 2,
preservePitch: true,
};
QUALITY_PRESERVING_PLAYBACK_RATE_OPTIONS therefore yields 9 discrete rates:
0.85, 0.90, 0.95, 1.00, 1.05, 1.10, 1.15, 1.20, 1.25. The package re-exports
normalizePlaybackRate, isQualityPreservingPlaybackRate, nextPlaybackRate,
playbackRateOptions, applyQualityPreservingPlaybackRate, and the
PlaybackRateRangeError class. normalizePlaybackRate throws
PlaybackRateRangeError for rates outside the 0.85×–1.25× range unless clamp
is passed. applyQualityPreservingPlaybackRate sets playbackRate,
defaultPlaybackRate, and the preservesPitch / mozPreservesPitch /
webkitPreservesPitch flags on a media element.
4.4 Audio Mixing Types#
The mixer manages multiple concurrent audio layers (voice, music, ambient, effects) with independent volume control and ducking. The types below describe the mixer's configuration and state surface.
| Type | Values |
|---|---|
AudioLayerType |
voice, music, ambient, effects, master |
AudioLayer — id, type, volume, muted, pan (−1 to 1), solo,
optional track.
MixerState — masterVolume, masterMuted,
layers: Record<AudioLayerType, AudioLayer>.
CrossfadeConfig — duration (ms), easing (linear / ease-in / ease-out
/ ease-in-out), overlap: boolean.
DuckConfig — enabled, duckLevel (0–1), attackTime (ms), releaseTime
(ms), affectedLayers: AudioLayerType[]. DEFAULT_DUCK_CONFIG ducks
['music', 'ambient'] to 0.3 with 200ms attack / 500ms release.
AutomationPoint — time (ms), volume, optional easing. VolumeAutomation
— layerType, points: AutomationPoint[], enabled.
The mixer also defines a sleep-fade subsystem: SleepFadeCompletion (none /
pause / stop), SleepFadeTarget (alias of AudioLayerType),
SleepFadeConfig, and SleepFadeState.
DEFAULT_MIXER_CONFIG layer volumes: voice 1.0, music 0.6, ambient 0.5, effects
0.8, master 1.0.
4.5 Queue Types#
The queue tracks a list of pending tracks plus a history of recently played ones. These types describe the queue's state and configuration.
QueueItem — queueId, track, addedAt, source (user / auto /
recommendation / program).
QueueState — items, currentIndex (−1 when nothing playing),
originalOrder: string[], history: QueueItem[], historySize.
PlaybackQueueConfig — maxHistorySize, initialRepeatMode,
initialShuffleMode. DEFAULT_QUEUE_CONFIG keeps a 50-item history.
4.6 Visualization Types#
The visualizer feeds real-time frequency data to the UI at the configured
updateInterval. The types below describe one visualization frame and how beat
detection is represented.
VisualizationData — frequencyData: Uint8Array, timeDomainData: Uint8Array,
amplitude (RMS 0–1), peak (0–1), frequencyBinCount.
VisualizationConfig — optional fftSize, smoothingTimeConstant,
minDecibels, maxDecibels. ExtendedVisualizationConfig adds
updateInterval, enableBeatDetection, beatSensitivity, frequencyBands.
DEFAULT_VISUALIZATION_CONFIG (player) uses fftSize 2048, smoothing 0.8,
minDecibels −100, maxDecibels −30, updateInterval 16ms, beat detection off,
beatSensitivity 0.7, 8 frequency bands.
FrequencyBand — index, startFrequency, endFrequency, amplitude,
peak. BeatData — isBeat, energy, averageEnergy, timeSinceLastBeat,
estimatedBpm: number | null. ExtendedVisualizationData adds bands, beat,
sampleRate, timestamp.
getStandardFrequencyBands() returns 7 named ranges (Hz): sub-bass 20–60,
bass 60–250, low-mid 250–500, mid 500–2000, upper-mid 2000–4000,
presence 4000–6000, brilliance 6000–20000.
4.7 Platform Abstraction Types#
The player's platform abstraction layer defines both the abstract interface for background audio management and the types for audio session events (interruptions, route changes, remote commands from hardware media buttons).
Platform — web, ios, android, electron, unknown. AudioBackend —
web-audio, html-audio, native, expo-av, react-native-track-player.
AudioAdapter is the platform-abstraction interface: getCapabilities, load,
play, pause, stop, seek, setVolume, setPlaybackRate,
getCurrentTime, getBuffered, getDuration, destroy, optional
createPeerAdapter (for overlapping crossfades) plus onEnded / onError /
onBuffering / onTimeUpdate callbacks.
RemoteCommand — play, pause, stop, togglePlayPause, nextTrack,
previousTrack, seekForward, seekBackward, seek.
AudioSessionCategory — ambient, soloAmbient, playback, record,
playAndRecord, multiRoute. AudioInterruptionType — began, ended.
AudioInterruptionReason — default, appWasSuspended, builtInMicMuted,
routeChange, phoneCall, alarm, other. AudioRoute.type — speaker,
headphones, bluetooth, airplay, carplay, unknown.
4.8 Configuration#
PlayerConfig contains the top-level player options. Nearly all fields have
defaults so callers only need to override what they care about.
PlayerConfig (all optional) — initialVolume, defaultPlaybackRate,
defaultRepeatMode, enableBackgroundAudio, enableAudioDucking, crossfade,
preferredQuality, autoPlayNext, progressUpdateInterval, bufferAhead,
enableVisualization, cacheSize. DEFAULT_PLAYER_CONFIG uses initialVolume
1, defaultPlaybackRate 1, crossfade 3000ms ease-in-out overlap, preferredQuality
high, progressUpdateInterval 250ms, bufferAhead 30s, cacheSize 500 MB.
AudioCacheConfig — maxSize (default 500 MB), dbName
(meditation-audio-cache), storeName (audio-files), defaultExpiration (0
= never), minFreeSpace (50 MB), useCacheApi, requestTimeout (30 s),
enablePartialDownloads, chunkSize (1 MB).
AdaptiveStreamingConfig — enableAdaptive, minBufferForPlayback (2 s),
targetBuffer (30 s), maxBuffer (60 s), rebufferThreshold (0.5 s),
upgradeThreshold (1.5×), downgradeThreshold (0.8×), speedSampleCount (10),
maxRetries (3), retryDelay (1000ms), segmentTimeout (30 s).
AudioSessionConfig default — category playback, mode spokenAudio,
autoRequestFocus/autoAbandonFocus/autoHandleInterruptions/resumeAfterInterruption
true, resumeDelay 500ms. BackgroundAudioConfig default — lock-screen
controls (play/pause, skip next/previous, seek bar, seek forward/backward at 15s
intervals; stop/like/dislike off), keepAudioSessionActive true,
showInControlCenter true.
BufferState — empty, filling, sufficient, full, stalled.
NetworkQuality — offline, poor, fair, good, excellent.
4.9 Primary Exports#
The code block below lists the primary classes, factory functions, and hook
factories exported by @oshun/meditation-player.
// Core class — event-driven, extends EventEmitter<MeditationPlayerEvents>
class MeditationPlayer {
load(track): Promise<void>; play(): Promise<void>; pause(): void;
stop(): void; seek(position): Promise<void>;
setVolume(v): void; setMuted(m): void; toggleMute(): void;
setPlaybackRate(rate): void; setRepeatMode(mode): void; setShuffleMode(mode): void;
fadeTo/fadeIn/fadeOut(duration): Promise<void>;
crossfade(nextTrack, duration?): Promise<void>;
setAdapter(a): void; setCrossfadeAdapter(a): void; destroy(): void;
// getters: currentState, currentTrack, playbackState, isPlaying, isPaused,
// isLoading, volume, isMuted, playbackRate, progress, currentTime,
// duration, repeatMode, shuffleMode
}
// PlaybackQueue — extends EventEmitter<PlaybackQueueEvents>
class PlaybackQueue {
add/addMultiple/insert/playNext; remove/removeById/removeByQueueId; clear;
reorder; current/currentItem; next/previous; skipTo/skipToTrackId;
hasNext/hasPrevious; getUpcoming/getAt/indexOf/contains;
setShuffleMode/toggleShuffle/reshuffle;
setRepeatMode/cycleRepeatMode; getHistory/clearHistory;
}
// AudioMixer, AudioVisualizer, StreamingManager, NetworkQualityMonitor,
// BufferManager, AdaptiveBitrateSelector, AudioCacheManager, AudioPreloader,
// ServiceWorkerCacheAdapter
// Platform three-tier abstractions (abstract base + Web + Noop + factory):
abstract class BackgroundAudioHandler { /* WebBackgroundAudioHandler, NoopBackgroundAudioHandler */ }
function createBackgroundAudioHandler(config?): BackgroundAudioHandler
abstract class AudioSessionManager { /* WebAudioSessionManager, NoopAudioSessionManager */ }
function createAudioSessionManager(config?): AudioSessionManager
// React hook factories
createPlayerHooks(): { usePlayer, usePlaybackProgress, useQueue,
useVisualization, useAudioSession, useMixer }
The factory functions select Web vs Noop by detecting window / navigator.
There is no separate native platform argument — React Native implementations
extend the abstract base class in the consuming app.
5. @oshun/meditation-timer — Meditation Timer#
5.1 State Types#
The timer moves through a strict linear state machine. The TimerPhase values
correspond to the three sub-periods of a session (preparation, main meditation,
wind-down) plus an interval phase that fires at recurring bell marks.
| Type | Values |
|---|---|
TimerState |
idle, preparing, running, paused, completed |
TimerPhase |
preparation, meditation, interval, wind-down |
5.2 Bell Types#
These types define the bell sound system. The 11-value BellType union covers
all built-in bells plus custom for user-supplied audio. Six pre-configured
BellSound constants — with real volumes and fade-out durations — are exported
for direct use.
BellType (11 values): tibetan-bowl, singing-bowl, gong, temple-bell,
chime, bell, soft-tone, nature-chime, crystal-bowl, zen-bell,
custom.
BellTrigger — start, interval, warning, end, preparation-end.
BellSound — type: BellType, optional customUrl, volume, repeatCount,
repeatDelay (ms), fadeOutDuration (ms).
BellConfiguration — startBell, intervalBell, warningBell, endBell,
preparationEndBell (each BellSound | null).
bells.ts exports 6 built-in BellSound constants, each with a real
fade-out:
| Constant | type |
volume | fadeOutDuration |
|---|---|---|---|
BELL_TIBETAN_BOWL |
tibetan-bowl |
0.7 | 4000ms |
BELL_SINGING_BOWL |
singing-bowl |
0.7 | 3000ms |
BELL_GONG |
gong |
0.6 | 6000ms |
BELL_TEMPLE_BELL |
temple-bell |
0.7 | 5000ms |
BELL_CHIME |
chime |
0.6 | 2000ms |
BELL_SOFT_TONE |
soft-tone |
0.5 | 1500ms |
All six default to repeatCount: 1, repeatDelay: 0. DEFAULT_BELL_CONFIG
uses BELL_SINGING_BOWL as the start bell and a 3×-repeat singing bowl (2000ms
delay) as the end bell. BellPlayer resolves sounds against BELL_SOUND_PATHS
(ten paths under /sounds/bells/), uses the Web Audio API with an HTML5 Audio
fallback, and supports custom sounds via createCustomBell(url, options).
5.3 Ambient Sound Types#
The ambient sound system supports up to 8 simultaneous layers, each drawn from a 27-value sound catalog. The most common sounds are promoted to named constants for convenience.
AmbientSoundCategory — nature, weather, urban, musical, noise,
binaural, custom.
AmbientSoundType (27 values): rain, rain-light, rain-heavy,
thunderstorm, ocean, ocean-waves, river, stream, waterfall,
forest, birds, crickets, wind, wind-leaves, fire, campfire,
coffee-shop, city, singing-bowls, om, temple, white-noise,
pink-noise, brown-noise, binaural-alpha, binaural-theta,
binaural-delta, custom.
ambient.ts defines an internal AMBIENT_SOUND_DEFINITIONS map covering all 26
non-custom types (each with a name, description, asset path under
/sounds/ambient/, and default volume), and exports 7 built-in AmbientSound
constants: AMBIENT_RAIN (0.5), AMBIENT_OCEAN (0.5), AMBIENT_FOREST
(0.4), AMBIENT_FIRE (0.4), AMBIENT_WHITE_NOISE (0.3), AMBIENT_PINK_NOISE
(0.3), AMBIENT_BROWN_NOISE (0.3). All loop.
AmbientSound — type, category, optional customUrl, volume, loop.
AmbientLayer — id, sound, currentVolume, active, muted. AmbientMix
— id, name, layers: AmbientLayer[], masterVolume.
The three built-in curated mixes provide ready-made combinations for the most common use cases:
| Constant | Layers | masterVolume |
|---|---|---|
AMBIENT_MIX_PEACEFUL |
rain @ 0.4 + singing-bowls @ 0.2 | 1.0 |
AMBIENT_MIX_FOCUS |
brown-noise @ 0.3 + fire @ 0.2 | 1.0 |
AMBIENT_MIX_SLEEP |
ocean @ 0.4 + binaural-delta @ 0.2 | 0.8 |
AmbientPlayer supports per-layer volume, smooth fadeLayerVolume with an
ease-in-out curve, mute/solo, loadMix with optional crossfade, and
createMixFromCurrent / createCustomSound. DEFAULT_AMBIENT_CONFIG allows up
to 8 concurrent layers with a 2000ms default fade.
5.4 Interval Types#
Intervals allow actions to be fired at specific time offsets or on a recurring cadence within a session (e.g., a bell every 10 minutes, or a voice prompt at the halfway mark).
IntervalActionType — bell, vibration, voice-prompt, ambient-change,
custom. IntervalAction carries the type plus the matching payload field.
TimerInterval — id, timeOffset (s), action, triggered, optional
label. RecurringInterval — every (s), action, optional startAfter,
stopBefore, includeFirst.
5.5 Configuration#
TimerConfig is the top-level configuration object passed to MeditationTimer.
Key sub-types for preparation and wind-down phases are shown below.
PreparationConfig — enabled, duration (s), voiceCountdown.
WindDownConfig — enabled, duration (s), fadeAmbient, reminderBell.
TimerConfig — duration (s), preparation, windDown, bells,
intervals: TimerInterval[], recurringIntervals: RecurringInterval[],
ambientMix: AmbientMix | null, hapticFeedback, screenDimming,
keepScreenOn, backgroundMode (continue / pause / notify).
DEFAULT_TIMER_CONFIG — 10-minute (600 s) session, 5 s preparation, 10 s
wind-down with fadeAmbient, singing-bowl start bell, 3×-repeat singing-bowl
end bell, backgroundMode: continue.
HapticConfig — enabled, intensity (light / medium / heavy),
onStart, onInterval, onComplete, breathingPattern. HapticType —
impact, notification, selection. HapticNotificationType — success,
warning, error.
5.6 Presets#
PresetDuration is a literal union of valid durations (in minutes):
PresetDuration — literal union
1 | 3 | 5 | 10 | 15 | 20 | 25 | 30 | 45 | 60 | 90 | 120. PresetCategory —
quick, standard, extended, pomodoro, sleep, custom.
TimerPreset — id, name, description, category, durationMinutes,
config: Partial<TimerConfig>, builtIn, optional icon, sortOrder.
presets.ts defines 12 built-in presets (ALL_PRESETS), grouped into 5
collections:
| Collection | Presets |
|---|---|
QUICK_PRESETS |
PRESET_1_MINUTE (Mindful Moment), PRESET_3_MINUTES (Quick Break), PRESET_5_MINUTES (Short Session) |
STANDARD_PRESETS |
PRESET_10_MINUTES (Standard Session), PRESET_15_MINUTES (Extended Session), PRESET_20_MINUTES (Deep Practice), PRESET_30_MINUTES (Long Session) |
EXTENDED_PRESETS |
PRESET_45_MINUTES (Extended Practice), PRESET_60_MINUTES (Full Hour) |
POMODORO_PRESETS |
PRESET_POMODORO_WORK (25 min Focus Work), PRESET_POMODORO_BREAK (5 min Short Break) |
SLEEP_PRESETS |
PRESET_SLEEP (30 min Sleep Timer, no end bell) |
The 9 single-duration presets (1/3/5/10/15/20/30/45/60 minutes) are re-exported
by name from the package entry point; the Pomodoro and Sleep presets are part of
ALL_PRESETS but are not individually re-exported. Each preset carries a full
Partial<TimerConfig> — for example PRESET_20_MINUTES schedules a recurring
soft-chime interval bell every 10 minutes, and PRESET_60_MINUTES every 15
minutes.
Preset utilities: getPresetById, getPresetsByCategory,
getPresetsByDuration,
createCustomPreset(name, durationMinutes, configOverrides?, description?),
presetToConfig. The PresetManager interface (createPresetManager()) tracks
custom presets and favorites: getAllPresets, getBuiltInPresets,
getCustomPresets, getPreset, addCustomPreset, removeCustomPreset,
updateCustomPreset, getFavorites, addFavorite, removeFavorite,
isFavorite.
5.7 Statistics Type#
TimerStatistics aggregates usage data across sessions for the timer. It is
returned by the PresetManager and available for session-history displays.
TimerStatistics — totalSessions, totalTimeSeconds, averageDuration,
longestSession, currentStreak, longestStreak, sessionsThisWeek,
timeThisWeekSeconds, sessionsToday, timeTodaySeconds, lastSessionDate,
favoritePresetId, favoriteDuration.
5.8 Primary Exports#
// MeditationTimer — extends EventEmitter<TimerEvents>
class MeditationTimer {
configure(config): void; setDuration(s): void;
setBellCallback(cb): void; setHapticCallback(cb): void;
start(): Promise<void>; pause(): void; resume(): Promise<void>;
stop(): void; reset(): void; skipPreparation(): void;
addTime(s): void; extend(minutes): void; loadPreset(preset): void;
getProgress(): TimerProgress | null; getSession(): readonly TimerSession | null;
dispose(): void; destroy(): void;
}
// Options: tickInterval (default 1000ms), driftCorrection (default true),
// autoStop (default true). The timer compares against wall-clock timestamps to
// correct drift over long sessions.
// BellPlayer, AmbientPlayer
// Background three-tier: TimerBackgroundHandler (abstract) +
// WebTimerBackgroundHandler + NoopTimerBackgroundHandler + createTimerBackgroundHandler
// — wraps Wake Lock API, Notifications API, Web Locks API, Page Visibility API.
// Haptics three-tier: HapticManager (abstract) + WebHapticManager (Vibration
// API) + NoopHapticManager + createHapticManager.
// React hook factories
createTimerHooks(hooks): { useMeditationTimer, useTimerState,
useTimerProgress, useTimerPresets }
createTimerHooks and each createUse* factory take an injected ReactHooks
object (useState, useEffect, useCallback, useMemo, useRef). The
package also exports convenience hooks useMeditationTimer, useTimerState,
useTimerProgress, useTimerPresets that resolve hooks registered via
setTimerReactHooks (re-exported as setReactHooks from hooks.ts).
6. @oshun/meditation-breathing — Breathing Exercise Engine#
6.1 Phase and State Types#
These types describe the vocabulary of a breathing exercise. BreathingPhase
names every phase a pattern can contain; ExerciseState mirrors the standard
lifecycle state machine used across all engine packages.
| Type | Values |
|---|---|
BreathingPhase |
inhale, hold-in, exhale, hold-out, rest |
ExerciseState |
idle, running, paused, completed |
PhaseTransition |
start, continue, end |
PatternCategory |
relaxation, focus, energy, sleep, stress-relief, meditation, custom |
PatternDifficulty |
beginner, intermediate, advanced |
CompletionMode |
cycles, duration, manual |
EasingFunction |
linear, easeIn, easeOut, easeInOut, sine |
PhaseConfig — phase: BreathingPhase, duration (s), label, optional
instruction. RatioPattern — { inhale; holdIn; exhale; holdOut } durations.
BreathingPattern — id, name, description, category, difficulty,
phases: PhaseConfig[], cycleDuration (computed from phases), builtIn,
optional icon, color, sortOrder, benefits, instructions,
recommendedCycles, recommendedDuration.
6.2 Built-In Breathing Patterns (10)#
patterns.ts exports 10 BreathingPattern constants in ALL_PATTERNS. The
ratio column shows the literal inhale–hold-in–exhale–hold-out timing in seconds
from createPhasesFromRatio; phases with duration 0 are omitted from phases.
| Constant | id |
Name | Category | Difficulty | Ratio (s) |
|---|---|---|---|---|---|
PATTERN_BOX_BREATHING |
box-breathing |
Box Breathing | focus |
beginner | 4-4-4-4 |
PATTERN_4_7_8 |
4-7-8 |
4-7-8 Relaxing Breath | relaxation |
intermediate | 4-7-8-0 |
PATTERN_COHERENT |
coherent |
Coherent Breathing | meditation |
beginner | 5-0-5-0 |
PATTERN_ENERGIZING |
energizing |
Energizing Breath | energy |
beginner | 2-0-4-0 |
PATTERN_CALMING |
calming |
Calming Breath | stress-relief |
beginner | 4-0-2-0 |
PATTERN_2_1_RATIO |
2-1-ratio |
2:1 Ratio Breathing | relaxation |
intermediate | 4-0-8-0 |
PATTERN_ALTERNATE_NOSTRIL |
alternate-nostril |
Alternate Nostril | meditation |
intermediate | see below |
PATTERN_WIM_HOF |
wim-hof |
Wim Hof Power Breathing | energy |
advanced | 2-2 (one power breath) |
PATTERN_SLEEP |
sleep |
Sleep Breathing | sleep |
beginner | 4-7-8-2 |
PATTERN_FOCUS |
focus |
Focus Breathing | focus |
beginner | 4-2-4-2 |
Notes on the patterns whose shape is not a simple four-part ratio:
- Alternate Nostril is a hand-built 6-phase cycle: inhale 4 (left nostril) → hold-in 4 → exhale 4 (right) → inhale 4 (right) → hold-in 4 → exhale 4 (left).
- Wim Hof is a single 2-phase power breath (inhale 2, exhale 2);
recommendedCyclesis 30, and theinstructionsdescribe the full retention protocol (30 breaths, exhale hold, 15 s recovery hold, 3 rounds). - Calming Breath is a long-inhale / short-exhale pattern (inhale 4, exhale 2), not a long-exhale pattern.
- Energizing Breath is a short-inhale / longer-exhale pattern (inhale 2, exhale 4).
Each pattern carries an icon, hex color, sortOrder (1–10), a benefits
array, an instructions string, recommendedCycles, and recommendedDuration.
Pattern collections (computed by filtering ALL_PATTERNS): BEGINNER_PATTERNS
(difficulty beginner), RELAXATION_PATTERNS (category relaxation or
sleep), FOCUS_PATTERNS (focus), ENERGY_PATTERNS (energy),
MEDITATION_PATTERNS (meditation). Pattern utilities: getPatternById,
getPatternsByCategory, getPatternsByDifficulty, createPatternFromRatio.
6.3 Exercise Configuration#
ExerciseConfig configures a single exercise run: which pattern to use, how
completion is defined, and which guidance modalities (audio, haptics) are
active.
ExerciseConfig — pattern, completionMode, targetCycles,
targetDurationMinutes, audioGuidance, hapticFeedback, countdownSeconds,
transitionSounds, voiceType (none / bell / voice-male /
voice-female), volume (0–1). DEFAULT_EXERCISE_CONFIG —
completionMode: cycles, 10 target cycles, 5-minute fallback, audio/haptics on,
3-second countdown, voiceType: bell, volume 0.7.
6.4 Visualization#
The VisualizationState snapshot is what the UI receives on every tick. It
contains everything a renderer needs to draw the current breathing circle
without requiring the UI to compute any animation math itself.
VisualizationState — phase, phaseProgress (0–1), cycleProgress (0–1),
scale (0–1), targetScale, opacity, color (hex), rotation (degrees),
pulse (0–1), easing: EasingFunction, label, timerDisplay.
PhaseColors — per-phase hex colors. DEFAULT_PHASE_COLORS — inhale #4CAF50,
hold-in #2196F3, exhale #9C27B0, hold-out #FF9800, rest #607D8B.
VisualizationConfig — minScale, maxScale, colors, showPulse,
pulseFrequency (Hz), showRotation, rotationSpeed.
DEFAULT_VISUALIZATION_CONFIG — minScale 0.6, maxScale 1.0, pulse on at 0.5 Hz,
rotation off at 10°/s. Per-phase easing: inhale easeOut, exhale easeIn,
holds linear, rest sine. interpolatePhaseColor blends two phase colors in
RGB space.
6.5 Haptics#
The haptics subsystem provides phase-timed vibration feedback. The pacing controller builds full-phase vibration patterns — stronger pulses on inhale, lighter pulses on exhale — enabling eyes-closed, screen-free practice for accessibility.
HapticIntensity — light, medium, heavy. BreathingHapticConfig —
enabled, intensity, onPhaseTransition, inhalePattern, exhalePattern,
onCycleComplete, onExerciseComplete, pacing: VibrationPacingConfig.
VibrationPacingConfig — enabled, intensity, intervalMs, phaseStartCue,
completionCue, maxPhaseDurationSeconds. DEFAULT_VIBRATION_PACING_CONFIG is
disabled by default with 1000ms interval and a 30-second cap;
DEFAULT_HAPTIC_CONFIG is enabled with medium intensity.
The package exports BreathingHapticManager, VibrationPacingController,
buildVibrationPacingPattern, getVibrationPacingCompletePattern,
isVibrationPacingSupported, createWebVibrationAdapter, and the base
PHASE_PATTERNS map. The VibrationAdapter interface (isSupported,
vibrate, cancel) abstracts the platform vibration API. The pacing subsystem
builds full-phase vibration patterns (stronger inhale pulses, lighter exhale
pulses, steady holds) for hearing-accessibility eyes-closed practice.
6.6 Audio Guidance#
GuidancePlayer plays phase-boundary cues from /sounds/breathing/ asset
paths, synchronized with the exercise runner's phase events. Each AudioCueType
value maps to a specific trigger point in the exercise lifecycle.
AudioCueType — inhale, hold, exhale, release, cycle-complete,
exercise-complete, countdown, bell, custom. AudioCue — type, url,
volume, delay. AudioGuidanceConfig — enabled, masterVolume,
phaseTransitions, cycleComplete, exerciseComplete, voiceCues,
bellCues, customCues: Map<AudioCueType, AudioCue>. DEFAULT_AUDIO_CONFIG
has bell cues on, voice cues off, master volume 0.7. GuidancePlayer resolves
cues against /sounds/breathing/ asset paths and plays them via the Web Audio
API in sync with the exercise's phase events.
6.7 Session History#
SessionRecord captures what happened in a single breathing exercise session.
HistoryStatistics aggregates across all recorded sessions for history display.
SessionRecord — id, patternId, patternName, startedAt, endedAt,
durationSeconds, completedCycles, completed, completionMode, target,
optional notes, rating. HistoryStatistics — totalSessions,
totalTimeSeconds, totalCycles, averageSessionSeconds,
averageCyclesPerSession, favoritePatternId, currentStreak,
longestStreak, lastSessionDate, sessionsThisWeek, sessionsToday.
6.8 Primary Exports#
// BreathingExercise — extends EventEmitter<ExerciseEvents>
class BreathingExercise {
start(pattern, configOverrides?): Promise<void>;
pause(): void; resume(): void; stop(): void; reset(): void; skipPhase(): void;
setRepetitions(n): void; setDuration(min): void; setCompletionMode(m): void;
setAudioGuidance(b): void; setHapticFeedback(b): void;
getConfig(); getSession(); dispose();
// getters: state, phase; predicates: isRunning, isPaused, isCompleted, isIdle
}
// Options: tickInterval (default 100ms for smooth animation), driftCorrection.
// BreathingPatternBuilder — fluent builder:
// .name/.description/.category/.difficulty/.icon/.color/.benefit/.benefits
// .instructions/.recommendedCycles/.recommendedDuration
// .inhale/.holdIn/.exhale/.holdOut/.rest (hold-* and rest skip 0-duration)
// .phase(type) -> PhaseBuilder .repeat(n) .fromRatio(i,h,e,h) .build()
// Factories: createPatternBuilder, createPatternFromRatio (aka quickPattern),
// createBoxBreathing(seconds), createCoherentBreathing(seconds).
// VisualizationProvider, GuidancePlayer, BreathingHapticManager
// SessionHistoryManager + InMemorySessionStorage + LocalSessionStorage
// (createSessionHistoryManager / createLocalSessionHistoryManager);
// exportAsJSON / exportAsCSV.
// React hook factories
createBreathingHooks(hooks): { useBreathingExercise, useBreathingState,
useBreathingAnimation, useBreathingPatterns }
createBreathingHooks and each createUse* factory take an injected
ReactHooks object. The package also exports the convenience hooks
useBreathingExercise, useBreathingState, useBreathingAnimation,
useBreathingPatterns (which resolve hooks registered via
setBreathingReactHooks).
7. @oshun/meditation-session — Session Management#
7.1 Branded Types and Enums#
The session package defines its own branded types (separate from core) for domain-specific safety, and its own enum values for session lifecycle concepts.
Branded types: SessionId, DurationSeconds, DurationMs, Timestamp,
DateString, TimeString, Percentage.
| Type | Values |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | --- | --- | --- | --- | --------------- |
| SessionType | timer, breathing, guided, ambient, body-scan, visualization, mindfulness, sleep, focus, custom |
| SessionState | idle, preparing, active, paused, completing, completed, cancelled, interrupted |
| InterruptionReason | phone-call, alarm, notification, app-background, low-battery, connection-lost, user-action, system, unknown |
| CompletionStatus | full, partial, minimal, abandoned |
| RecurrencePattern | daily, weekdays, weekends, weekly, monthly, custom |
| DayOfWeek | 0 | 1 | 2 | 3 | 4 | 5 | 6 (0 = Sunday) |
| AnalyticsEventType | session_created, session_started, session_paused, session_resumed, session_completed, session_cancelled, session_interrupted, session_abandoned, phase_changed, milestone_reached, achievement_unlocked |
7.2 Configuration#
These configuration objects are passed to the session manager and its
sub-systems at construction time. All fields have defaults; only override what
differs from DEFAULT_SESSION_CONFIG.
SessionAudioConfig — primarySource, backgroundSource, guidanceSource,
primaryVolume, backgroundVolume, enableDucking, fadeDurationMs.
SessionTimingConfig — targetDurationSeconds, preparationSeconds,
completionSeconds, progressIntervalMs, allowOvertime,
maxOvertimeSeconds.
SessionNotificationConfig — startBell, endBell, intervalBells,
bellIntervalSeconds, hapticFeedback.
SessionConfig — type, optional title / description, timing, optional
audio, notifications, keepScreenAwake, blockNotifications, optional
metadata / tags. DEFAULT_SESSION_CONFIG defines a 600-second target with 5
s preparation, 10 s completion, audio ducking on, start/end bells on, interval
bells off.
PersistenceConfig — autoSave, autoSaveIntervalMs, saveOnPause,
saveOnBackground, keepHistory, maxHistoryItems.
DEFAULT_PERSISTENCE_CONFIG auto-saves every 5000ms, keeps 100 history items.
AnalyticsConfig — enabled, optional tracker, includeDeviceInfo,
sampleRate (0–1), optional customProperties.
SchedulerConfig — storage, checkIntervalMs, optional onNotification /
onTrigger, timezone. DEFAULT_SCHEDULER_CONFIG checks every 60000ms in the
resolved local timezone.
SessionManagerConfig — optional persistence / storage / analytics /
defaultConfig, minStreakDurationSeconds, enableDndMode.
DEFAULT_SESSION_MANAGER_CONFIG requires 60 seconds for streak credit and
enables DND mode.
7.3 State and Result Models#
These models describe what a session looks like at runtime and what it produces
when it ends. MeditationSession is the live session object; SessionResult is
the summary returned by completeSession().
SessionProgress — elapsedSeconds, remainingSeconds, progressPercentage,
isOvertime, overtimeSeconds, currentPhase (preparation / main /
completion / overtime).
SessionInterruption — timestamp, reason, durationMs, resumed.
SessionPause — pausedAt, resumedAt | null, durationMs.
MeditationSession (session-package shape) — id, config, state,
createdAt, startedAt, endedAt, updatedAt, activeDurationSeconds,
totalElapsedSeconds, pauses, interruptions, optional notes, rating,
moodBefore, moodAfter, heartRateData: number[], userData.
SessionResult — session, completionStatus, completionPercentage,
activeDurationSeconds, totalPauseSeconds, pauseCount, interruptionCount,
countsTowardStreak, optional achievements / milestones.
ScheduledSession — id, sessionConfig, title, optional description,
enabled, time: TimeString, recurrence, optional daysOfWeek /
dayOfMonth, startDate, optional endDate, notification, createdAt,
updatedAt, optional lastTriggeredAt / nextScheduledAt / metadata.
ScheduleNotification — enabled, minutesBefore, title, body, optional
sound / vibrate / persistent. UpcomingSession — schedule,
scheduledAt, notified, started, skipped.
7.4 Primary Exports#
// SessionManager — extends EventEmitter<SessionManagerEvents>
class SessionManager {
createSession(config): MeditationSession;
startSession(session): Promise<void>;
pauseSession(): void;
resumeSession(): void;
completeSession(): Promise<SessionResult>;
cancelSession(): void;
recordInterruption(reason): void;
getCurrentSession(): MeditationSession | null;
getProgress(): SessionProgress | null;
}
// createSessionManager(config?)
// Persistence — three interchangeable backends:
// InMemorySessionStorage, LocalSessionStorage, IndexedDBSessionStorage
// + SessionPersistenceManager (auto-save, app-background save, recovery)
// factories: createInMemoryStorage, createLocalStorage,
// createIndexedDBStorage, createPersistenceManager
// Analytics:
// SessionAnalyticsManager, ConsoleAnalyticsTracker, InMemoryAnalyticsStorage
// factories: createAnalyticsManager, createConsoleTracker,
// createInMemoryAnalyticsStorage
// AggregatedAnalytics: totalSessions, totalActiveSeconds,
// averageDurationSeconds, completionRate, sessionsByType,
// sessionsByDayOfWeek, sessionsByHourOfDay, averageCompletionPercentage,
// sessionsByCompletionStatus, totalPauseCount, averagePausesPerSession,
// averageMoodChange
// Scheduling:
// SessionScheduler + InMemorySchedulerStorage + LocalSchedulerStorage
// exportToICalendar(schedule) -> iCalendar (.ics) text
// factories: createSessionScheduler, createInMemorySchedulerStorage,
// createLocalSchedulerStorage
// React hook factories
createSessionHooks: (createUseSessionManager,
createUseCurrentSession,
createUseSessionProgress,
createUseSessionState,
createUseScheduler);
useScheduler's result exposes schedules, upcoming, nextSession,
isRunning, createSchedule, updateSchedule, deleteSchedule,
enableSchedule, disableSchedule, start, stop.
8. @oshun/meditation-progress — Progress Tracking#
8.1 Branded Types and Enums#
The progress package defines its own branded types and the enum vocabularies specific to streak management, statistics, achievements, and sync.
Branded types: DurationMinutes, DurationSeconds, Timestamp, DateString,
Percentage.
| Type | Values |
|---|---|
SessionType |
same 10 values as core/session |
SessionStatus |
completed, interrupted, cancelled |
PredefinedRange |
today, yesterday, this-week, last-week, this-month, last-month, this-year, last-year, all-time |
AchievementCategory |
streak, time, sessions, exploration, consistency, special |
AchievementRarity |
common, uncommon, rare, epic, legendary |
MilestoneCategory |
minutes, sessions, streak, days |
SyncStatus |
idle, syncing, error, success |
ConflictStrategy |
local-wins, remote-wins, merge, manual |
8.2 Core Models#
MeditationSession (progress-package shape) has a simpler shape than the
session package's version — it captures only the fields the progress tracker
needs to update streaks and statistics.
MeditationSession (progress-package shape) — id, type, startedAt,
endedAt, durationSeconds, optional targetDurationSeconds, status,
optional patternId / contentId / rating / notes / metadata.
StreakData — currentStreak, longestStreak, streakStartDate,
longestStreakEndDate, completedToday, completedYesterday,
freezesRemaining, freezesUsed, lastFreezeDate, totalActiveDays.
StreakConfig governs how the streak calculator determines whether a session
counts toward a given day's practice:
StreakConfig — minSessionSeconds, maxFreezesPerMonth, timezone,
dayResetHour, allowGracePeriod, gracePeriodHours. DEFAULT_STREAK_CONFIG
requires 60-second sessions, allows 2 freezes/month, uses UTC, resets at hour 0,
and allows a 4-hour grace period.
Statistics is a wide aggregate type: range, totalTimeSeconds,
totalSessions, averageSessionSeconds, medianSessionSeconds,
longestSessionSeconds, shortestSessionSeconds, sessionsByType,
timeByType, sessionsByDayOfWeek, sessionsByTimeOfDay, mostActiveDay,
mostActiveTime, completionRate, consistencyScore, sessionsPerWeek,
sessionsPerDay, activeDays, activeDaysPercentage, averageRating,
favoriteType. TimeOfDayDistribution buckets are earlyMorning (5–9am),
morning (9–12), afternoon (12–5pm), evening (5–9pm), night (9pm–5am).
8.3 Achievements#
ACHIEVEMENT_DEFINITIONS contains 26 built-in achievements. Each has id,
name, description, category, rarity, icon, points, hidden,
condition.
The table below groups them by category. Rarity scales with goal difficulty —
first-* achievements are common; year-long streak, 500 hours, and 1000
sessions are legendary and hidden until unlocked.
| Group | Achievements |
|---|---|
| First steps | first-meditation (10pt), first-breathing (10pt), first-guided (10pt) |
| Streak | streak-3, streak-7, streak-14, streak-30, streak-60, streak-100, streak-365 (25→2000pt) |
| Time | time-1h, time-10h, time-50h, time-100h, time-500h (25→1500pt) |
| Sessions | sessions-10, sessions-50, sessions-100, sessions-500, sessions-1000 (25→1000pt) |
| Exploration | explorer-3 (try 3 types, 30pt), explorer-all (try all 10 types, 150pt) |
| Consistency | early-bird (10 sessions before 7am), night-owl (10 sessions after 10pm) |
| Special | perfectionist (a full week with 100% completion, hidden), marathon (a 60+-minute session) |
Achievement extends AchievementDefinition with unlocked, unlockedAt,
progress, currentValue, targetValue. The AchievementManager re-evaluates
all ACHIEVEMENT_THRESHOLDS against completed sessions on every recorded
session.
8.4 Milestones#
MILESTONE_DEFINITIONS contains 26 built-in milestones across 4 categories.
Unlike achievements, milestones are strictly progress markers against
quantitative thresholds; they do not have rarity tiers. Each has id, name,
description, category, targetValue, icon, celebrationMessage.
| Category | Milestone target values |
|---|---|
minutes |
100, 500, 1000, 2500, 5000, 10000, 25000, 50000 (8) |
sessions |
10, 25, 50, 100, 250, 500, 1000 (7) |
streak |
7, 14, 30, 60, 90, 180, 365 days (7) |
days |
7, 30, 100, 365 active days (4) |
Milestone extends MilestoneDefinition with reached, reachedAt,
progress, currentValue. MilestoneNotification — { milestone; isNew }.
8.5 Export and Sync#
ExportDataTypes — sessions, statistics, streak, achievements,
milestones (all boolean). ExportOptions — optional dateRange, dataTypes,
sessionTypes, completedOnly, isoTimestamps. ExportData carries
metadata plus the selected sections. ProgressExporter produces both JSON and
CSV output.
SyncState — status, lastSyncAt, pendingChanges, lastError, isOnline.
SyncConfig — endpoint, getAuthToken, conflictStrategy,
autoSyncInterval, syncOnStart, syncOnReconnect. ProgressSyncManager
queues offline changes, uploads them, merges remote sessions, and resolves
conflicts per the configured strategy.
8.6 Primary Exports#
// ProgressTracker — extends EventEmitter<ProgressEvents>
class ProgressTracker {
recordSession(session): Promise<{ streak; newAchievements; newMilestones }>;
getHistory(options?): Promise<SessionHistory>;
// delegates to StreakCalculator, StatisticsCalculator, AchievementManager,
// MilestoneManager, and (when syncConfig is supplied) ProgressSyncManager
}
// InMemoryProgressStorage, LocalProgressStorage
// factories: createProgressTracker, createInMemoryProgressTracker,
// createLocalProgressTracker
// StreakCalculator (timezone-aware, freeze tokens, grace period)
// StatisticsCalculator + getDateRangeFromPredefined
// AchievementManager + ACHIEVEMENT_DEFINITIONS
// MilestoneManager + MILESTONE_DEFINITIONS
// ProgressExporter, ProgressSyncManager
// React hook factories
createProgressHooks: (createUseStreak,
createUseStatistics,
createUseAchievements,
createUseMilestones,
createUseSync,
createUseProgressTracker);
// Convenience hooks (after setProgressReactHooks): useStreak, useStatistics,
// useAchievements, useMilestones, useSync, useProgressTracker
recordSession is atomic: it saves the session, recalculates the streak,
evaluates achievements, checks milestones, optionally queues a sync change, and
returns the updated streak plus newly unlocked achievements/milestones in one
call.
9. @oshun/meditation-offline — Offline Content Management#
9.1 Branded Types and Enums#
Branded types: ContentId, DownloadId, VersionString, ByteSize,
Timestamp, Percentage, UrlString.
The enums below define every status and policy value in the offline system — from the download state machine to the suggestion engine.
| Type | Values |
|---|---|
ContentType |
audio, guided-meditation, course, music, ambient-sound, video, image, data, bundle |
ContentQuality |
low, medium, high, lossless |
DownloadStatus |
queued, downloading, paused, completed, failed, cancelled, verifying |
DownloadErrorType |
network, storage, authentication, not-found, checksum-mismatch, expired, cancelled, unknown |
QueuePriority |
critical, high, normal, low, background |
CleanupStrategy |
oldest-first, least-used, largest-first, expired-first, custom |
VersionComparison |
older, same, newer |
UpdatePolicy |
manual, notify, auto-wifi, auto-always |
SuggestionReason |
frequently-used, favorite, course-progress, similar-content, popular, new-release, expiring-soon, small-size, recommended |
NetworkType |
wifi, cellular, ethernet, offline, unknown |
9.2 Content and Download Models#
DownloadableContent describes a content item that can be downloaded. Once
downloaded, it is represented as DownloadedContent which extends the base with
local storage metadata.
DownloadableContent — id, type, version: VersionString, url,
sizeBytes, optional checksum (SHA-256), metadata, qualityOptions,
selectedQuality, requiresAuth, expiresAt. DownloadedContent extends it
with localPath, downloadedBytes, downloadedAt, lastAccessedAt,
accessCount, isComplete, isVerified.
DownloadError — type, message, optional statusCode, retryable,
timestamp, retryCount. DownloadProgress — downloadId, contentId,
status, downloadedBytes, totalBytes, progressPercentage,
speedBytesPerSecond, estimatedSecondsRemaining, startedAt, optional
error.
9.3 Configuration#
QueueConfig — maxConcurrent, maxRetries, retryDelayMs,
exponentialBackoff, maxBackoffMs, priorityWeights, autoStart.
DEFAULT_QUEUE_CONFIG — 3 concurrent downloads, 3 retries, exponential backoff
capped at 60000ms, priority weights
critical 100 / high 75 / normal 50 / low 25 / background 10.
StorageLimits — maxTotalBytes, warningThreshold, criticalThreshold,
minFreeBytes, optional contentTypeLimits. DEFAULT_STORAGE_LIMITS — 5 GB
cap, 80% warning, 95% critical, 100 MB minimum free.
CleanupConfig — strategy, targetUsagePercentage, minAgeDays, optional
excludeTypes / excludeIds, autoCleanExpired, optional customCleanup.
DEFAULT_CLEANUP_CONFIG uses least-used, targets 70% usage, requires a 7-day
minimum age, auto-cleans expired content.
VersioningConfig — checkIntervalMs, updatePolicy, checkOnStart, optional
manifestUrl, deleteOldVersion. DEFAULT_VERSIONING_CONFIG — checks every 24
hours with notify policy.
NetworkPreferences — wifiOnly, allowMetered, allowCellular, optional
maxCellularBytes. DEFAULT_NETWORK_PREFERENCES — Wi-Fi not required, metered
disallowed, cellular allowed up to 50 MB.
SuggestionConfig — maxSuggestions, minConfidence, useUsageData,
suggestFavorites, suggestCourseProgress, suggestPopular, optional
maxSuggestedBytes. DEFAULT_SUGGESTION_CONFIG returns up to 10 suggestions
above 0.5 confidence.
OfflineManagerConfig — optional queue / storage / cleanup / versioning
/ network / suggestions / storagePath, verifyChecksums, resumeOnStart.
DEFAULT_OFFLINE_MANAGER_CONFIG verifies checksums and resumes incomplete
downloads on start.
9.4 Suggestion Scoring#
The suggestion engine combines eight behavioral signals into a single confidence score for each candidate content item. The weights are configurable — lower them to deprioritize a signal, or raise them to make it more influential.
ScoringWeights — DEFAULT_SCORING_WEIGHTS: frequentlyUsed 1.0,
favorites 0.9, courseProgress 0.85, similarContent 0.7, popularity 0.6,
newContent 0.5, preferencesMatch 0.4, smallSize 0.3. DownloadSuggestion
— content, reason: SuggestionReason, confidence (0–1), explanation,
suggestedPriority.
9.5 Primary Exports#
// OfflineManager — extends EventEmitter<OfflineManagerEvents>
class OfflineManager {
initialize(): Promise<void>;
setDownloadHandler/setContentVerifier/setFileDeleter/setManifestFetch;
setNetworkPreferences/setCatalog/setUserPreferences/updateNetworkState;
download(content, priority?): ...; downloadMultiple(...);
cancelDownload/pauseDownload/resumeDownload/pauseAllDownloads/resumeAllDownloads;
clearDownloadQueue; getQueueState; getDownloadProgress; isDownloading;
isAvailableOffline; getOfflineContent; getAllOfflineContent;
removeOfflineContent/removeMultipleOfflineContent;
getStorageInfo; cleanupStorage; cleanupExpired;
checkForUpdates; checkContentUpdate; updateContent; getRequiredUpdates;
getSuggestions; downloadSuggested; saveState; shutdown; clearAll;
}
// createOfflineManager(config?), createBrowserOfflineManager()
// detectNetworkState(), detectNetworkType()
// DownloadQueue (createDownloadQueue), StorageManager
// Storage backends: InMemoryStorage, LocalStorageBackend, IndexedDBStorage
// (createInMemoryStorageManager / createLocalStorageManager /
// createIndexedDBStorageManager)
// ContentVersionManager + version utilities: createVersion, incrementVersion,
// isValidVersion, sortVersions, getLatestVersion, filterVersionRange
// SuggestionEngine (createSuggestionEngine), getQuickSuggestions
// React hook factories
createOfflineHooks: createUseOfflineContent, createUseOfflineContentById,
createUseAllOfflineContent, createUseDownloadProgress, createUseNetworkState,
createUseQueueState, createUseStorageInfo, createUseSuggestions
createBrowserOfflineManager() wires an IndexedDB-backed storage manager and
browser network detection.
10. @oshun/meditation-analytics — Session Analytics#
A small, dependency-free package (no eventemitter3, no React) for
privacy-conscious event tracking. It is distinct from SessionAnalyticsManager
in the session package: that class computes local aggregate statistics, while
this package emits outbound events to external analytics backends.
AnalyticsEventName (11 values): session_started, session_paused,
session_resumed, session_completed, session_interrupted,
content_started, content_completed, timer_completed,
breathing_completed, download_completed, streak_updated.
AnalyticsEvent<TProperties> — name, occurredAt, anonymousId, optional
sessionId / contentId, properties.
AnalyticsProvider interface — optional identify, required track, optional
flush. MemoryAnalyticsProvider is an in-memory provider (exposes events
and identified, plus clear()) used for testing.
AnalyticsClientOptions — anonymousId, provider, optional enabled,
consentGranted, now, defaultProperties. AnalyticsClient gates all
tracking on enabled && consentGranted (setEnabled / setConsentGranted),
merges defaultProperties into every event, and exposes identify, track,
flush.
SessionSummaryInput / SessionSummary plus summarizeSession (computes
durationSeconds and a clamped completionRatio, validates time ordering) and
trackSessionSummary (emits session_completed or session_interrupted from a
summary). Tracking captures only behavioural fields — durations, completion
ratios, session type, IDs — and is keyed by an anonymousId, never personally
identifiable content.
11. Cross-Cutting Patterns#
The six engine packages share the implementation patterns described in this section. Understanding these once lets you reason about any of the six packages without re-learning the pattern for each.
11.1 Event-Driven Core#
Each engine package's primary class extends EventEmitter from eventemitter3
with a typed event map (MeditationPlayerEvents, TimerEvents,
ExerciseEvents, SessionManagerEvents, ProgressEvents,
OfflineManagerEvents). The meditation-analytics package does not use
eventemitter3.
11.2 Three-Tier Platform Abstraction#
Packages that touch platform APIs ship an abstract base class plus a Web implementation, a Noop implementation, and a factory. This makes every platform-dependent feature testable in Node.js (Noop), functional in the browser (Web), and extensible for React Native (extend the abstract base class in the consuming app).
The packages that use this pattern and what they abstract:
meditation-player—BackgroundAudioHandler/Web…/Noop…/createBackgroundAudioHandler;AudioSessionManager/Web…/Noop…/createAudioSessionManager.meditation-timer—TimerBackgroundHandler/Web…/Noop…/createTimerBackgroundHandler;HapticManager/WebHapticManager/NoopHapticManager/createHapticManager.
The breathing package uses a lighter VibrationAdapter interface with
createWebVibrationAdapter. React Native implementations are provided by the
consuming application by extending the abstract base classes — the factory
functions only choose between Web and Noop based on window / navigator /
navigator.vibrate detection.
11.3 Interchangeable Storage Backends#
All backends in a package implement the same storage interface. The table below maps each package to its available backends.
| Package | Backends |
|---|---|
| breathing | InMemorySessionStorage, LocalSessionStorage |
| session | InMemorySessionStorage, LocalSessionStorage, IndexedDBSessionStorage; schedulers add InMemorySchedulerStorage, LocalSchedulerStorage |
| progress | InMemoryProgressStorage, LocalProgressStorage |
| offline | InMemoryStorage, LocalStorageBackend, IndexedDBStorage |
| player | AudioCacheManager (IndexedDB) + ServiceWorkerCacheAdapter |
All backends implement the package's storage interface (SessionStorage,
ProgressStorage, OfflineStorage, SchedulerStorage, SessionStorage for
breathing history) so business logic is backend-agnostic.
11.4 React Hook Factory Pattern#
Player, timer, breathing, session, progress, and offline export createUse*
hook factories plus a combined create*Hooks factory. React is an optional peer
dependency: a setReactHooks registration function (re-exported under
package-specific aliases — setTimerReactHooks, setBreathingReactHooks,
setProgressReactHooks) registers useState / useEffect / useCallback /
useMemo / useRef once at app startup.
The three steps for using the hook factories in a React application are:
// 1. Register React hooks once at app startup
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { setReactHooks } from '@oshun/meditation-player';
setReactHooks({ useState, useEffect, useCallback, useMemo, useRef });
// 2. Either use the package's convenience hooks directly...
import { usePlayer } from '@oshun/meditation-player';
// 3. ...or build bound hooks from a factory:
const hooks = { useState, useEffect, useCallback, useMemo, useRef };
const { useBreathingExercise } = createBreathingHooks(hooks);
createBreathingHooks, createTimerHooks, and createPlayerHooks accept the
injected ReactHooks object as an argument. Breathing and progress also export
zero-argument convenience hooks (useBreathingExercise, useStreak, …) that
resolve hooks registered via their setReactHooks alias.
11.5 Branded Types#
meditation-core, meditation-session, meditation-progress, and
meditation-offline each define their own branded primitive types (SessionId,
DurationSeconds, ContentId, ByteSize, …) using the
T & { readonly __brand: '…' } pattern, preventing accidental misuse of raw
string / number values at compile time. meditation-player and
meditation-timer use plain type aliases (TrackId,
DurationSeconds = number) rather than brands.
12. Configuration Examples#
These short examples show the most common construction patterns. Each demonstrates selecting a storage backend and passing configuration overrides for a specific concern.
// Timer with a custom tick interval and drift correction
const timer = new MeditationTimer({
tickInterval: 1000,
driftCorrection: true,
autoStop: true,
config: {
duration: 600,
preparation: { enabled: true, duration: 5, voiceCountdown: false },
},
});
// Session persistence with IndexedDB and auto-save
const manager = createSessionManager({
storage: new IndexedDBSessionStorage(),
persistence: { autoSave: true, autoSaveIntervalMs: 5000 },
});
// Offline manager: IndexedDB storage, 3 concurrent downloads, Wi-Fi-only
const offline = createOfflineManager({
queue: { maxConcurrent: 3 },
network: { wifiOnly: true },
storage: { maxTotalBytes: (5 * 1024 * 1024 * 1024) as ByteSize },
verifyChecksums: true,
resumeOnStart: true,
});
13. Build and Test#
Running Tests and Builds#
# Per-library tests (Nx)
pnpm nx test @oshun/meditation-player
pnpm nx test @oshun/meditation-timer
# Build a library
pnpm nx build @oshun/meditation-session
# All meditation-domain libraries share the scope:meditation tag
pnpm nx run-many --target=test --projects=tag:scope:meditation
Every package configures Vitest via a vitest.config.ts and an @nx/vite:test
target. When Nx is blocked by duplicate worktree projects, run
npx vitest run <path> and npx tsc --noEmit from the library directory.
13.1 Test Coverage#
The table below maps each package to its spec files. When tracing a failing test
back to its source, match the spec filename to the package directory under
libs/meditation/<package>/src/.
| Package | Spec files |
|---|---|
| core | core.spec.ts |
| player | player.spec.ts, queue.spec.ts, mixer.spec.ts, playback-rate.spec.ts |
| timer | timer.spec.ts |
| breathing | exercise.spec.ts, haptics.spec.ts |
| session | manager.spec.ts, persistence.spec.ts, analytics.spec.ts, scheduler-crud.spec.ts, scheduler-ical.spec.ts, scheduler-recurrence.spec.ts, scheduler-triggers.spec.ts, scheduling-storage.spec.ts |
| progress | tracker.spec.ts |
| offline | manager.spec.ts, queue.spec.ts, storage.spec.ts, suggestions.spec.ts, versioning.spec.ts |
| analytics | analytics.spec.ts |
14. Acceptance Criteria#
A meditation library is considered correctly implemented when all eight of the following conditions are true. These criteria are designed to catch stub implementations — code that compiles but does not actually do what the specification requires.
- Every package builds with its configured Nx executor and
tsc --noEmitpasses. - The primary class of each engine package extends
eventemitter3and emits the documented typed events. - Built-in data is exactly as documented: 10 breathing patterns with the timings in §6.2, 12 timer presets in §5.6, 6 bell constants / 7 ambient constants / 3 ambient mixes in §5.2–§5.3, 26 achievements in §8.3, 26 milestones in §8.4.
- Playback rate stays inside the 0.85×–1.25× quality-preserving policy and
normalizePlaybackRatethrowsPlaybackRateRangeErroroutside it. - Storage backends are interchangeable through the per-package storage interface; switching backend does not change behaviour.
- React hook factories work without React installed until
setReactHooksis called; calling a convenience hook before registration throws a clear error. - Platform factories return a working Web implementation in the browser and a Noop implementation elsewhere; no platform-specific code runs server-side.
- Per-package Vitest suites pass.