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

@gj-kit/expo-workouts

v0.1.2

Published

HealthKit and Health Connect workout, GPS-route, authorization, and incremental sync bridge for Expo.

Readme

@gj-kit/expo-workouts

npm CI types included runtime dependencies: 0 license

English · 한국어

HealthKit and Health Connect workouts in Expo, where destroying stored data is a compile error.

Why this exists

A Health Connect upsert is full-state: a saveWorkout that omits the route deletes the route already stored, and a write with a lower version returns normally with the same record id, so nothing but a read-back can tell it apart from a real write. HealthKit never reports read authorization at all, so a scope you forgot to declare leaves distanceM undefined on every workout with no error anywhere. And a sync loop that commits its cursor before the rows it just received loses those workouts permanently.

What it does about it

  • Route omission is a compile error — WorkoutWrite.route is a required readonly RoutePoint[] | 'none', so omitting it is TS2741 — a Health Connect upsert without it deletes the route already stored.
  • Locked-device branch cannot be skipped — SaveResult is a union discriminated on status, so saved.nativeId is TS2339 until status === 'saved' narrows away the pendingUnlock branch a locked device produces.
  • Missing scopes named before reading — unpopulatedWorkoutMetrics(state) returns the Workout field names, distanceM among them, whose gating read scope is denied or never requested — before you read a single workout.
  • Safe to import anywhere — The node/browser condition routes to index.unsupported, whose built module graph contains no expo; in Expo Go requireOptionalNativeModule returns null, so import never throws and getAvailability() resolves unavailable.
  • Sync gaps reproduce on Node — createFakeWorkouts() replaces the NativeWorkoutsModule seam rather than WorkoutsApi, so real ./core code runs under vitest for all six CursorResetReason values and a crash mid-drain.

Golden path

Outcome: A native Expo app can check HealthKit or Health Connect availability and request workout access.

1. Install

pnpm add @gj-kit/expo-workouts

2. Keep the app-owned boundary explicit

Add the config plugin and use a development build; Expo Go and the web cannot call the native module.

3. Start with the smallest integration

Copy this first, then replace only the app-owned values named above.

import { getAvailability, requestAuthorization } from '@gj-kit/expo-workouts';

export async function requestWorkoutAccess() {
  const availability = await getAvailability();
  if (availability.status === 'available') {
    await requestAuthorization({ read: ['workouts'] });
  }
  return availability;
}

What that looks like

The two saveWorkout mistakes that only surface in production — a forgotten route and an unhandled locked device — are a TS2741 and a TS2339.

import { workouts } from '@gj-kit/expo-workouts';
import type { WorkoutWrite } from '@gj-kit/expo-workouts/core';

export const write: WorkoutWrite = {
  id: 'a2f6c0b8-0d7e-4f31-9d1a-7c2b5e8f0a11', // your own idempotency key, not the platform's
  version: 3, // never Date.now(): a crash retry would mint a second workout
  kind: 'running',
  startMs: 1_754_000_000_000,
  endMs: 1_754_000_600_000,
  route: 'none', // delete this line -> TS2741; an Android upsert without it erases the stored route
};

export async function save(): Promise<string | null> {
  const saved = await workouts.saveWorkout(write);
  // @ts-expect-error TS2339 — `nativeId` is absent on the `pendingUnlock` branch
  void saved.nativeId;
  if (saved.status === 'pendingUnlock') return null; // locked device: retry the same id + version
  return saved.nativeId; // narrowed past pendingUnlock, so it exists
}

Verified, not asserted

  • 460+ tests across unit, native and plugin
  • 33 @ts-expect-error guards
  • 0 runtime dependencies
  • 60 XCTest + 70 Kotlin tests on shared fixtures

Every code block on this page is type-checked against the published declarations before release; pnpm verify:release is the gate all ten packages share.

Use it when

Use it when an Expo app needs platform health data while retaining location collection, UI, and sync ownership.

Do not use it when

Do not use it for live GPS tracking, background location policy, or server-side health-data processing.

Runtime and peers

| Peer | Supported range | | --- | --- | | expo | >=56.0.0 <58.0.0 |

Public entry points

  • @gj-kit/expo-workouts
  • @gj-kit/expo-workouts/core
  • @gj-kit/expo-workouts/testing
  • @gj-kit/expo-workouts/plugin

Safety boundary

This is a native module: Expo Go and web/Node are intentionally unsupported for native calls. Explain health permissions before requesting them.

Documentation

The documentation references the npm-latest declaration snapshot. Use only documented public entry points; do not deep-import internal source files.

Release notes and support