Skip to content

Health-data 1.0: TypeScript, results instead of silent zeros - #5

Merged
YauhenBichel merged 1 commit into
mainfrom
feat/v1-typescript
Sep 13, 2026
Merged

YauhenBichel merged 1 commit into
mainfrom
feat/v1-typescript

Conversation

@YauhenBichel

Copy link
Copy Markdown
Member

This is the 1.0 redesign that followed the critical review, using the same setup as scale-reference 1.0 (rn-scale-reference#3). Nothing is published yet, so this is the cheapest time to break the API.

Problems in 0.2.0 this fixes

Problem 0.2.0 1.0
Failures looked like data 16 catch blocks returned 0, [] or null, so "no permission" read as "no steps" Every read resolves to HealthResult<T>: {ok: true, value} or {ok: false, error: {code, message, cause?}}. Codes: unsupported_platform, module_missing, unavailable, not_permitted, native_error.
Android reads never started Health Connect Only requestPermissions called initialize(), so reads before it in an app session were rejected with CLIENT_NOT_INITIALIZED and came back as 0 Every read calls initialize() and checks granted permissions first. Missing access is not_permitted, and nothing is read.
Platform shapes leaked requestPermissions returned a HealthKit result, a Health Connect list or null; getSleepData returned raw platform samples PermissionStatus ({known: true, granted, denied} on Android, {known: false, requested} on iOS, because HealthKit never reveals read access). Sleep stages use the same names on both platforms.
Availability was a boolean false for every reason 'available' | 'not_installed' | 'update_required' | 'unsupported_platform' | 'module_missing'
"Today" meant the device clock new Date() in 8 places A now option, and every day-based method takes an optional day
Class, logging, no types, raw source HealthDataService with console.warn; ESM source that Node, default Jest and TypeScript could not load createHealthData(options) returns a frozen client and logs nothing. TypeScript with the builder-bob dual build and an exports map.

Kept from 0.x: calendar-day arithmetic (the daylight-saving fix from #4, with its tests ported), Health Connect aggregates for steps and water, paging, time asleep only with overlaps merged, and the Metro platform split so iOS-only apps don't need react-native-health-connect. builder-bob keeps that import extensionless in lib/, which I checked in bob's source and in the build output.

The full rename table is in CHANGELOG.md. Peer ranges are now bounded: react-native-health ^1.19.0 and react-native-health-connect >=3.5.0 <5. The 4.0 release notes list only Expo packaging as breaking, and the APIs used here are unchanged.

Quality gates

These are the same as scale-reference 1.0:

  • ESLint (typescript-eslint strict type-checked, with the unsafe-any rules relaxed for Jest mocks only), Prettier and tsc
  • Jest at a 100% coverage threshold, with the suite pinned to Europe/London
  • publint and arethetypeswrong on a fresh build
  • the tarball allowlist
  • the six-package-manager consumer matrix. The consumer gets a stand-in react-native, and getAvailability must answer module_missing.

How it was checked (locally)

  • ESLint, Prettier and tsc are clean. 51 tests pass at 100% statements, branches, functions and lines. They cover:

    • every error code on both platforms, including a missing native library and Health Connect rejection codes
    • permission status, availability, unit and field names, paging, and sleep stage mapping
    • daylight saving (ported from Count daily steps by calendar day across clock changes #4), invalid arguments, frozen options, and no module-level variables
  • From a clean copy (no lib/): publint --strict passes and attw --pack finds no problems (node10, node16 CJS, node16 ESM, bundler). The tarball allowlist passes on the 81-file package.

  • The six-package-manager check found a real limit, now handled and documented:

    • require worked with all six, but a plain-Node import failed with ERR_MODULE_NOT_FOUND on ./healthConnect.
    • That import is extensionless on purpose, so bundlers can choose healthConnect.ios.js, and Node's ES module loader can't do that.
    • A native health library never runs in plain Node. So the check now bundles the ES module build with esbuild: the iOS bundle picks healthConnect.ios.js and contains no require("react-native-health-connect"), and the Android bundle does.
    • The README says Node is supported through require, and the ES module build is for bundlers.
  • Consumer check, run locally against the packed tarball:

    • npm passes all four checks: require, the iOS and Android esbuild bundles, a default-settings Jest project, and strict TypeScript in node16 (CommonJS and ESM files) and bundler resolution.
    • Yarn 1, Yarn 4 Plug'n'Play, Yarn 4 node-modules and pnpm install the package and load it with require.
    • Bun is checked by the CI matrix on this PR.

For the app

The app uses the 0.x method names through app/service/HealthDataService.js. After merge, an app PR will move that adapter to 1.0. The plan:

  • keep the screens' calls working
  • create the client once
  • call requestPermissions on iOS before reading
  • decide per screen what to show for not_permitted rather than a silent 0

🤖 Generated with Claude Code

- createHealthData(options) replaces the HealthDataService class; frozen,
  no state, injectable clock.
- Every read returns HealthResult: ok with a value, or an error code
  (unsupported_platform, module_missing, unavailable, not_permitted,
  native_error) instead of 0, [] or null.
- Android reads start Health Connect and check read access first; in 0.x
  reads before requestPermissions failed as CLIENT_NOT_INITIALIZED and
  came back as 0.
- Same shapes on both platforms: named sleep stages, PermissionStatus
  (unknown on iOS), availability reasons, durationMinutes.
- Built with react-native-builder-bob (CommonJS, ES modules, types,
  exports map); Metro platform split kept for iOS-only apps.
- CI: lint, types, 100% coverage, publint, arethetypeswrong, and the
  six-package-manager consumer check with iOS and Android bundles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@YauhenBichel
YauhenBichel merged commit 16cbfb0 into main Sep 13, 2026
8 checks passed
@YauhenBichel
YauhenBichel deleted the feat/v1-typescript branch September 13, 2026 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant