From 6b12e68c108403d3c8ba0cc69cb221918e17507c Mon Sep 17 00:00:00 2001 From: Piotr Kuczynski Date: Mon, 15 Jun 2026 17:52:19 +0200 Subject: [PATCH 1/2] fix(generator): read HKWorkoutActivityType from relocated header (iOS 27) + verify guard --- .../scripts/generate-healthkit.ts | 5 ++++- .../scripts/healthkit-sdk.ts | 20 +++++++++++++++++++ .../scripts/verify-healthkit-sdk.ts | 14 +++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/packages/react-native-healthkit/scripts/generate-healthkit.ts b/packages/react-native-healthkit/scripts/generate-healthkit.ts index b58452db..865d56b9 100644 --- a/packages/react-native-healthkit/scripts/generate-healthkit.ts +++ b/packages/react-native-healthkit/scripts/generate-healthkit.ts @@ -1049,12 +1049,15 @@ export function buildHealthkitSchemaFromSources(sources: { readonly metadataHeader: string readonly metadataEnumsHeader: string readonly workoutHeader: string + readonly workoutActivityTypeHeader: string }): HealthkitSchema { const enumsByName = new Map() for (const enumSchema of [ ...parseNsEnums(sources.categoryValuesHeader), ...parseNsEnums(sources.metadataEnumsHeader), - ...parseNsEnums(sources.workoutHeader).filter( + ...parseNsEnums( + `${sources.workoutHeader}\n${sources.workoutActivityTypeHeader}`, + ).filter( (schema) => schema.name === 'WorkoutActivityType' || schema.name === 'WorkoutEventType', diff --git a/packages/react-native-healthkit/scripts/healthkit-sdk.ts b/packages/react-native-healthkit/scripts/healthkit-sdk.ts index 0e2140bc..c18ee64d 100644 --- a/packages/react-native-healthkit/scripts/healthkit-sdk.ts +++ b/packages/react-native-healthkit/scripts/healthkit-sdk.ts @@ -1,5 +1,6 @@ import { execFileSync } from 'node:child_process' import { + existsSync, mkdirSync, mkdtempSync, readFileSync, @@ -47,6 +48,7 @@ export interface HealthkitSdkSources { readonly metadataHeader: string readonly metadataEnumsHeader: string readonly workoutHeader: string + readonly workoutActivityTypeHeader: string } export interface GeneratedArtifactPaths { @@ -110,6 +112,20 @@ function readHealthkitHeader(relativePath: string, sdkPath: string): string { ) } +// Some enums (e.g. HKWorkoutActivityType, relocated into its own header in newer +// SDKs) only exist in certain SDK versions; missing headers read as empty. +function readOptionalHealthkitHeader( + relativePath: string, + sdkPath: string, +): string { + const headerPath = join( + sdkPath, + 'System/Library/Frameworks/HealthKit.framework', + relativePath, + ) + return existsSync(headerPath) ? readFileSync(headerPath, 'utf8') : '' +} + function readHealthkitSymbolGraph(sdkPath: string): SymbolGraphDocument { const outputDir = mkdtempSync(join(tmpdir(), 'healthkit-symbolgraph-')) @@ -161,6 +177,10 @@ export function loadHealthkitSdkSources( sdkPath, ), workoutHeader: readHealthkitHeader('Headers/HKWorkout.h', sdkPath), + workoutActivityTypeHeader: readOptionalHealthkitHeader( + 'Headers/HKWorkoutActivityType.h', + sdkPath, + ), } } diff --git a/packages/react-native-healthkit/scripts/verify-healthkit-sdk.ts b/packages/react-native-healthkit/scripts/verify-healthkit-sdk.ts index 46f3732b..3d3e3e4f 100644 --- a/packages/react-native-healthkit/scripts/verify-healthkit-sdk.ts +++ b/packages/react-native-healthkit/scripts/verify-healthkit-sdk.ts @@ -45,6 +45,20 @@ function main() { 'Blood glucose should retain canonical unit mapping', ) + // Regression guard: iOS 27 relocated HKWorkoutActivityType into its own header, + // so a generator that only reads HKWorkout.h drops the whole enum. + const workoutActivityType = schema.enums.find( + (schemaEnum) => schemaEnum.name === 'WorkoutActivityType', + ) + assert.ok( + workoutActivityType, + 'WorkoutActivityType enum should be present regardless of which header defines it', + ) + assert.ok( + workoutActivityType.members.length > 50, + 'WorkoutActivityType should retain its full member set, not a stub', + ) + const workoutBrandName = findMetadataKey( schema, 'HKMetadataKeyWorkoutBrandName', From 583ea6dd6ffbc55c9a3681c45faffd6fe8d146ee Mon Sep 17 00:00:00 2001 From: Piotr Kuczynski Date: Mon, 15 Jun 2026 17:52:36 +0200 Subject: [PATCH 2/2] feat: add iOS 27 MenopausalState + BleedingAfterMenopause category types --- .changeset/ios27-menopause-types.md | 5 ++ .../src/generated/healthkit-schema.json | 55 +++++++++++++++++-- .../src/generated/healthkit.generated.ts | 16 ++++++ 3 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 .changeset/ios27-menopause-types.md diff --git a/.changeset/ios27-menopause-types.md b/.changeset/ios27-menopause-types.md new file mode 100644 index 00000000..06cccd2b --- /dev/null +++ b/.changeset/ios27-menopause-types.md @@ -0,0 +1,5 @@ +--- +"@kingstinct/react-native-healthkit": minor +--- + +Add iOS 27 category types `HKCategoryTypeIdentifierMenopausalState` and `HKCategoryTypeIdentifierBleedingAfterMenopause`. Fix the schema generator to read `HKWorkoutActivityType` from its relocated header (`HKWorkoutActivityType.h`) on iOS 27 SDKs — where it moved out of `HKWorkout.h` — and add a verify guard so the enum can't silently drop again. diff --git a/packages/react-native-healthkit/src/generated/healthkit-schema.json b/packages/react-native-healthkit/src/generated/healthkit-schema.json index c858935e..bf11a51a 100644 --- a/packages/react-native-healthkit/src/generated/healthkit-schema.json +++ b/packages/react-native-healthkit/src/generated/healthkit-schema.json @@ -156,7 +156,7 @@ "name": "HKQuantityTypeIdentifierCyclingCadence", "ios": "17.0", "canonicalUnit": "count/min", - "aggregationStyle": "Discrete (Arithmetic)", + "aggregationStyle": "Discrete (Temporally Weighted)", "writeable": true, "legacy": false }, @@ -172,7 +172,7 @@ "name": "HKQuantityTypeIdentifierCyclingPower", "ios": "17.0", "canonicalUnit": "W", - "aggregationStyle": "Discrete (Arithmetic)", + "aggregationStyle": "Discrete (Temporally Weighted)", "writeable": true, "legacy": false }, @@ -180,7 +180,7 @@ "name": "HKQuantityTypeIdentifierCyclingSpeed", "ios": "17.0", "canonicalUnit": "m/s", - "aggregationStyle": "Discrete (Arithmetic)", + "aggregationStyle": "Discrete (Temporally Weighted)", "writeable": true, "legacy": false }, @@ -772,7 +772,7 @@ "name": "HKQuantityTypeIdentifierRestingHeartRate", "ios": "11.0", "canonicalUnit": "count/min", - "aggregationStyle": "Discrete (Arithmetic)", + "aggregationStyle": "Discrete (Temporally Weighted)", "writeable": true, "legacy": false }, @@ -924,7 +924,7 @@ "name": "HKQuantityTypeIdentifierWalkingHeartRateAverage", "ios": "11.0", "canonicalUnit": "count/min", - "aggregationStyle": "Discrete (Arithmetic)", + "aggregationStyle": "Discrete (Temporally Weighted)", "writeable": false, "legacy": false }, @@ -1011,6 +1011,13 @@ "writeable": true, "legacy": false }, + { + "name": "HKCategoryTypeIdentifierBleedingAfterMenopause", + "ios": "27.0", + "valueEnum": "HKCategoryValueVaginalBleeding", + "writeable": true, + "legacy": false + }, { "name": "HKCategoryTypeIdentifierBleedingAfterPregnancy", "ios": "18.0", @@ -1270,6 +1277,13 @@ "writeable": true, "legacy": false }, + { + "name": "HKCategoryTypeIdentifierMenopausalState", + "ios": "27.0", + "valueEnum": "HKCategoryValueMenopausalState", + "writeable": true, + "legacy": false + }, { "name": "HKCategoryTypeIdentifierMenstrualFlow", "ios": "9.0", @@ -1747,6 +1761,27 @@ } ] }, + { + "name": "CategoryValueMenopausalState", + "ios": "27.0", + "members": [ + { + "name": "menopause", + "swiftName": "HKCategoryValueMenopausalStateMenopause", + "rawValue": 1 + }, + { + "name": "perimenopause", + "swiftName": "HKCategoryValueMenopausalStatePerimenopause", + "rawValue": 2 + }, + { + "name": "none", + "swiftName": "HKCategoryValueMenopausalStateNone", + "rawValue": 3 + } + ] + }, { "name": "CategoryValueMenstrualFlow", "ios": "9.0", @@ -2806,6 +2841,16 @@ "swiftName": "HKWorkoutActivityTypeUnderwaterDiving", "rawValue": 84 }, + { + "name": "rest", + "swiftName": "HKWorkoutActivityTypeRest", + "rawValue": 2998 + }, + { + "name": "group", + "swiftName": "HKWorkoutActivityTypeGroup", + "rawValue": 2999 + }, { "name": "other", "swiftName": "HKWorkoutActivityTypeOther", diff --git a/packages/react-native-healthkit/src/generated/healthkit.generated.ts b/packages/react-native-healthkit/src/generated/healthkit.generated.ts index a2935ff8..ef0e0340 100644 --- a/packages/react-native-healthkit/src/generated/healthkit.generated.ts +++ b/packages/react-native-healthkit/src/generated/healthkit.generated.ts @@ -153,6 +153,7 @@ export type CategoryTypeIdentifierWriteable = | 'HKCategoryTypeIdentifierAppleWalkingSteadinessEvent' | 'HKCategoryTypeIdentifierAudioExposureEvent' | 'HKCategoryTypeIdentifierBladderIncontinence' + | 'HKCategoryTypeIdentifierBleedingAfterMenopause' | 'HKCategoryTypeIdentifierBleedingAfterPregnancy' | 'HKCategoryTypeIdentifierBleedingDuringPregnancy' | 'HKCategoryTypeIdentifierBloating' @@ -186,6 +187,7 @@ export type CategoryTypeIdentifierWriteable = | 'HKCategoryTypeIdentifierLowCardioFitnessEvent' | 'HKCategoryTypeIdentifierLowerBackPain' | 'HKCategoryTypeIdentifierMemoryLapse' + | 'HKCategoryTypeIdentifierMenopausalState' | 'HKCategoryTypeIdentifierMenstrualFlow' | 'HKCategoryTypeIdentifierMindfulSession' | 'HKCategoryTypeIdentifierMoodChanges' @@ -345,6 +347,7 @@ export const CATEGORY_IDENTIFIER_IOS_AVAILABILITY = { HKCategoryTypeIdentifierAppleWalkingSteadinessEvent: '15.0', HKCategoryTypeIdentifierAudioExposureEvent: '13.0', HKCategoryTypeIdentifierBladderIncontinence: '14.0', + HKCategoryTypeIdentifierBleedingAfterMenopause: '27.0', HKCategoryTypeIdentifierBleedingAfterPregnancy: '18.0', HKCategoryTypeIdentifierBleedingDuringPregnancy: '18.0', HKCategoryTypeIdentifierBloating: '13.6', @@ -382,6 +385,7 @@ export const CATEGORY_IDENTIFIER_IOS_AVAILABILITY = { HKCategoryTypeIdentifierLowerBackPain: '13.6', HKCategoryTypeIdentifierLowHeartRateEvent: '12.2', HKCategoryTypeIdentifierMemoryLapse: '14.0', + HKCategoryTypeIdentifierMenopausalState: '27.0', HKCategoryTypeIdentifierMenstrualFlow: '9.0', HKCategoryTypeIdentifierMindfulSession: '10.0', HKCategoryTypeIdentifierMoodChanges: '13.6', @@ -540,6 +544,8 @@ export const CATEGORY_IDENTIFIER_VALUE_ENUMS = { 'CategoryValueAppleWalkingSteadinessEvent', HKCategoryTypeIdentifierAudioExposureEvent: null, HKCategoryTypeIdentifierBladderIncontinence: 'CategoryValueSeverity', + HKCategoryTypeIdentifierBleedingAfterMenopause: + 'CategoryValueVaginalBleeding', HKCategoryTypeIdentifierBleedingAfterPregnancy: 'CategoryValueVaginalBleeding', HKCategoryTypeIdentifierBleedingDuringPregnancy: @@ -583,6 +589,7 @@ export const CATEGORY_IDENTIFIER_VALUE_ENUMS = { HKCategoryTypeIdentifierLowerBackPain: 'CategoryValueSeverity', HKCategoryTypeIdentifierLowHeartRateEvent: 'CategoryValue', HKCategoryTypeIdentifierMemoryLapse: 'CategoryValueSeverity', + HKCategoryTypeIdentifierMenopausalState: 'CategoryValueMenopausalState', HKCategoryTypeIdentifierMenstrualFlow: 'CategoryValueMenstrualFlow', HKCategoryTypeIdentifierMindfulSession: 'CategoryValue', HKCategoryTypeIdentifierMoodChanges: 'CategoryValuePresence', @@ -683,6 +690,11 @@ export enum CategoryValueHeadphoneAudioExposureEvent { export enum CategoryValueLowCardioFitnessEvent { lowFitness = 1, } +export enum CategoryValueMenopausalState { + menopause = 1, + perimenopause = 2, + none = 3, +} export enum CategoryValueMenstrualFlow { unspecified = 1, light = 2, @@ -911,6 +923,8 @@ export enum WorkoutActivityType { swimBikeRun = 82, transition = 83, underwaterDiving = 84, + rest = 2998, + group = 2999, other = 3000, } export enum WorkoutEventType { @@ -935,6 +949,7 @@ export interface CategoryValueByIdentifierMap { readonly HKCategoryTypeIdentifierAppleStandHour: CategoryValueAppleStandHour readonly HKCategoryTypeIdentifierAppleWalkingSteadinessEvent: CategoryValueAppleWalkingSteadinessEvent readonly HKCategoryTypeIdentifierBladderIncontinence: CategoryValueSeverity + readonly HKCategoryTypeIdentifierBleedingAfterMenopause: CategoryValueVaginalBleeding readonly HKCategoryTypeIdentifierBleedingAfterPregnancy: CategoryValueVaginalBleeding readonly HKCategoryTypeIdentifierBleedingDuringPregnancy: CategoryValueVaginalBleeding readonly HKCategoryTypeIdentifierBloating: CategoryValueSeverity @@ -972,6 +987,7 @@ export interface CategoryValueByIdentifierMap { readonly HKCategoryTypeIdentifierLowerBackPain: CategoryValueSeverity readonly HKCategoryTypeIdentifierLowHeartRateEvent: CategoryValue readonly HKCategoryTypeIdentifierMemoryLapse: CategoryValueSeverity + readonly HKCategoryTypeIdentifierMenopausalState: CategoryValueMenopausalState readonly HKCategoryTypeIdentifierMenstrualFlow: CategoryValueMenstrualFlow readonly HKCategoryTypeIdentifierMindfulSession: CategoryValue readonly HKCategoryTypeIdentifierMoodChanges: CategoryValuePresence