@tryterra/react-native 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +31 -0
  2. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Conversions.kt +103 -14
  3. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/ReadConversions.kt +250 -0
  4. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeOpsImpl.kt +47 -5
  5. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeStreamsImpl.kt +1 -3
  6. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeOps.kt +89 -0
  7. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeTypes.kt +672 -0
  8. package/docs/availability.md +154 -4
  9. package/frameworks/TerraSDK.xcframework/Info.plist +5 -5
  10. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Info.plist +0 -0
  11. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.abi.json +1385 -226
  12. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.package.swiftinterface +37 -1
  13. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +37 -1
  14. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  15. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftinterface +37 -1
  16. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/TerraSDK +0 -0
  17. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +2 -2
  18. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
  19. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6589 -6468
  20. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Info.plist +0 -0
  21. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +1385 -226
  22. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.package.swiftinterface +37 -1
  23. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +37 -1
  24. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  25. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +37 -1
  26. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +1385 -226
  27. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.package.swiftinterface +37 -1
  28. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +37 -1
  29. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  30. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +37 -1
  31. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/TerraSDK +0 -0
  32. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/_CodeSignature/CodeResources +21 -21
  33. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +2 -2
  34. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
  35. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6590 -6469
  36. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/TerraSDK.yml +6508 -6387
  37. package/ios/TerraBridgeCore/Conversions+Reads.swift +259 -0
  38. package/ios/TerraBridgeCore/Conversions.swift +129 -2
  39. package/ios/TerraBridgeCore/Generated/BridgeOps.swift +172 -0
  40. package/ios/TerraBridgeCore/Generated/BridgeTypes.swift +734 -1
  41. package/ios/TerraBridgeCore/TerraBridgeOpsImpl.swift +64 -0
  42. package/lib/Terra.d.ts +74 -5
  43. package/lib/Terra.js +69 -13
  44. package/lib/generated/client.d.ts +37 -1
  45. package/lib/generated/client.js +60 -0
  46. package/lib/generated/codecs.d.ts +85 -1
  47. package/lib/generated/codecs.js +710 -2
  48. package/lib/generated/ops.d.ts +1 -1
  49. package/lib/generated/ops.js +24 -0
  50. package/lib/generated/types.d.ts +314 -1
  51. package/lib/generated/types.js +133 -1
  52. package/lib/index.d.ts +1 -1
  53. package/package.json +3 -3
  54. package/src/Terra.ts +146 -16
  55. package/src/generated/client.ts +74 -2
  56. package/src/generated/codecs.ts +705 -3
  57. package/src/generated/ops.ts +36 -0
  58. package/src/generated/types.ts +556 -1
  59. package/src/index.ts +2 -0
@@ -136,6 +136,13 @@ public enum DataCategory: String, Codable, Sendable, CaseIterable {
136
136
  case daily = "daily"
137
137
  case sleep = "sleep"
138
138
  case nutrition = "nutrition"
139
+ /// Read-only on both platforms: menstruation.flow reads it and neither SDK has a write route
140
+ /// for it.
141
+ case menstruation = "menstruation"
142
+ /// HealthKit's characteristics; Health Connect has no counterpart.
143
+ ///
144
+ /// Available on ios only.
145
+ case athlete = "athlete"
139
146
  }
140
147
 
141
148
  public enum PermissionStatus: String, Codable, Sendable, CaseIterable {
@@ -158,6 +165,90 @@ public enum MotionContext: String, Codable, Sendable, CaseIterable {
158
165
  case active = "active"
159
166
  }
160
167
 
168
+ /// On both platforms, wire names strip HKQuantityTypeIdentifierDietary from HealthKit
169
+ /// identifiers and lowercase the first letter. Units: kcal for energyConsumed; grams for
170
+ /// protein, carbohydrates, the four fats, sugar and fiber; milligrams for everything else,
171
+ /// never micrograms.
172
+ public enum Nutrient: String, Codable, Sendable, CaseIterable {
173
+ case energyConsumed = "energyConsumed"
174
+ case protein = "protein"
175
+ case carbohydrates = "carbohydrates"
176
+ case fatTotal = "fatTotal"
177
+ case fatSaturated = "fatSaturated"
178
+ case fatMonounsaturated = "fatMonounsaturated"
179
+ case fatPolyunsaturated = "fatPolyunsaturated"
180
+ case sugar = "sugar"
181
+ case fiber = "fiber"
182
+ case cholesterol = "cholesterol"
183
+ case sodium = "sodium"
184
+ case potassium = "potassium"
185
+ case calcium = "calcium"
186
+ case iron = "iron"
187
+ case magnesium = "magnesium"
188
+ case zinc = "zinc"
189
+ case phosphorus = "phosphorus"
190
+ case iodine = "iodine"
191
+ case manganese = "manganese"
192
+ case molybdenum = "molybdenum"
193
+ case selenium = "selenium"
194
+ case copper = "copper"
195
+ case chromium = "chromium"
196
+ case chloride = "chloride"
197
+ case caffeine = "caffeine"
198
+ case vitaminA = "vitaminA"
199
+ case vitaminC = "vitaminC"
200
+ case vitaminD = "vitaminD"
201
+ case vitaminE = "vitaminE"
202
+ case vitaminK = "vitaminK"
203
+ case vitaminB6 = "vitaminB6"
204
+ case vitaminB12 = "vitaminB12"
205
+ case thiamin = "thiamin"
206
+ case riboflavin = "riboflavin"
207
+ case niacin = "niacin"
208
+ case folate = "folate"
209
+ case biotin = "biotin"
210
+ case pantothenicAcid = "pantothenicAcid"
211
+ }
212
+
213
+ /// Which meal a quantity was logged against. unknown is the store's explicit answer, not an
214
+ /// absence: HealthKit has no meal type at all, so every iOS quantity is unknown.
215
+ public enum MealType: String, Codable, Sendable, CaseIterable {
216
+ case breakfast = "breakfast"
217
+ case lunch = "lunch"
218
+ case dinner = "dinner"
219
+ case snack = "snack"
220
+ case unknown = "unknown"
221
+ }
222
+
223
+ /// How heavy a logged day's flow was.
224
+ public enum MenstruationFlow: String, Codable, Sendable, CaseIterable {
225
+ case unspecified = "unspecified"
226
+ case light = "light"
227
+ case medium = "medium"
228
+ case heavy = "heavy"
229
+ /// noFlow avoids ambiguity with Optional.none. Health Connect has no constant for it, so
230
+ /// Android never produces it.
231
+ case noFlow = "none"
232
+ }
233
+
234
+ /// Available on ios only.
235
+ public enum BiologicalSex: String, Codable, Sendable, CaseIterable {
236
+ case female = "female"
237
+ case male = "male"
238
+ case other = "other"
239
+ /// The characteristic exists and holds no value, or reading it failed. The store's explicit
240
+ /// unknown.
241
+ case notSet = "notSet"
242
+ }
243
+
244
+ /// A move-time user's daily totals are minutes rather than active energy.
245
+ ///
246
+ /// Available on ios only.
247
+ public enum ActivityMoveMode: String, Codable, Sendable, CaseIterable {
248
+ case activeEnergy = "activeEnergy"
249
+ case appleMoveTime = "appleMoveTime"
250
+ }
251
+
161
252
  /// Why background delivery is restricted. Android discloses which; Apple reports the state
162
253
  /// without a reason.
163
254
  ///
@@ -241,6 +332,110 @@ public enum WorkoutSchedulingStatus: String, Codable, Sendable, CaseIterable {
241
332
  case authorized = "authorized"
242
333
  }
243
334
 
335
+ /// distance* are metres, *EnergyBurned kilocalories, flightsClimbed a count, and
336
+ /// appleExerciseTime minutes.
337
+ public enum DayTotalMetric: String, Codable, Sendable, CaseIterable {
338
+ case distanceWalkingRunning = "distanceWalkingRunning"
339
+ /// Health Connect does not split distance by activity, so only Apple ever answers it.
340
+ case distanceCycling = "distanceCycling"
341
+ case distanceSwimming = "distanceSwimming"
342
+ case distanceWheelchair = "distanceWheelchair"
343
+ case activeEnergyBurned = "activeEnergyBurned"
344
+ case basalEnergyBurned = "basalEnergyBurned"
345
+ case flightsClimbed = "flightsClimbed"
346
+ /// The exercise ring, in minutes. Health Connect has no such figure: summing session durations
347
+ /// is a different number.
348
+ ///
349
+ /// Available on ios only.
350
+ case appleExerciseTime = "appleExerciseTime"
351
+ }
352
+
353
+ /// activeEnergy and basalEnergy are kilocalories, distance metres, floorsClimbed a count.
354
+ public enum IntradayMetric: String, Codable, Sendable, CaseIterable {
355
+ case activeEnergy = "activeEnergy"
356
+ /// Health Connect has no basal energy record; its basal metabolic rate is a body measurement
357
+ /// and is not this.
358
+ ///
359
+ /// Available on ios only.
360
+ case basalEnergy = "basalEnergy"
361
+ case distance = "distance"
362
+ case floorsClimbed = "floorsClimbed"
363
+ }
364
+
365
+ /// speed is metres per second, power watts, cadence pedal revolutions per minute.
366
+ public enum WorkoutIntensityMetric: String, Codable, Sendable, CaseIterable {
367
+ case speed = "speed"
368
+ case power = "power"
369
+ case cadence = "cadence"
370
+ }
371
+
372
+ /// The body composition figure a measurement carries. weight and leanBodyMass are kilograms,
373
+ /// height centimetres, bodyFatPercentage a fraction in 0..1, bmi dimensionless.
374
+ public enum BodyMetric: String, Codable, Sendable, CaseIterable {
375
+ case weight = "weight"
376
+ case height = "height"
377
+ case bodyFatPercentage = "bodyFatPercentage"
378
+ case leanBodyMass = "leanBodyMass"
379
+ /// Health Connect has no BMI record.
380
+ ///
381
+ /// Available on ios only.
382
+ case bmi = "bmi"
383
+ }
384
+
385
+ /// The unit the writing app chose. Never converted on the device.
386
+ public enum BloodGlucoseUnit: String, Codable, Sendable, CaseIterable {
387
+ case milligramsPerDecilitre = "mg/dL"
388
+ case millimolesPerLitre = "mmol/L"
389
+ }
390
+
391
+ /// unknown is the store saying nothing about the meal, distinct from absent.
392
+ public enum GlucoseMealTime: String, Codable, Sendable, CaseIterable {
393
+ case preprandial = "preprandial"
394
+ case postprandial = "postprandial"
395
+ case unknown = "unknown"
396
+ }
397
+
398
+ /// interstitial is a continuous monitor.
399
+ public enum GlucoseSpecimen: String, Codable, Sendable, CaseIterable {
400
+ case capillary = "capillary"
401
+ case interstitial = "interstitial"
402
+ case plasma = "plasma"
403
+ case serum = "serum"
404
+ case tears = "tears"
405
+ case wholeBlood = "wholeBlood"
406
+ case unknown = "unknown"
407
+ }
408
+
409
+ /// body and basal are absolute; wrist and skin are deltas from a baseline and routinely
410
+ /// negative.
411
+ public enum BodyTemperatureMetric: String, Codable, Sendable, CaseIterable {
412
+ case body = "body"
413
+ case basal = "basal"
414
+ /// HealthKit's sleeping wrist temperature. Health Connect has no such record.
415
+ ///
416
+ /// Available on ios only.
417
+ case wrist = "wrist"
418
+ case skin = "skin"
419
+ }
420
+
421
+ /// HealthKit records SDNN only and Health Connect RMSSD only; they are never one series.
422
+ public enum HeartRateVariabilityMetric: String, Codable, Sendable, CaseIterable {
423
+ case sdnn = "sdnn"
424
+ case rmssd = "rmssd"
425
+ }
426
+
427
+ public enum VO2MaxTestType: String, Codable, Sendable, CaseIterable {
428
+ case maxExercise = "maxExercise"
429
+ case predictedSubMaxExercise = "predictedSubMaxExercise"
430
+ case predictedNonExercise = "predictedNonExercise"
431
+ }
432
+
433
+ public enum MenstrualEventKind: String, Codable, Sendable, CaseIterable {
434
+ case intermenstrualBleeding = "intermenstrualBleeding"
435
+ case ovulationTest = "ovulationTest"
436
+ case cervicalMucus = "cervicalMucus"
437
+ }
438
+
244
439
  /// A `CodingKey` over a plain string, for the dictionary-key conformances below.
245
440
  public struct StringCodingKey: CodingKey {
246
441
  public let stringValue: String
@@ -576,6 +771,177 @@ public enum StreamMessage: Codable, Equatable, Sendable {
576
771
  }
577
772
  }
578
773
 
774
+ /// What a BodyReading measured, with the unit fixed by the variant. Blood glucose alone keeps
775
+ /// the writer's unit.
776
+ public enum BodyReadingKind: Codable, Equatable, Sendable {
777
+ case measurement(metric: BodyMetric, value: Double)
778
+ case bloodPressure(systolic: Double, diastolic: Double)
779
+ case bloodGlucose(value: Double, unit: BloodGlucoseUnit, mealTime: GlucoseMealTime?, specimen: GlucoseSpecimen?)
780
+ case oxygenSaturation(fraction: Double)
781
+ case temperature(metric: BodyTemperatureMetric, celsius: Double)
782
+ case heartRateVariability(metric: HeartRateVariabilityMetric, milliseconds: Double)
783
+ case respiratoryRate(breathsPerMinute: Double)
784
+ case vo2Max(millilitresPerKilogramMinute: Double, testType: VO2MaxTestType?)
785
+
786
+ private enum CodingKeys: String, CodingKey {
787
+ case type
788
+ case metric
789
+ case value
790
+ case systolic
791
+ case diastolic
792
+ case unit
793
+ case mealTime
794
+ case specimen
795
+ case fraction
796
+ case celsius
797
+ case milliseconds
798
+ case breathsPerMinute
799
+ case millilitresPerKilogramMinute
800
+ case testType
801
+ }
802
+
803
+ public init(from decoder: any Decoder) throws {
804
+ let container = try decoder.container(keyedBy: CodingKeys.self)
805
+ let tag = try container.decode(String.self, forKey: .type)
806
+ switch tag {
807
+ case "measurement":
808
+ let metric = try container.decode(BodyMetric.self, forKey: .metric)
809
+ let value = try container.decode(Double.self, forKey: .value)
810
+ self = .measurement(metric: metric, value: value)
811
+ case "bloodPressure":
812
+ let systolic = try container.decode(Double.self, forKey: .systolic)
813
+ let diastolic = try container.decode(Double.self, forKey: .diastolic)
814
+ self = .bloodPressure(systolic: systolic, diastolic: diastolic)
815
+ case "bloodGlucose":
816
+ let value = try container.decode(Double.self, forKey: .value)
817
+ let unit = try container.decode(BloodGlucoseUnit.self, forKey: .unit)
818
+ let mealTime = try container.decodeIfPresent(GlucoseMealTime.self, forKey: .mealTime)
819
+ let specimen = try container.decodeIfPresent(GlucoseSpecimen.self, forKey: .specimen)
820
+ self = .bloodGlucose(value: value, unit: unit, mealTime: mealTime, specimen: specimen)
821
+ case "oxygenSaturation":
822
+ let fraction = try container.decode(Double.self, forKey: .fraction)
823
+ self = .oxygenSaturation(fraction: fraction)
824
+ case "temperature":
825
+ let metric = try container.decode(BodyTemperatureMetric.self, forKey: .metric)
826
+ let celsius = try container.decode(Double.self, forKey: .celsius)
827
+ self = .temperature(metric: metric, celsius: celsius)
828
+ case "heartRateVariability":
829
+ let metric = try container.decode(HeartRateVariabilityMetric.self, forKey: .metric)
830
+ let milliseconds = try container.decode(Double.self, forKey: .milliseconds)
831
+ self = .heartRateVariability(metric: metric, milliseconds: milliseconds)
832
+ case "respiratoryRate":
833
+ let breathsPerMinute = try container.decode(Double.self, forKey: .breathsPerMinute)
834
+ self = .respiratoryRate(breathsPerMinute: breathsPerMinute)
835
+ case "vo2Max":
836
+ let millilitresPerKilogramMinute = try container.decode(Double.self, forKey: .millilitresPerKilogramMinute)
837
+ let testType = try container.decodeIfPresent(VO2MaxTestType.self, forKey: .testType)
838
+ self = .vo2Max(millilitresPerKilogramMinute: millilitresPerKilogramMinute, testType: testType)
839
+ default:
840
+ throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "unknown BodyReadingKind \(tag)")
841
+ }
842
+ }
843
+
844
+ public func encode(to encoder: any Encoder) throws {
845
+ var container = encoder.container(keyedBy: CodingKeys.self)
846
+ switch self {
847
+ case .measurement(let metric, let value):
848
+ try container.encode("measurement", forKey: .type)
849
+ try container.encode(metric, forKey: .metric)
850
+ try container.encode(value, forKey: .value)
851
+ case .bloodPressure(let systolic, let diastolic):
852
+ try container.encode("bloodPressure", forKey: .type)
853
+ try container.encode(systolic, forKey: .systolic)
854
+ try container.encode(diastolic, forKey: .diastolic)
855
+ case .bloodGlucose(let value, let unit, let mealTime, let specimen):
856
+ try container.encode("bloodGlucose", forKey: .type)
857
+ try container.encode(value, forKey: .value)
858
+ try container.encode(unit, forKey: .unit)
859
+ try container.encodeIfPresent(mealTime, forKey: .mealTime)
860
+ try container.encodeIfPresent(specimen, forKey: .specimen)
861
+ case .oxygenSaturation(let fraction):
862
+ try container.encode("oxygenSaturation", forKey: .type)
863
+ try container.encode(fraction, forKey: .fraction)
864
+ case .temperature(let metric, let celsius):
865
+ try container.encode("temperature", forKey: .type)
866
+ try container.encode(metric, forKey: .metric)
867
+ try container.encode(celsius, forKey: .celsius)
868
+ case .heartRateVariability(let metric, let milliseconds):
869
+ try container.encode("heartRateVariability", forKey: .type)
870
+ try container.encode(metric, forKey: .metric)
871
+ try container.encode(milliseconds, forKey: .milliseconds)
872
+ case .respiratoryRate(let breathsPerMinute):
873
+ try container.encode("respiratoryRate", forKey: .type)
874
+ try container.encode(breathsPerMinute, forKey: .breathsPerMinute)
875
+ case .vo2Max(let millilitresPerKilogramMinute, let testType):
876
+ try container.encode("vo2Max", forKey: .type)
877
+ try container.encode(millilitresPerKilogramMinute, forKey: .millilitresPerKilogramMinute)
878
+ try container.encodeIfPresent(testType, forKey: .testType)
879
+ }
880
+ }
881
+ }
882
+
883
+ /// Apple's store is part of the OS, so it is there or the device cannot have it; Health Connect
884
+ /// is an app a person can lack.
885
+ public enum StoreAvailability: Codable, Equatable, Sendable {
886
+ case available
887
+ case unsupportedDevice
888
+ case providerAppMissing
889
+ case providerAppTooOld(installed: String)
890
+ case moduleMissing
891
+ case partnerPolicyMissing
892
+ case featureUnavailable
893
+
894
+ private enum CodingKeys: String, CodingKey {
895
+ case kind
896
+ case installed
897
+ }
898
+
899
+ public init(from decoder: any Decoder) throws {
900
+ let container = try decoder.container(keyedBy: CodingKeys.self)
901
+ let tag = try container.decode(String.self, forKey: .kind)
902
+ switch tag {
903
+ case "available":
904
+ self = .available
905
+ case "unsupportedDevice":
906
+ self = .unsupportedDevice
907
+ case "providerAppMissing":
908
+ self = .providerAppMissing
909
+ case "providerAppTooOld":
910
+ let installed = try container.decode(String.self, forKey: .installed)
911
+ self = .providerAppTooOld(installed: installed)
912
+ case "moduleMissing":
913
+ self = .moduleMissing
914
+ case "partnerPolicyMissing":
915
+ self = .partnerPolicyMissing
916
+ case "featureUnavailable":
917
+ self = .featureUnavailable
918
+ default:
919
+ throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "unknown StoreAvailability \(tag)")
920
+ }
921
+ }
922
+
923
+ public func encode(to encoder: any Encoder) throws {
924
+ var container = encoder.container(keyedBy: CodingKeys.self)
925
+ switch self {
926
+ case .available:
927
+ try container.encode("available", forKey: .kind)
928
+ case .unsupportedDevice:
929
+ try container.encode("unsupportedDevice", forKey: .kind)
930
+ case .providerAppMissing:
931
+ try container.encode("providerAppMissing", forKey: .kind)
932
+ case .providerAppTooOld(let installed):
933
+ try container.encode("providerAppTooOld", forKey: .kind)
934
+ try container.encode(installed, forKey: .installed)
935
+ case .moduleMissing:
936
+ try container.encode("moduleMissing", forKey: .kind)
937
+ case .partnerPolicyMissing:
938
+ try container.encode("partnerPolicyMissing", forKey: .kind)
939
+ case .featureUnavailable:
940
+ try container.encode("featureUnavailable", forKey: .kind)
941
+ }
942
+ }
943
+ }
944
+
579
945
  /// Who recorded a sample. The only stable handle an app has for filtering by app or device.
580
946
  public struct SampleSource: Codable, Equatable, Sendable {
581
947
  public var name: String
@@ -685,8 +1051,11 @@ public struct Workout: Codable, Equatable, Sendable {
685
1051
  public var source: SampleSource
686
1052
  public var userEntered: Bool
687
1053
  public var timeZone: String?
1054
+ /// ExerciseSessionRecord.title on Android; HKMetadataKeyWorkoutBrandName on Apple, which has no
1055
+ /// title field.
1056
+ public var title: String?
688
1057
 
689
- public init(id: String, start: String, end: String, activity: WorkoutActivity, activityTypeCode: Int, indoor: Bool? = nil, activeEnergyKilocalories: Double? = nil, distanceMeters: Double? = nil, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
1058
+ public init(id: String, start: String, end: String, activity: WorkoutActivity, activityTypeCode: Int, indoor: Bool? = nil, activeEnergyKilocalories: Double? = nil, distanceMeters: Double? = nil, source: SampleSource, userEntered: Bool, timeZone: String? = nil, title: String? = nil) {
690
1059
  self.id = id
691
1060
  self.start = start
692
1061
  self.end = end
@@ -698,6 +1067,77 @@ public struct Workout: Codable, Equatable, Sendable {
698
1067
  self.source = source
699
1068
  self.userEntered = userEntered
700
1069
  self.timeZone = timeZone
1070
+ self.title = title
1071
+ }
1072
+ }
1073
+
1074
+ /// Quantities of one meal share foodCorrelationId.
1075
+ public struct DietaryQuantity: Codable, Equatable, Sendable {
1076
+ public var id: String
1077
+ public var nutrient: Nutrient
1078
+ public var value: Double
1079
+ public var start: String
1080
+ public var end: String
1081
+ public var mealType: MealType
1082
+ public var name: String?
1083
+ /// HealthKit's food correlation, absent for a loose quantity. Always set on Android, where it
1084
+ /// is the NutritionRecord's id - the id nutrition.delete takes for a meal this app logged.
1085
+ public var foodCorrelationId: String?
1086
+ public var source: SampleSource
1087
+ public var userEntered: Bool
1088
+ public var timeZone: String?
1089
+
1090
+ public init(id: String, nutrient: Nutrient, value: Double, start: String, end: String, mealType: MealType, name: String? = nil, foodCorrelationId: String? = nil, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
1091
+ self.id = id
1092
+ self.nutrient = nutrient
1093
+ self.value = value
1094
+ self.start = start
1095
+ self.end = end
1096
+ self.mealType = mealType
1097
+ self.name = name
1098
+ self.foodCorrelationId = foodCorrelationId
1099
+ self.source = source
1100
+ self.userEntered = userEntered
1101
+ self.timeZone = timeZone
1102
+ }
1103
+ }
1104
+
1105
+ /// start and end are the local midnights of that day in timeZone.
1106
+ public struct MenstruationFlowDay: Codable, Equatable, Sendable {
1107
+ public var id: String
1108
+ public var start: String
1109
+ public var end: String
1110
+ public var flow: MenstruationFlow
1111
+ public var cycleStart: Bool
1112
+ public var source: SampleSource
1113
+ public var userEntered: Bool
1114
+ public var timeZone: String?
1115
+
1116
+ public init(id: String, start: String, end: String, flow: MenstruationFlow, cycleStart: Bool, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
1117
+ self.id = id
1118
+ self.start = start
1119
+ self.end = end
1120
+ self.flow = flow
1121
+ self.cycleStart = cycleStart
1122
+ self.source = source
1123
+ self.userEntered = userEntered
1124
+ self.timeZone = timeZone
1125
+ }
1126
+ }
1127
+
1128
+ /// Available on ios only.
1129
+ public struct AthleteProfile: Codable, Equatable, Sendable {
1130
+ public var biologicalSex: BiologicalSex
1131
+ /// A calendar day, as HealthKit stores it, never an instant.
1132
+ public var dateOfBirth: String?
1133
+ public var wheelchairUse: Bool?
1134
+ public var activityMoveMode: ActivityMoveMode?
1135
+
1136
+ public init(biologicalSex: BiologicalSex, dateOfBirth: String? = nil, wheelchairUse: Bool? = nil, activityMoveMode: ActivityMoveMode? = nil) {
1137
+ self.biologicalSex = biologicalSex
1138
+ self.dateOfBirth = dateOfBirth
1139
+ self.wheelchairUse = wheelchairUse
1140
+ self.activityMoveMode = activityMoveMode
701
1141
  }
702
1142
  }
703
1143
 
@@ -1216,6 +1656,67 @@ public struct WorkoutList: Codable, Equatable, Sendable {
1216
1656
  }
1217
1657
  }
1218
1658
 
1659
+ public struct DietaryQuantityList: Codable, Equatable, Sendable {
1660
+ public var samples: [DietaryQuantity]
1661
+
1662
+ public init(samples: [DietaryQuantity]) {
1663
+ self.samples = samples
1664
+ }
1665
+ }
1666
+
1667
+ public struct MenstruationFlowDayList: Codable, Equatable, Sendable {
1668
+ public var samples: [MenstruationFlowDay]
1669
+
1670
+ public init(samples: [MenstruationFlowDay]) {
1671
+ self.samples = samples
1672
+ }
1673
+ }
1674
+
1675
+ /// A cycle event logged at an instant.
1676
+ public struct MenstrualEvent: Codable, Equatable, Sendable {
1677
+ public var id: String
1678
+ public var kind: MenstrualEventKind
1679
+ public var time: String
1680
+ /// ovulationTest: negative, positive, high, indeterminate or luteinizingHormoneSurge.
1681
+ /// cervicalMucus: dry, sticky, creamy, watery, eggWhite or unusual. Absent for
1682
+ /// intermenstrualBleeding, which is a flag.
1683
+ public var value: String?
1684
+ /// The store's own record type, passed through: OvulationTestRecord on Android,
1685
+ /// HKCategoryTypeIdentifierOvulationTestResult on Apple.
1686
+ public var storeIdentifier: String
1687
+ public var source: SampleSource
1688
+ public var userEntered: Bool
1689
+ public var timeZone: String?
1690
+
1691
+ public init(id: String, kind: MenstrualEventKind, time: String, value: String? = nil, storeIdentifier: String, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
1692
+ self.id = id
1693
+ self.kind = kind
1694
+ self.time = time
1695
+ self.value = value
1696
+ self.storeIdentifier = storeIdentifier
1697
+ self.source = source
1698
+ self.userEntered = userEntered
1699
+ self.timeZone = timeZone
1700
+ }
1701
+ }
1702
+
1703
+ public struct MenstrualEventList: Codable, Equatable, Sendable {
1704
+ public var samples: [MenstrualEvent]
1705
+
1706
+ public init(samples: [MenstrualEvent]) {
1707
+ self.samples = samples
1708
+ }
1709
+ }
1710
+
1711
+ /// Available on ios only.
1712
+ public struct AthleteProfileResult: Codable, Equatable, Sendable {
1713
+ public var profile: AthleteProfile?
1714
+
1715
+ public init(profile: AthleteProfile? = nil) {
1716
+ self.profile = profile
1717
+ }
1718
+ }
1719
+
1219
1720
  /// Available on ios only.
1220
1721
  public struct PlannedWorkoutList: Codable, Equatable, Sendable {
1221
1722
  public var workouts: [PlannedWorkout]
@@ -1292,6 +1793,238 @@ public struct NutritionEntryList: Codable, Equatable, Sendable {
1292
1793
  }
1293
1794
  }
1294
1795
 
1796
+ /// The store's own total for one local day, from its statistics engine. start and end are that
1797
+ /// day's midnights.
1798
+ public struct DayTotal: Codable, Equatable, Sendable {
1799
+ public var metric: DayTotalMetric
1800
+ public var start: String
1801
+ public var end: String
1802
+ public var value: Double
1803
+ public var unit: String
1804
+ public var timeZone: String?
1805
+
1806
+ public init(metric: DayTotalMetric, start: String, end: String, value: Double, unit: String, timeZone: String? = nil) {
1807
+ self.metric = metric
1808
+ self.start = start
1809
+ self.end = end
1810
+ self.value = value
1811
+ self.unit = unit
1812
+ self.timeZone = timeZone
1813
+ }
1814
+ }
1815
+
1816
+ /// One record a day total is made of, as the store holds it. A record crossing midnight is
1817
+ /// whole.
1818
+ public struct IntradaySample: Codable, Equatable, Sendable {
1819
+ public var id: String
1820
+ public var metric: IntradayMetric
1821
+ public var start: String
1822
+ public var end: String
1823
+ public var value: Double
1824
+ public var source: SampleSource
1825
+ public var userEntered: Bool
1826
+ public var timeZone: String?
1827
+
1828
+ public init(id: String, metric: IntradayMetric, start: String, end: String, value: Double, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
1829
+ self.id = id
1830
+ self.metric = metric
1831
+ self.start = start
1832
+ self.end = end
1833
+ self.value = value
1834
+ self.source = source
1835
+ self.userEntered = userEntered
1836
+ self.timeZone = timeZone
1837
+ }
1838
+ }
1839
+
1840
+ /// The store's own resting heart rate for a day, never derived from heart rate readings.
1841
+ public struct RestingHeartRateSample: Codable, Equatable, Sendable {
1842
+ public var id: String
1843
+ public var start: String
1844
+ public var end: String
1845
+ public var beatsPerMinute: Double
1846
+ public var source: SampleSource
1847
+ public var userEntered: Bool
1848
+ public var timeZone: String?
1849
+
1850
+ public init(id: String, start: String, end: String, beatsPerMinute: Double, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
1851
+ self.id = id
1852
+ self.start = start
1853
+ self.end = end
1854
+ self.beatsPerMinute = beatsPerMinute
1855
+ self.source = source
1856
+ self.userEntered = userEntered
1857
+ self.timeZone = timeZone
1858
+ }
1859
+ }
1860
+
1861
+ /// A point reading has start equal to end; skin and wrist temperature span a stretch.
1862
+ public struct BodyReading: Codable, Equatable, Sendable {
1863
+ public var id: String
1864
+ public var kind: BodyReadingKind
1865
+ public var start: String
1866
+ public var end: String
1867
+ public var source: SampleSource
1868
+ public var userEntered: Bool
1869
+ public var timeZone: String?
1870
+
1871
+ public init(id: String, kind: BodyReadingKind, start: String, end: String, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
1872
+ self.id = id
1873
+ self.kind = kind
1874
+ self.start = start
1875
+ self.end = end
1876
+ self.source = source
1877
+ self.userEntered = userEntered
1878
+ self.timeZone = timeZone
1879
+ }
1880
+ }
1881
+
1882
+ public struct RoutePoint: Codable, Equatable, Sendable {
1883
+ public var time: String
1884
+ public var latitude: Double
1885
+ public var longitude: Double
1886
+
1887
+ public init(time: String, latitude: Double, longitude: Double) {
1888
+ self.time = time
1889
+ self.latitude = latitude
1890
+ self.longitude = longitude
1891
+ }
1892
+ }
1893
+
1894
+ /// The GPS track a workout left, thinned to one point a second and at most 5,000 points. start
1895
+ /// and end are the first and last point.
1896
+ public struct WorkoutRoute: Codable, Equatable, Sendable {
1897
+ public var id: String
1898
+ public var workoutId: String
1899
+ public var start: String
1900
+ public var end: String
1901
+ public var points: [RoutePoint]
1902
+ public var source: SampleSource
1903
+ public var timeZone: String?
1904
+
1905
+ public init(id: String, workoutId: String, start: String, end: String, points: [RoutePoint], source: SampleSource, timeZone: String? = nil) {
1906
+ self.id = id
1907
+ self.workoutId = workoutId
1908
+ self.start = start
1909
+ self.end = end
1910
+ self.points = points
1911
+ self.source = source
1912
+ self.timeZone = timeZone
1913
+ }
1914
+ }
1915
+
1916
+ /// One speed, power or cadence reading. Android's are instants, so start equals end there.
1917
+ public struct WorkoutIntensitySample: Codable, Equatable, Sendable {
1918
+ public var id: String
1919
+ public var metric: WorkoutIntensityMetric
1920
+ /// Set on Apple, where HealthKit stores a reading inside its workout. Health Connect stores
1921
+ /// them free-standing, so Android leaves it absent rather than guessing by time.
1922
+ public var workoutId: String?
1923
+ public var start: String
1924
+ public var end: String
1925
+ public var value: Double
1926
+ public var source: SampleSource
1927
+ public var userEntered: Bool
1928
+ public var timeZone: String?
1929
+
1930
+ public init(id: String, metric: WorkoutIntensityMetric, workoutId: String? = nil, start: String, end: String, value: Double, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
1931
+ self.id = id
1932
+ self.metric = metric
1933
+ self.workoutId = workoutId
1934
+ self.start = start
1935
+ self.end = end
1936
+ self.value = value
1937
+ self.source = source
1938
+ self.userEntered = userEntered
1939
+ self.timeZone = timeZone
1940
+ }
1941
+ }
1942
+
1943
+ /// One drink, in millilitres.
1944
+ public struct HydrationSample: Codable, Equatable, Sendable {
1945
+ public var id: String
1946
+ public var start: String
1947
+ public var end: String
1948
+ public var millilitres: Double
1949
+ public var source: SampleSource
1950
+ public var userEntered: Bool
1951
+ public var timeZone: String?
1952
+
1953
+ public init(id: String, start: String, end: String, millilitres: Double, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
1954
+ self.id = id
1955
+ self.start = start
1956
+ self.end = end
1957
+ self.millilitres = millilitres
1958
+ self.source = source
1959
+ self.userEntered = userEntered
1960
+ self.timeZone = timeZone
1961
+ }
1962
+ }
1963
+
1964
+ public struct DayTotalList: Codable, Equatable, Sendable {
1965
+ public var totals: [DayTotal]
1966
+
1967
+ public init(totals: [DayTotal]) {
1968
+ self.totals = totals
1969
+ }
1970
+ }
1971
+
1972
+ public struct IntradaySampleList: Codable, Equatable, Sendable {
1973
+ public var samples: [IntradaySample]
1974
+
1975
+ public init(samples: [IntradaySample]) {
1976
+ self.samples = samples
1977
+ }
1978
+ }
1979
+
1980
+ public struct RestingHeartRateList: Codable, Equatable, Sendable {
1981
+ public var samples: [RestingHeartRateSample]
1982
+
1983
+ public init(samples: [RestingHeartRateSample]) {
1984
+ self.samples = samples
1985
+ }
1986
+ }
1987
+
1988
+ public struct BodyReadingList: Codable, Equatable, Sendable {
1989
+ public var samples: [BodyReading]
1990
+
1991
+ public init(samples: [BodyReading]) {
1992
+ self.samples = samples
1993
+ }
1994
+ }
1995
+
1996
+ public struct WorkoutRouteList: Codable, Equatable, Sendable {
1997
+ public var routes: [WorkoutRoute]
1998
+
1999
+ public init(routes: [WorkoutRoute]) {
2000
+ self.routes = routes
2001
+ }
2002
+ }
2003
+
2004
+ public struct WorkoutIntensityList: Codable, Equatable, Sendable {
2005
+ public var samples: [WorkoutIntensitySample]
2006
+
2007
+ public init(samples: [WorkoutIntensitySample]) {
2008
+ self.samples = samples
2009
+ }
2010
+ }
2011
+
2012
+ public struct HydrationList: Codable, Equatable, Sendable {
2013
+ public var samples: [HydrationSample]
2014
+
2015
+ public init(samples: [HydrationSample]) {
2016
+ self.samples = samples
2017
+ }
2018
+ }
2019
+
2020
+ public struct AvailabilityResult: Codable, Equatable, Sendable {
2021
+ public var availability: StoreAvailability
2022
+
2023
+ public init(availability: StoreAvailability) {
2024
+ self.availability = availability
2025
+ }
2026
+ }
2027
+
1295
2028
  public enum TerraEventEnvelope {
1296
2029
  public static func validate(_ value: TerraEvent) throws {
1297
2030
  guard case .string = value.storage["at"] else {