npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@molecule/api-wearable

v1.0.2

Published

Wearable cloud-API ingestion core for molecule.dev — normalized contract for Fitbit, Oura, Withings, and other consumer wearable providers

Readme

@molecule/api-wearable

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Wearable core interface for molecule.dev (server-side).

Defines a stack-neutral, provider-neutral contract for wearable cloud-API bonds (Fitbit, Oura, Withings, Garmin, etc.) consumed by handlers and background sync jobs. Wearable bonds register as a named multi-provider — multiple wearable providers may be active for a single user — so wiring uses the named bond API: bond('wearable', 'fitbit', provider).

Quick Start

import { setProvider, getProvider } from '@molecule/api-wearable'
import { createProvider as createFitbit } from '@molecule/api-wearable-fitbit'

setProvider('fitbit', createFitbit({ credentialsStore, redirectUri: '...' }))

const fitbit = getProvider('fitbit')
const today = await fitbit.getDailyActivity('user-1', '2026-05-01')

Type

core

Installation

npm install @molecule/api-wearable @molecule/api-bond @molecule/api-i18n

API

Interfaces

DailyActivity

Daily activity rollup. All fields are zero-defaulted by the bond when the provider does not return a value, so handlers can sum across days without null-checks.

interface DailyActivity {
  /** Calendar day this rollup covers (`YYYY-MM-DD`). */
  date: WearableDate
  /** Total step count for the day. */
  steps: number
  /** Total distance traveled, in meters. */
  distanceMeters: number
  /** Total calories burned (active + BMR), in kilocalories. */
  caloriesOut: number
  /** Active minutes (provider-specific intensity definitions). */
  activeMinutes: number
  /** Floors climbed, when reported. */
  floors?: number
  /** Total elevation gain, in meters, when reported. */
  elevationMeters?: number
  /** Resting heart-rate, in beats per minute, when reported. */
  restingHeartRate?: number
}

HeartRateSummary

Daily heart-rate summary.

interface HeartRateSummary {
  /** Calendar day this summary covers (`YYYY-MM-DD`). */
  date: WearableDate
  /** Resting heart-rate, in beats per minute, when reported. */
  restingHeartRate?: number
  /** Per-zone breakdown, when reported. */
  zones?: HeartRateZone[]
}

HeartRateZone

Heart-rate zone definition (rest/fat-burn/cardio/peak or provider-named).

interface HeartRateZone {
  /** Zone label as reported by the provider. */
  name: string
  /** Lower bound of the zone, in beats per minute (inclusive). */
  minBpm: number
  /** Upper bound of the zone, in beats per minute (exclusive). */
  maxBpm: number
  /** Minutes spent in the zone for this period. */
  minutes: number
  /** Calories burned in the zone, in kilocalories. */
  caloriesOut?: number
}

SleepSession

One sleep session. Most users have exactly one per night, but providers report naps as separate sessions, so handlers must sum across the array.

interface SleepSession {
  /** Provider-specific session id. */
  id: string
  /** Calendar day this session is bucketed under (`YYYY-MM-DD`). */
  date: WearableDate
  /** ISO 8601 sleep start. */
  start: string
  /** ISO 8601 sleep end. */
  end: string
  /** Total time in bed, in minutes. */
  timeInBedMinutes: number
  /** Total time asleep, in minutes (excludes `awake` segments). */
  timeAsleepMinutes: number
  /** Sleep efficiency percentage (0-100). */
  efficiency?: number
  /** Whether this session is the user's primary sleep for the day. */
  isMainSleep: boolean
  /** Per-stage minute totals when the provider reports stages. */
  stageSummary?: SleepStageSummary
  /** Per-segment stage breakdown when the provider reports stages. */
  segments?: SleepStageSegment[]
}

SleepStageSegment

A contiguous block of a single sleep stage within a sleep session.

interface SleepStageSegment {
  /** Normalized stage. */
  stage: SleepStage
  /** ISO 8601 segment start. */
  start: string
  /** ISO 8601 segment end. */
  end: string
  /** Segment duration in seconds (provider-reported when available). */
  durationSeconds: number
}

SleepStageSummary

Per-stage totals for a sleep session, in minutes. Optional — providers that only report the coarse "asleep" classification will omit light/deep/rem.

interface SleepStageSummary {
  /** Minutes spent awake during the session. */
  awakeMinutes?: number
  /** Minutes in light sleep. */
  lightMinutes?: number
  /** Minutes in deep sleep. */
  deepMinutes?: number
  /** Minutes in REM sleep. */
  remMinutes?: number
  /** Minutes restless (legacy classifications). */
  restlessMinutes?: number
}

UserConnection

Per-user OAuth tokens minted by {@link WearableProvider.connect} and rotated by {@link WearableProvider.refreshConnection}. Bonds persist these via a caller-supplied {@link WearableCredentialsStore}.

Token strings are NEVER thrown, logged, or echoed back in error messages — implementations must sanitize all error paths.

interface UserConnection {
  /** The user owning the connection in the host application. */
  userId: string
  /** Provider-specific account identifier (e.g. Fitbit `user_id`). */
  providerAccountId: string
  /** Current access token. */
  accessToken: string
  /** Refresh token used to mint new access tokens. */
  refreshToken: string
  /** Optional epoch-millis timestamp at which the access token expires. */
  expiresAt?: number
  /** Granted OAuth scopes (provider-specific names). */
  scopes?: string[]
  /** Epoch-millis timestamp at which the connection was first established. */
  connectedAt: number
}

WearableCredentialsStore

Persistence contract for {@link UserConnection} records. Implementations are responsible for at-rest encryption (refresh tokens are bearer credentials and MUST be stored securely).

The same store can back any number of provider bonds (Fitbit, Oura, Withings, etc.) — segregation is by (userId, providerName) pair.

interface WearableCredentialsStore {
  /**
   * Looks up the connection for `(userId, providerName)`.
   *
   * @param userId - Host-app user identifier.
   * @param providerName - Provider key, e.g. `"fitbit"`.
   * @returns The stored connection or `null` if none.
   */
  read(userId: string, providerName: string): Promise<UserConnection | null>
  /**
   * Persists a new or refreshed connection.
   *
   * @param providerName - Provider key, e.g. `"fitbit"`.
   * @param connection - The connection record to write.
   */
  write(providerName: string, connection: UserConnection): Promise<void>
  /**
   * Deletes the connection for `(userId, providerName)`.
   *
   * @param userId - Host-app user identifier.
   * @param providerName - Provider key.
   */
  remove(userId: string, providerName: string): Promise<void>
}

WearableDateRange

Inclusive date range.

interface WearableDateRange {
  /** Lower-bound calendar day (`YYYY-MM-DD`), inclusive. */
  start: WearableDate
  /** Upper-bound calendar day (`YYYY-MM-DD`), inclusive. */
  end: WearableDate
}

WearableProvider

Wearable provider contract.

Each method takes a host-app userId rather than per-call credentials. Implementations look up persisted {@link UserConnection} records via the caller-supplied {@link WearableCredentialsStore}, transparently refresh expired access tokens (writing the rotated record back to the store), and surface refreshes as a side-effect of normal data calls.

Token-bearing values (access tokens, refresh tokens, Authorization headers, OAuth error_description payloads that may include the token) MUST NEVER be thrown, logged, or returned in error messages — every error path must be sanitized.

interface WearableProvider {
  /** Stable provider key used for credential-store segregation, e.g. `"fitbit"`. */
  readonly providerName: string

  /**
   * Reads the daily activity rollup for `(userId, date)`.
   *
   * @param userId - Host-app user identifier.
   * @param date - Calendar day (`YYYY-MM-DD`).
   * @returns Normalized daily activity.
   */
  getDailyActivity(userId: string, date: WearableDate): Promise<DailyActivity>

  /**
   * Reads sleep sessions bucketed under `(userId, date)`. Most days return
   * a single primary session; multi-nap days return multiple.
   *
   * @param userId - Host-app user identifier.
   * @param date - Calendar day (`YYYY-MM-DD`).
   * @returns All sleep sessions bucketed under that day.
   */
  getDailySleep(userId: string, date: WearableDate): Promise<SleepSession[]>

  /**
   * Reads the daily heart-rate summary for `(userId, date)`.
   *
   * @param userId - Host-app user identifier.
   * @param date - Calendar day (`YYYY-MM-DD`).
   * @returns Normalized daily heart-rate summary.
   */
  getDailyHeartRate(userId: string, date: WearableDate): Promise<HeartRateSummary>

  /**
   * Reads body-weight entries for the user across `range`. Providers cap
   * how far back a single call may reach — implementations should clamp
   * `range` to the provider's documented maximum and return only the
   * available entries.
   *
   * @param userId - Host-app user identifier.
   * @param range - Inclusive calendar-day range.
   * @returns Weight entries, ordered by `recordedAt` ascending.
   */
  getWeight(userId: string, range: WearableDateRange): Promise<WeightEntry[]>

  /**
   * Exchanges an OAuth authorization `code` for tokens and persists the
   * resulting {@link UserConnection} via the bond's credentials store.
   *
   * @param userId - Host-app user identifier (the local user accepting the link).
   * @param code - Authorization code from the OAuth redirect callback.
   * @returns The freshly-minted connection (already persisted).
   */
  connect(userId: string, code: string): Promise<UserConnection>

  /**
   * Forces a refresh of the user's access token using the stored refresh
   * token. The rotated connection is persisted before being returned.
   *
   * @param userId - Host-app user identifier.
   * @returns The rotated connection (already persisted).
   */
  refreshConnection(userId: string): Promise<UserConnection>

  /**
   * Revokes (best-effort) and removes the user's connection record.
   *
   * Implementations SHOULD attempt to revoke the token at the provider but
   * MUST always remove the local record even if revocation fails — leaking
   * a record after `disconnect` is worse than a stranded provider-side
   * token.
   *
   * @param userId - Host-app user identifier.
   */
  disconnect(userId: string): Promise<void>
}

WeightEntry

One body-weight reading.

interface WeightEntry {
  /** ISO 8601 timestamp at which the reading was taken. */
  recordedAt: string
  /** Calendar day of the reading (`YYYY-MM-DD`). */
  date: WearableDate
  /** Weight in kilograms. */
  weightKg: number
  /** Body-fat percentage (0-100), when reported. */
  bodyFatPercent?: number
  /** BMI (kg / m²), when reported. */
  bmi?: number
  /** Provider-specific entry id. */
  id?: string
}

Types

SleepStage

Sleep stage taxonomy normalized across providers.

  • awake — periods awake during a sleep session
  • light, deep, rem — modern stage classifications
  • restless, asleep — legacy/coarse classifications used by some providers
  • unknown — fallback for unmapped values
type SleepStage = 'awake' | 'light' | 'deep' | 'rem' | 'restless' | 'asleep' | 'unknown'

WearableDate

Calendar-day identifier in YYYY-MM-DD format. Wearable providers universally bucket activity/sleep/HR data by local-day, so the core exchanges date strings (not absolute timestamps) for per-day reads.

type WearableDate = string

Functions

getOptionalProvider(name)

Retrieves the bonded wearable provider for name, or null when none is bonded. Use this when a missing connection is a normal state (e.g. the user hasn't linked a Fitbit account yet).

function getOptionalProvider(name: string): WearableProvider | null
  • name — Provider key.

Returns: The bonded provider, or null.

getProvider(name)

Retrieves the bonded wearable provider for name, throwing if none is configured.

function getProvider(name: string): WearableProvider
  • name — Provider key, e.g. 'fitbit'.

Returns: The bonded wearable provider.

hasProvider(name)

Checks whether a wearable provider is bonded under name.

function hasProvider(name: string): boolean
  • name — Provider key.

Returns: true if bonded.

listProviders()

Lists every currently-bonded wearable provider name (e.g. ['fitbit', 'oura']). Useful for rendering a "linked accounts" settings page or fanning a sync job out across all bonded providers.

function listProviders(): string[]

Returns: The list of bonded provider keys.

setProvider(name, provider)

Registers a wearable provider under the given name (e.g. 'fitbit'). Bond packages call this during application startup.

function setProvider(name: string, provider: WearableProvider): void
  • name — Stable provider key, matching {@link WearableProvider.providerName}.
  • provider — The wearable provider implementation to bond.

Available Providers

| Provider | Package | | --------- | ------------------------------- | | Fitbit | @molecule/api-wearable-fitbit | | Oura Ring | @molecule/api-wearable-oura |

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-bond ^1.0.1
  • @molecule/api-i18n ^1.0.1

Runtime Dependencies

  • @molecule/api-bond
  • @molecule/api-i18n

E2E Tests

Integration checklist — drive the real UI (live preview), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip. CAVEAT: the device vendor's OAuth consent screen and real sensor data can't be driven in the sandbox, so verify the connection lifecycle + the data mapping/display you own by wiring a stub/test provider (or feeding a sample payload) where the real grant/sync would occur — never mock the app's own handlers or UI:

  • [ ] Connecting a device from the UI runs the provider's connect(userId, code), which exchanges the OAuth code and persists a UserConnection via the WearableCredentialsStore (store.write), keyed by (userId, providerName). Afterward store.read(userId, name) returns that connection and the device shows "connected"; disconnect(userId) removes the record and the UI returns to the unlinked state.
  • [ ] Fetching a metric for a real date renders plausible, in-range values in a chart/summary: getDailyActivity().steps in the thousands, getDailyHeartRate() restingHeartRate ~40-200 bpm, getDailySleep() timeAsleepMinutes a sane number of hours, getWeight() weightKg a human bodyweight — never null/NaN/negative.
  • [ ] Different days/ranges return different data and the dates line up with no off-by-one/timezone shift: each rollup's date (a YYYY-MM-DD WearableDate) equals the day requested, and every getWeight(range) entry's date falls within range.start..range.end inclusive.
  • [ ] A day the device wasn't worn/synced shows as a GAP, not a celebrated zero. The core zero-defaults DailyActivity (a missing day comes back as steps: 0, activeMinutes: 0), so the UI must distinguish "no data" from a real 0 and never present an unsynced day as "0 steps achieved".
  • [ ] This core is pull-based — the WearableProvider interface has no webhook method; data is read on demand via the getDaily*/getWeight calls. If a provider bond wires a sync/subscription callback, delivering a valid callback updates the stored data and a forged/unsigned callback is rejected.
  • [ ] PRIVACY/SECURITY — health data is per-user: every getDaily*/getWeight call is scoped by the caller's authenticated userId and the store is segregated by (userId, providerName), so no id-guessing reaches another user's metrics. Device tokens (accessToken/refreshToken) and provider keys stay server-side (the package is server-only) and are never logged in the clear — error paths are sanitized so no token or health data leaks into logs.