@tryterra/react-native 0.0.0-stage → 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 (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -2
  3. package/TerraReactNative.podspec +48 -0
  4. package/android/build.gradle +87 -0
  5. package/android/src/bridgeCore/kotlin/README.md +1 -0
  6. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/BridgeBootstrap.kt +133 -0
  7. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/BridgeHost.kt +32 -0
  8. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Conversions.kt +557 -0
  9. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/LauncherSlots.kt +41 -0
  10. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/ReadConversions.kt +250 -0
  11. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Rejections.kt +150 -0
  12. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/StreamRegistry.kt +77 -0
  13. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridge.kt +86 -0
  14. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeOpsImpl.kt +300 -0
  15. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeStreamsImpl.kt +50 -0
  16. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TokenBroker.kt +198 -0
  17. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeErrors.kt +217 -0
  18. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeOps.kt +582 -0
  19. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeTypes.kt +1569 -0
  20. package/android/src/healthConnectOnly/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +11 -0
  21. package/android/src/main/AndroidManifest.xml +24 -0
  22. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraBridgeModule.kt +96 -0
  23. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraReactNativePackage.kt +26 -0
  24. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraStartup.kt +46 -0
  25. package/android/src/samsung/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +13 -0
  26. package/docs/availability.md +396 -0
  27. package/frameworks/TerraSDK.xcframework/Info.plist +48 -0
  28. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Info.plist +0 -0
  29. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.abi.json +82672 -0
  30. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.package.swiftinterface +2186 -0
  31. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +2186 -0
  32. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  33. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftinterface +2163 -0
  34. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
  35. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/TerraSDK +0 -0
  36. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
  37. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
  38. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6594 -0
  39. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Info.plist +0 -0
  40. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +82672 -0
  41. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.package.swiftinterface +2186 -0
  42. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +2186 -0
  43. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  44. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +2163 -0
  45. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +82672 -0
  46. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.package.swiftinterface +2186 -0
  47. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +2186 -0
  48. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  49. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +2163 -0
  50. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
  51. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/TerraSDK +0 -0
  52. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/_CodeSignature/CodeResources +223 -0
  53. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
  54. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
  55. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6595 -0
  56. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/TerraSDK.yml +6513 -0
  57. package/ios/TerraBridgeCore/BridgeBootstrap.swift +89 -0
  58. package/ios/TerraBridgeCore/BridgeHost.swift +13 -0
  59. package/ios/TerraBridgeCore/Conversions+Reads.swift +259 -0
  60. package/ios/TerraBridgeCore/Conversions.swift +594 -0
  61. package/ios/TerraBridgeCore/Generated/BridgeErrors.swift +179 -0
  62. package/ios/TerraBridgeCore/Generated/BridgeOps.swift +967 -0
  63. package/ios/TerraBridgeCore/Generated/BridgeTypes.swift +2050 -0
  64. package/ios/TerraBridgeCore/Locked.swift +15 -0
  65. package/ios/TerraBridgeCore/README.md +1 -0
  66. package/ios/TerraBridgeCore/Rejections.swift +130 -0
  67. package/ios/TerraBridgeCore/RuntimeAttachment.swift +60 -0
  68. package/ios/TerraBridgeCore/StreamRegistry.swift +59 -0
  69. package/ios/TerraBridgeCore/TerraBridge.swift +122 -0
  70. package/ios/TerraBridgeCore/TerraBridgeOpsImpl.swift +318 -0
  71. package/ios/TerraBridgeCore/TerraBridgeStreamsImpl.swift +60 -0
  72. package/ios/TerraBridgeCore/TokenBroker.swift +147 -0
  73. package/ios/TerraBridgeModule.mm +79 -0
  74. package/ios/TerraBridgeModuleImpl.swift +49 -0
  75. package/ios/TerraReactNative.swift +19 -0
  76. package/lib/EndUser.d.ts +12 -0
  77. package/lib/EndUser.js +13 -0
  78. package/lib/NativeTerraBridge.d.ts +23 -0
  79. package/lib/NativeTerraBridge.js +13 -0
  80. package/lib/Terra.d.ts +223 -0
  81. package/lib/Terra.js +336 -0
  82. package/lib/TerraError.d.ts +42 -0
  83. package/lib/TerraError.js +95 -0
  84. package/lib/createTerra.d.ts +8 -0
  85. package/lib/createTerra.js +19 -0
  86. package/lib/generated/client.d.ts +128 -0
  87. package/lib/generated/client.js +256 -0
  88. package/lib/generated/codecs.d.ts +219 -0
  89. package/lib/generated/codecs.js +2066 -0
  90. package/lib/generated/errors.d.ts +13 -0
  91. package/lib/generated/errors.js +68 -0
  92. package/lib/generated/ops.d.ts +13 -0
  93. package/lib/generated/ops.js +161 -0
  94. package/lib/generated/streams.d.ts +23 -0
  95. package/lib/generated/streams.js +58 -0
  96. package/lib/generated/types.d.ts +794 -0
  97. package/lib/generated/types.js +224 -0
  98. package/lib/index.d.ts +29 -0
  99. package/lib/index.js +55 -0
  100. package/package.json +71 -4
  101. package/src/EndUser.ts +15 -0
  102. package/src/NativeTerraBridge.ts +27 -0
  103. package/src/Terra.ts +592 -0
  104. package/src/TerraError.ts +116 -0
  105. package/src/createTerra.ts +18 -0
  106. package/src/generated/client.ts +315 -0
  107. package/src/generated/codecs.ts +1989 -0
  108. package/src/generated/errors.ts +113 -0
  109. package/src/generated/ops.ts +233 -0
  110. package/src/generated/streams.ts +74 -0
  111. package/src/generated/types.ts +1222 -0
  112. package/src/index.ts +54 -0
@@ -0,0 +1,2050 @@
1
+ // Generated from surface/terra-surface.json by surface/gen. Do not edit by hand.
2
+ // Regenerate with: node surface/gen/src/cli.ts generate
3
+
4
+ import Foundation
5
+
6
+ /// The one spelling of an instant on the wire: UTC, and always exactly three fractional digits.
7
+ ///
8
+ /// `.iso8601` is not it. `ISO8601DateFormatter` without `.withFractionalSeconds` drops the
9
+ /// sub-second part, and HealthKit stores sub-second, so a round trip through the bridge would
10
+ /// silently move every sample to the nearest whole second. Three digits rather than however many
11
+ /// the source had, so that the same instant produces the same bytes in Swift, Kotlin and
12
+ /// JavaScript - which is what lets a vector be compared rather than interpreted.
13
+ public enum BridgeInstant {
14
+ /// `ISO8601DateFormatter` is not `Sendable`, and building one per call is not free on a read
15
+ /// of a month of samples. One pair behind a lock is the cheap end of correct here.
16
+ private final class Formatters: @unchecked Sendable {
17
+ let lock = NSLock()
18
+ let withFraction = ISO8601DateFormatter()
19
+ let withoutFraction = ISO8601DateFormatter()
20
+
21
+ init() {
22
+ withFraction.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
23
+ withFraction.timeZone = TimeZone(secondsFromGMT: 0)
24
+ withoutFraction.formatOptions = [.withInternetDateTime]
25
+ withoutFraction.timeZone = TimeZone(secondsFromGMT: 0)
26
+ }
27
+ }
28
+
29
+ private static let formatters = Formatters()
30
+
31
+ public static func format(_ date: Date) -> String {
32
+ formatters.lock.lock()
33
+ defer { formatters.lock.unlock() }
34
+ return formatters.withFraction.string(from: date)
35
+ }
36
+
37
+ /// Tolerant on the way in: a peer that omitted the fractional part is still understood.
38
+ public static func parse(_ text: String) -> Date? {
39
+ formatters.lock.lock()
40
+ defer { formatters.lock.unlock() }
41
+ return formatters.withFraction.date(from: text) ?? formatters.withoutFraction.date(from: text)
42
+ }
43
+ }
44
+
45
+ /// A JSON value the bridge carries without interpreting: an error's detail bag, an event's payload.
46
+ ///
47
+ /// The SDKs already encode those shapes, and re-describing them here would be a second opinion
48
+ /// about a spelling `DiagnosticsWire` and Apple's `Encodable` already agree on.
49
+ public struct BridgeJSON: Codable, Equatable, Sendable {
50
+ public let storage: [String: BridgeValue]
51
+ public init(_ storage: [String: BridgeValue]) { self.storage = storage }
52
+ public init(from decoder: any Decoder) throws {
53
+ storage = try decoder.singleValueContainer().decode([String: BridgeValue].self)
54
+ }
55
+ public func encode(to encoder: any Encoder) throws {
56
+ var container = encoder.singleValueContainer()
57
+ try container.encode(storage)
58
+ }
59
+ }
60
+
61
+ /// `indirect` because the cases hold `BridgeValue`: without it the enum has no finite size and
62
+ /// the file does not compile.
63
+ public indirect enum BridgeValue: Codable, Equatable, Sendable {
64
+ case string(String)
65
+ case number(Double)
66
+ case bool(Bool)
67
+ case array([BridgeValue])
68
+ case object([String: BridgeValue])
69
+ case null
70
+
71
+ public init(from decoder: any Decoder) throws {
72
+ let container = try decoder.singleValueContainer()
73
+ if container.decodeNil() { self = .null; return }
74
+ if let value = try? container.decode(Bool.self) { self = .bool(value); return }
75
+ if let value = try? container.decode(Double.self) { self = .number(value); return }
76
+ if let value = try? container.decode(String.self) { self = .string(value); return }
77
+ if let value = try? container.decode([BridgeValue].self) { self = .array(value); return }
78
+ self = .object(try container.decode([String: BridgeValue].self))
79
+ }
80
+
81
+ public func encode(to encoder: any Encoder) throws {
82
+ var container = encoder.singleValueContainer()
83
+ switch self {
84
+ case .string(let value): try container.encode(value)
85
+ case .number(let value): try container.encode(value)
86
+ case .bool(let value): try container.encode(value)
87
+ case .array(let value): try container.encode(value)
88
+ case .object(let value): try container.encode(value)
89
+ case .null: try container.encodeNil()
90
+ }
91
+ }
92
+ }
93
+
94
+ /// A list of enum cases that is a set on the wire: de-duplicated and sorted by wire spelling
95
+ /// whenever it is stored, decoded or encoded, as the Kotlin and TypeScript codecs do.
96
+ @propertyWrapper
97
+ public struct CaseSet<Element: RawRepresentable & Codable & Hashable & Sendable>: Codable, Equatable, Sendable
98
+ where Element.RawValue == String {
99
+ private var storage: [Element]
100
+
101
+ public var wrappedValue: [Element] {
102
+ get { storage }
103
+ set { storage = Self.canonical(newValue) }
104
+ }
105
+
106
+ public init(wrappedValue: [Element]) { storage = Self.canonical(wrappedValue) }
107
+
108
+ public init(from decoder: any Decoder) throws {
109
+ storage = Self.canonical(try decoder.singleValueContainer().decode([Element].self))
110
+ }
111
+
112
+ public func encode(to encoder: any Encoder) throws {
113
+ var container = encoder.singleValueContainer()
114
+ try container.encode(storage)
115
+ }
116
+
117
+ private static func canonical(_ value: [Element]) -> [Element] {
118
+ Array(Set(value)).sorted { $0.rawValue < $1.rawValue }
119
+ }
120
+ }
121
+
122
+ /// A health store the SDK can link. One connection per source per device.
123
+ public enum Source: String, Codable, Sendable, CaseIterable {
124
+ case health = "health"
125
+ /// Samsung Health, through the terra-android-samsung add-on. Connect, disconnect, permissions
126
+ /// and diagnostics take it; reads, writes and change streams are Health Connect's.
127
+ ///
128
+ /// Available on android only.
129
+ case samsungHealth = "samsungHealth"
130
+ }
131
+
132
+ /// The unit permissions are asked for and changes are reported against.
133
+ public enum DataCategory: String, Codable, Sendable, CaseIterable {
134
+ case activity = "activity"
135
+ case body = "body"
136
+ case daily = "daily"
137
+ case sleep = "sleep"
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"
146
+ }
147
+
148
+ public enum PermissionStatus: String, Codable, Sendable, CaseIterable {
149
+ case granted = "granted"
150
+ case denied = "denied"
151
+ case notDetermined = "notDetermined"
152
+ /// A read grant HealthKit will not report. Not an error: it is the documented answer for reads
153
+ /// on iOS.
154
+ case notDisclosed = "notDisclosed"
155
+ case unsupported = "unsupported"
156
+ /// Android answers unsupported where Apple would answer unavailable.
157
+ ///
158
+ /// Available on ios only.
159
+ case unavailable = "unavailable"
160
+ }
161
+
162
+ public enum MotionContext: String, Codable, Sendable, CaseIterable {
163
+ case unknown = "unknown"
164
+ case sedentary = "sedentary"
165
+ case active = "active"
166
+ }
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
+
252
+ /// Why background delivery is restricted. Android discloses which; Apple reports the state
253
+ /// without a reason.
254
+ ///
255
+ /// Available on android only.
256
+ public enum BackgroundRestrictionReason: String, Codable, Sendable, CaseIterable {
257
+ case oemRestricted = "oemRestricted"
258
+ case backgroundReadDenied = "backgroundReadDenied"
259
+ case healthConnectFeatureUnavailable = "healthConnectFeatureUnavailable"
260
+ case storeFeatureUnavailable = "storeFeatureUnavailable"
261
+ case ticksNotRunning = "ticksNotRunning"
262
+ }
263
+
264
+ public enum DiagnosticsReason: String, Codable, Sendable, CaseIterable {
265
+ case uploadFailed = "uploadFailed"
266
+ case backfillAbandoned = "backfillAbandoned"
267
+ case backgroundObservationUnavailable = "backgroundObservationUnavailable"
268
+ case backgroundRestricted = "backgroundRestricted"
269
+ case backgroundRefreshUnscheduled = "backgroundRefreshUnscheduled"
270
+ case quiet = "quiet"
271
+ case requested = "requested"
272
+ }
273
+
274
+ public enum DeviceKeyRevokeOutcome: String, Codable, Sendable, CaseIterable {
275
+ case revoked = "revoked"
276
+ case alreadyRevoked = "alreadyRevoked"
277
+ case routeAbsent = "routeAbsent"
278
+ }
279
+
280
+ /// The redacted error name an event carries. Deliberately not TerraErrorCode: an event names
281
+ /// the failure without the detail bag, because a support report leaves the device.
282
+ public enum FailureCode: String, Codable, Sendable, CaseIterable {
283
+ case healthDataUnavailable = "healthDataUnavailable"
284
+ case notConnected = "notConnected"
285
+ case tokenUnavailable = "tokenUnavailable"
286
+ case tokenRejected = "tokenRejected"
287
+ case tokenProviderFailed = "tokenProviderFailed"
288
+ case offline = "offline"
289
+ case timedOut = "timedOut"
290
+ case serverError = "serverError"
291
+ case transport = "transport"
292
+ case malformedResponse = "malformedResponse"
293
+ case userLimitReached = "userLimitReached"
294
+ case upgradeRequired = "upgradeRequired"
295
+ case platform = "platform"
296
+ case deviceKeyUnavailable = "deviceKeyUnavailable"
297
+ case signatureRejected = "signatureRejected"
298
+ case linkRevoked = "linkRevoked"
299
+ case linkConflict = "linkConflict"
300
+ case clockSkewed = "clockSkewed"
301
+ case devIDMismatch = "devIDMismatch"
302
+ case syncAlreadyRunning = "syncAlreadyRunning"
303
+ case linkAlreadyRunning = "linkAlreadyRunning"
304
+ case storeLocked = "storeLocked"
305
+ case storeUnreadable = "storeUnreadable"
306
+ case deadlineExceeded = "deadlineExceeded"
307
+ case permissionDenied = "permissionDenied"
308
+ case authorizationNotDetermined = "authorizationNotDetermined"
309
+ case sampleNotFound = "sampleNotFound"
310
+ case rollbackIncomplete = "rollbackIncomplete"
311
+ case distanceNotAttributable = "distanceNotAttributable"
312
+ case bloodPressureIncomplete = "bloodPressureIncomplete"
313
+ case authorizationUnavailableInExtension = "authorizationUnavailableInExtension"
314
+ case routeAwaitingConverter = "routeAwaitingConverter"
315
+ /// Available on android only.
316
+ case readQuotaExceeded = "readQuotaExceeded"
317
+ /// Apple's FailureCode carries it; Android's does not, because there is no Android
318
+ /// planned-workout model.
319
+ ///
320
+ /// Available on ios only.
321
+ case plannedWorkoutUnnamed = "plannedWorkoutUnnamed"
322
+ }
323
+
324
+ /// Whether Terra's plans can be put on the person's Apple Watch. unsupported before iOS 17 or
325
+ /// where WorkoutKit cannot schedule.
326
+ ///
327
+ /// Available on ios only.
328
+ public enum WorkoutSchedulingStatus: String, Codable, Sendable, CaseIterable {
329
+ case unsupported = "unsupported"
330
+ case notDetermined = "notDetermined"
331
+ case denied = "denied"
332
+ case authorized = "authorized"
333
+ }
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
+
439
+ /// A `CodingKey` over a plain string, for the dictionary-key conformances below.
440
+ public struct StringCodingKey: CodingKey {
441
+ public let stringValue: String
442
+ public var intValue: Int? { nil }
443
+ public init(_ value: String) { stringValue = value }
444
+ public init?(stringValue: String) { self.stringValue = stringValue }
445
+ public init?(intValue: Int) { return nil }
446
+ }
447
+
448
+ /// So that a `[DataCategory: T]` encodes as a JSON object rather than an alternating array.
449
+ extension DataCategory: CodingKeyRepresentable {
450
+ public var codingKey: any CodingKey { StringCodingKey(rawValue) }
451
+ public init?<T: CodingKey>(codingKey: T) {
452
+ self.init(rawValue: codingKey.stringValue)
453
+ }
454
+ }
455
+
456
+ /// The person a connect is for. A stored link is restored only for the person it was made for;
457
+ /// anybody else gets a fresh link. Never sent to Terra as identity.
458
+ public enum EndUser: Codable, Equatable, Sendable {
459
+ case reference(id: String)
460
+ case anonymous
461
+
462
+ private enum CodingKeys: String, CodingKey {
463
+ case kind
464
+ case id
465
+ }
466
+
467
+ public init(from decoder: any Decoder) throws {
468
+ let container = try decoder.container(keyedBy: CodingKeys.self)
469
+ let tag = try container.decode(String.self, forKey: .kind)
470
+ switch tag {
471
+ case "reference":
472
+ let id = try container.decode(String.self, forKey: .id)
473
+ self = .reference(id: id)
474
+ case "anonymous":
475
+ self = .anonymous
476
+ default:
477
+ throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "unknown EndUser \(tag)")
478
+ }
479
+ }
480
+
481
+ public func encode(to encoder: any Encoder) throws {
482
+ var container = encoder.container(keyedBy: CodingKeys.self)
483
+ switch self {
484
+ case .reference(let id):
485
+ try container.encode("reference", forKey: .kind)
486
+ try container.encode(id, forKey: .id)
487
+ case .anonymous:
488
+ try container.encode("anonymous", forKey: .kind)
489
+ }
490
+ }
491
+ }
492
+
493
+ /// A half-open window [start, end). Resolved against the device's calendar natively.
494
+ public enum TimeRange: Codable, Equatable, Sendable {
495
+ case lastHours(hours: Int)
496
+ case lastDays(days: Int)
497
+ case today
498
+ case between(start: String, end: String)
499
+
500
+ private enum CodingKeys: String, CodingKey {
501
+ case kind
502
+ case hours
503
+ case days
504
+ case start
505
+ case end
506
+ }
507
+
508
+ public init(from decoder: any Decoder) throws {
509
+ let container = try decoder.container(keyedBy: CodingKeys.self)
510
+ let tag = try container.decode(String.self, forKey: .kind)
511
+ switch tag {
512
+ case "lastHours":
513
+ let hours = try container.decode(Int.self, forKey: .hours)
514
+ self = .lastHours(hours: hours)
515
+ case "lastDays":
516
+ let days = try container.decode(Int.self, forKey: .days)
517
+ self = .lastDays(days: days)
518
+ case "today":
519
+ self = .today
520
+ case "between":
521
+ let start = try container.decode(String.self, forKey: .start)
522
+ let end = try container.decode(String.self, forKey: .end)
523
+ self = .between(start: start, end: end)
524
+ default:
525
+ throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "unknown TimeRange \(tag)")
526
+ }
527
+ }
528
+
529
+ public func encode(to encoder: any Encoder) throws {
530
+ var container = encoder.container(keyedBy: CodingKeys.self)
531
+ switch self {
532
+ case .lastHours(let hours):
533
+ try container.encode("lastHours", forKey: .kind)
534
+ try container.encode(hours, forKey: .hours)
535
+ case .lastDays(let days):
536
+ try container.encode("lastDays", forKey: .kind)
537
+ try container.encode(days, forKey: .days)
538
+ case .today:
539
+ try container.encode("today", forKey: .kind)
540
+ case .between(let start, let end):
541
+ try container.encode("between", forKey: .kind)
542
+ try container.encode(start, forKey: .start)
543
+ try container.encode(end, forKey: .end)
544
+ }
545
+ }
546
+ }
547
+
548
+ public enum SleepStage: Codable, Equatable, Sendable {
549
+ case inBed
550
+ case awake
551
+ case asleep
552
+ case light
553
+ case deep
554
+ case rem
555
+ case unknown(raw: String)
556
+
557
+ private enum CodingKeys: String, CodingKey {
558
+ case kind
559
+ case raw
560
+ }
561
+
562
+ public init(from decoder: any Decoder) throws {
563
+ let container = try decoder.container(keyedBy: CodingKeys.self)
564
+ let tag = try container.decode(String.self, forKey: .kind)
565
+ switch tag {
566
+ case "inBed":
567
+ self = .inBed
568
+ case "awake":
569
+ self = .awake
570
+ case "asleep":
571
+ self = .asleep
572
+ case "light":
573
+ self = .light
574
+ case "deep":
575
+ self = .deep
576
+ case "rem":
577
+ self = .rem
578
+ case "unknown":
579
+ let raw = try container.decode(String.self, forKey: .raw)
580
+ self = .unknown(raw: raw)
581
+ default:
582
+ throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "unknown SleepStage \(tag)")
583
+ }
584
+ }
585
+
586
+ public func encode(to encoder: any Encoder) throws {
587
+ var container = encoder.container(keyedBy: CodingKeys.self)
588
+ switch self {
589
+ case .inBed:
590
+ try container.encode("inBed", forKey: .kind)
591
+ case .awake:
592
+ try container.encode("awake", forKey: .kind)
593
+ case .asleep:
594
+ try container.encode("asleep", forKey: .kind)
595
+ case .light:
596
+ try container.encode("light", forKey: .kind)
597
+ case .deep:
598
+ try container.encode("deep", forKey: .kind)
599
+ case .rem:
600
+ try container.encode("rem", forKey: .kind)
601
+ case .unknown(let raw):
602
+ try container.encode("unknown", forKey: .kind)
603
+ try container.encode(raw, forKey: .raw)
604
+ }
605
+ }
606
+ }
607
+
608
+ public enum WorkoutActivity: Codable, Equatable, Sendable {
609
+ case running
610
+ case walking
611
+ case cycling
612
+ case swimming
613
+ case strengthTraining
614
+ case other(label: String)
615
+
616
+ private enum CodingKeys: String, CodingKey {
617
+ case kind
618
+ case label
619
+ }
620
+
621
+ public init(from decoder: any Decoder) throws {
622
+ let container = try decoder.container(keyedBy: CodingKeys.self)
623
+ let tag = try container.decode(String.self, forKey: .kind)
624
+ switch tag {
625
+ case "running":
626
+ self = .running
627
+ case "walking":
628
+ self = .walking
629
+ case "cycling":
630
+ self = .cycling
631
+ case "swimming":
632
+ self = .swimming
633
+ case "strengthTraining":
634
+ self = .strengthTraining
635
+ case "other":
636
+ let label = try container.decode(String.self, forKey: .label)
637
+ self = .other(label: label)
638
+ default:
639
+ throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "unknown WorkoutActivity \(tag)")
640
+ }
641
+ }
642
+
643
+ public func encode(to encoder: any Encoder) throws {
644
+ var container = encoder.container(keyedBy: CodingKeys.self)
645
+ switch self {
646
+ case .running:
647
+ try container.encode("running", forKey: .kind)
648
+ case .walking:
649
+ try container.encode("walking", forKey: .kind)
650
+ case .cycling:
651
+ try container.encode("cycling", forKey: .kind)
652
+ case .swimming:
653
+ try container.encode("swimming", forKey: .kind)
654
+ case .strengthTraining:
655
+ try container.encode("strengthTraining", forKey: .kind)
656
+ case .other(let label):
657
+ try container.encode("other", forKey: .kind)
658
+ try container.encode(label, forKey: .label)
659
+ }
660
+ }
661
+ }
662
+
663
+ /// What the SDK believes about delivery right now. Wire spelling matches DiagnosticsWire.state
664
+ /// and Apple's DiagnosticsState Encodable.
665
+ public enum DiagnosticsState: Codable, Equatable, Sendable {
666
+ case healthy
667
+ case quiet(since: String)
668
+ case backgroundRestricted(reason: BackgroundRestrictionReason?)
669
+ case backgroundRefreshUnscheduled
670
+ case pendingRetry
671
+ case linkRevoked
672
+ case devIDMismatch
673
+
674
+ private enum CodingKeys: String, CodingKey {
675
+ case state
676
+ case since
677
+ case reason
678
+ }
679
+
680
+ public init(from decoder: any Decoder) throws {
681
+ let container = try decoder.container(keyedBy: CodingKeys.self)
682
+ let tag = try container.decode(String.self, forKey: .state)
683
+ switch tag {
684
+ case "healthy":
685
+ self = .healthy
686
+ case "quiet":
687
+ let since = try container.decode(String.self, forKey: .since)
688
+ self = .quiet(since: since)
689
+ case "backgroundRestricted":
690
+ let reason = try container.decodeIfPresent(BackgroundRestrictionReason.self, forKey: .reason)
691
+ self = .backgroundRestricted(reason: reason)
692
+ case "backgroundRefreshUnscheduled":
693
+ self = .backgroundRefreshUnscheduled
694
+ case "pendingRetry":
695
+ self = .pendingRetry
696
+ case "linkRevoked":
697
+ self = .linkRevoked
698
+ case "devIDMismatch":
699
+ self = .devIDMismatch
700
+ default:
701
+ throw DecodingError.dataCorruptedError(forKey: .state, in: container, debugDescription: "unknown DiagnosticsState \(tag)")
702
+ }
703
+ }
704
+
705
+ public func encode(to encoder: any Encoder) throws {
706
+ var container = encoder.container(keyedBy: CodingKeys.self)
707
+ switch self {
708
+ case .healthy:
709
+ try container.encode("healthy", forKey: .state)
710
+ case .quiet(let since):
711
+ try container.encode("quiet", forKey: .state)
712
+ try container.encode(since, forKey: .since)
713
+ case .backgroundRestricted(let reason):
714
+ try container.encode("backgroundRestricted", forKey: .state)
715
+ try container.encodeIfPresent(reason, forKey: .reason)
716
+ case .backgroundRefreshUnscheduled:
717
+ try container.encode("backgroundRefreshUnscheduled", forKey: .state)
718
+ case .pendingRetry:
719
+ try container.encode("pendingRetry", forKey: .state)
720
+ case .linkRevoked:
721
+ try container.encode("linkRevoked", forKey: .state)
722
+ case .devIDMismatch:
723
+ try container.encode("devIDMismatch", forKey: .state)
724
+ }
725
+ }
726
+ }
727
+
728
+ /// What a subscription delivers. A stream can fail or finish, and without saying so the
729
+ /// framework layer has to invent a convention for both - two of those, one per wrapper, is a
730
+ /// contract nobody wrote down.
731
+ public enum StreamMessage: Codable, Equatable, Sendable {
732
+ case data(element: BridgeJSON)
733
+ case error(error: TerraErrorPayload)
734
+ case complete
735
+
736
+ private enum CodingKeys: String, CodingKey {
737
+ case kind
738
+ case element
739
+ case error
740
+ }
741
+
742
+ public init(from decoder: any Decoder) throws {
743
+ let container = try decoder.container(keyedBy: CodingKeys.self)
744
+ let tag = try container.decode(String.self, forKey: .kind)
745
+ switch tag {
746
+ case "data":
747
+ let element = try container.decode(BridgeJSON.self, forKey: .element)
748
+ self = .data(element: element)
749
+ case "error":
750
+ let error = try container.decode(TerraErrorPayload.self, forKey: .error)
751
+ self = .error(error: error)
752
+ case "complete":
753
+ self = .complete
754
+ default:
755
+ throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "unknown StreamMessage \(tag)")
756
+ }
757
+ }
758
+
759
+ public func encode(to encoder: any Encoder) throws {
760
+ var container = encoder.container(keyedBy: CodingKeys.self)
761
+ switch self {
762
+ case .data(let element):
763
+ try container.encode("data", forKey: .kind)
764
+ try container.encode(element, forKey: .element)
765
+ case .error(let error):
766
+ try container.encode("error", forKey: .kind)
767
+ try container.encode(error, forKey: .error)
768
+ case .complete:
769
+ try container.encode("complete", forKey: .kind)
770
+ }
771
+ }
772
+ }
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
+
945
+ /// Who recorded a sample. The only stable handle an app has for filtering by app or device.
946
+ public struct SampleSource: Codable, Equatable, Sendable {
947
+ public var name: String
948
+ public var bundleId: String
949
+ public var productType: String?
950
+ public var deviceType: String?
951
+
952
+ public init(name: String, bundleId: String, productType: String? = nil, deviceType: String? = nil) {
953
+ self.name = name
954
+ self.bundleId = bundleId
955
+ self.productType = productType
956
+ self.deviceType = deviceType
957
+ }
958
+ }
959
+
960
+ /// A link record. A value, not a handle: Apple's Connection carries sync and disconnect
961
+ /// methods, and an object with behaviour cannot cross the bridge, so those are ops on the
962
+ /// client.
963
+ public struct Connection: Codable, Equatable, Sendable {
964
+ public var source: Source
965
+ public var userId: String
966
+ /// Android's StoredConnection carries the store-side id. Apple's Connection does not.
967
+ ///
968
+ /// Available on android only.
969
+ public var providerUserId: String?
970
+
971
+ public init(source: Source, userId: String, providerUserId: String? = nil) {
972
+ self.source = source
973
+ self.userId = userId
974
+ self.providerUserId = providerUserId
975
+ }
976
+ }
977
+
978
+ public struct HeartRateSample: Codable, Equatable, Sendable {
979
+ public var id: String
980
+ public var time: String
981
+ public var beatsPerMinute: Double
982
+ public var source: SampleSource
983
+ public var motionContext: MotionContext?
984
+ public var userEntered: Bool
985
+ public var timeZone: String?
986
+
987
+ public init(id: String, time: String, beatsPerMinute: Double, source: SampleSource, motionContext: MotionContext? = nil, userEntered: Bool, timeZone: String? = nil) {
988
+ self.id = id
989
+ self.time = time
990
+ self.beatsPerMinute = beatsPerMinute
991
+ self.source = source
992
+ self.motionContext = motionContext
993
+ self.userEntered = userEntered
994
+ self.timeZone = timeZone
995
+ }
996
+ }
997
+
998
+ /// A step total over an interval. Apple derives the id from the source and start; Android
999
+ /// carries a RecordId when it has one, so the id is optional on the wire.
1000
+ public struct StepCount: Codable, Equatable, Sendable {
1001
+ public var id: String?
1002
+ public var start: String
1003
+ public var end: String
1004
+ public var steps: Int
1005
+ public var source: SampleSource
1006
+ public var userEntered: Bool
1007
+ public var timeZone: String?
1008
+
1009
+ public init(id: String? = nil, start: String, end: String, steps: Int, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
1010
+ self.id = id
1011
+ self.start = start
1012
+ self.end = end
1013
+ self.steps = steps
1014
+ self.source = source
1015
+ self.userEntered = userEntered
1016
+ self.timeZone = timeZone
1017
+ }
1018
+ }
1019
+
1020
+ public struct SleepSegment: Codable, Equatable, Sendable {
1021
+ public var id: String
1022
+ public var start: String
1023
+ public var end: String
1024
+ public var stage: SleepStage
1025
+ public var source: SampleSource
1026
+ public var userEntered: Bool
1027
+ public var timeZone: String?
1028
+
1029
+ public init(id: String, start: String, end: String, stage: SleepStage, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
1030
+ self.id = id
1031
+ self.start = start
1032
+ self.end = end
1033
+ self.stage = stage
1034
+ self.source = source
1035
+ self.userEntered = userEntered
1036
+ self.timeZone = timeZone
1037
+ }
1038
+ }
1039
+
1040
+ /// indoor is optional because Health Connect does not always record it; Apple always has an
1041
+ /// answer.
1042
+ public struct Workout: Codable, Equatable, Sendable {
1043
+ public var id: String
1044
+ public var start: String
1045
+ public var end: String
1046
+ public var activity: WorkoutActivity
1047
+ public var activityTypeCode: Int
1048
+ public var indoor: Bool?
1049
+ public var activeEnergyKilocalories: Double?
1050
+ public var distanceMeters: Double?
1051
+ public var source: SampleSource
1052
+ public var userEntered: Bool
1053
+ public var timeZone: String?
1054
+ /// ExerciseSessionRecord.title on Android; HKMetadataKeyWorkoutBrandName on Apple, which has no
1055
+ /// title field.
1056
+ public var title: String?
1057
+
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) {
1059
+ self.id = id
1060
+ self.start = start
1061
+ self.end = end
1062
+ self.activity = activity
1063
+ self.activityTypeCode = activityTypeCode
1064
+ self.indoor = indoor
1065
+ self.activeEnergyKilocalories = activeEnergyKilocalories
1066
+ self.distanceMeters = distanceMeters
1067
+ self.source = source
1068
+ self.userEntered = userEntered
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
1141
+ }
1142
+ }
1143
+
1144
+ /// A day whose data changed. Carries no health value by construction.
1145
+ public struct HealthChange: Codable, Equatable, Sendable {
1146
+ public var category: DataCategory
1147
+ public var day: String
1148
+
1149
+ public init(category: DataCategory, day: String) {
1150
+ self.category = category
1151
+ self.day = day
1152
+ }
1153
+ }
1154
+
1155
+ public struct WorkoutScope: Codable, Equatable, Sendable {
1156
+ public var activity: WorkoutActivity
1157
+ public var energy: Bool
1158
+ public var distance: Bool
1159
+
1160
+ public init(activity: WorkoutActivity, energy: Bool, distance: Bool) {
1161
+ self.activity = activity
1162
+ self.energy = energy
1163
+ self.distance = distance
1164
+ }
1165
+ }
1166
+
1167
+ /// Lists rather than sets: JSON has no set, so the category lists are marked `set` and every
1168
+ /// generated codec de-duplicates and sorts them, so two orderings compare equal.
1169
+ public struct HealthAccess: Codable, Equatable, Sendable {
1170
+ @CaseSet public var read: [DataCategory]
1171
+ @CaseSet public var write: [DataCategory]
1172
+ public var workouts: [WorkoutScope]
1173
+
1174
+ public init(read: [DataCategory], write: [DataCategory], workouts: [WorkoutScope]) {
1175
+ self.read = read
1176
+ self.write = write
1177
+ self.workouts = workouts
1178
+ }
1179
+ }
1180
+
1181
+ public struct HeartRateEntry: Codable, Equatable, Sendable {
1182
+ public var time: String
1183
+ public var beatsPerMinute: Double
1184
+ public var recordId: String?
1185
+
1186
+ public init(time: String, beatsPerMinute: Double, recordId: String? = nil) {
1187
+ self.time = time
1188
+ self.beatsPerMinute = beatsPerMinute
1189
+ self.recordId = recordId
1190
+ }
1191
+ }
1192
+
1193
+ public struct StepEntry: Codable, Equatable, Sendable {
1194
+ public var start: String
1195
+ public var end: String
1196
+ public var steps: Int
1197
+ public var recordId: String?
1198
+
1199
+ public init(start: String, end: String, steps: Int, recordId: String? = nil) {
1200
+ self.start = start
1201
+ self.end = end
1202
+ self.steps = steps
1203
+ self.recordId = recordId
1204
+ }
1205
+ }
1206
+
1207
+ public struct SleepEntry: Codable, Equatable, Sendable {
1208
+ public var start: String
1209
+ public var end: String
1210
+ public var stage: SleepStage
1211
+ public var recordId: String?
1212
+
1213
+ public init(start: String, end: String, stage: SleepStage, recordId: String? = nil) {
1214
+ self.start = start
1215
+ self.end = end
1216
+ self.stage = stage
1217
+ self.recordId = recordId
1218
+ }
1219
+ }
1220
+
1221
+ public struct WorkoutEntry: Codable, Equatable, Sendable {
1222
+ public var start: String
1223
+ public var end: String
1224
+ public var activity: WorkoutActivity
1225
+ public var totalEnergyKilocalories: Double?
1226
+ public var totalDistanceMeters: Double?
1227
+ public var recordId: String?
1228
+
1229
+ public init(start: String, end: String, activity: WorkoutActivity, totalEnergyKilocalories: Double? = nil, totalDistanceMeters: Double? = nil, recordId: String? = nil) {
1230
+ self.start = start
1231
+ self.end = end
1232
+ self.activity = activity
1233
+ self.totalEnergyKilocalories = totalEnergyKilocalories
1234
+ self.totalDistanceMeters = totalDistanceMeters
1235
+ self.recordId = recordId
1236
+ }
1237
+ }
1238
+
1239
+ public struct BodyEntry: Codable, Equatable, Sendable {
1240
+ public var time: String
1241
+ public var bodyMassKilograms: Double?
1242
+ public var bodyFatPercent: Double?
1243
+ public var bodyMassIndex: Double?
1244
+ public var heightMeters: Double?
1245
+ public var leanBodyMassKilograms: Double?
1246
+ public var bodyTemperatureCelsius: Double?
1247
+ public var oxygenSaturationPercent: Double?
1248
+ public var bloodGlucoseMilligramsPerDeciliter: Double?
1249
+ public var bloodPressureSystolicMillimetersOfMercury: Double?
1250
+ public var bloodPressureDiastolicMillimetersOfMercury: Double?
1251
+ public var recordId: String?
1252
+
1253
+ public init(time: String, bodyMassKilograms: Double? = nil, bodyFatPercent: Double? = nil, bodyMassIndex: Double? = nil, heightMeters: Double? = nil, leanBodyMassKilograms: Double? = nil, bodyTemperatureCelsius: Double? = nil, oxygenSaturationPercent: Double? = nil, bloodGlucoseMilligramsPerDeciliter: Double? = nil, bloodPressureSystolicMillimetersOfMercury: Double? = nil, bloodPressureDiastolicMillimetersOfMercury: Double? = nil, recordId: String? = nil) {
1254
+ self.time = time
1255
+ self.bodyMassKilograms = bodyMassKilograms
1256
+ self.bodyFatPercent = bodyFatPercent
1257
+ self.bodyMassIndex = bodyMassIndex
1258
+ self.heightMeters = heightMeters
1259
+ self.leanBodyMassKilograms = leanBodyMassKilograms
1260
+ self.bodyTemperatureCelsius = bodyTemperatureCelsius
1261
+ self.oxygenSaturationPercent = oxygenSaturationPercent
1262
+ self.bloodGlucoseMilligramsPerDeciliter = bloodGlucoseMilligramsPerDeciliter
1263
+ self.bloodPressureSystolicMillimetersOfMercury = bloodPressureSystolicMillimetersOfMercury
1264
+ self.bloodPressureDiastolicMillimetersOfMercury = bloodPressureDiastolicMillimetersOfMercury
1265
+ self.recordId = recordId
1266
+ }
1267
+ }
1268
+
1269
+ /// Every field optional and every unit in the name, matching NutritionEntry.Nutrients on both
1270
+ /// platforms field for field.
1271
+ public struct Nutrients: Codable, Equatable, Sendable {
1272
+ public var energyKilocalories: Double?
1273
+ public var proteinGrams: Double?
1274
+ public var carbohydratesGrams: Double?
1275
+ public var fiberGrams: Double?
1276
+ public var sugarGrams: Double?
1277
+ public var fatTotalGrams: Double?
1278
+ public var fatSaturatedGrams: Double?
1279
+ public var fatMonounsaturatedGrams: Double?
1280
+ public var fatPolyunsaturatedGrams: Double?
1281
+ public var cholesterolMilligrams: Double?
1282
+ public var sodiumMilligrams: Double?
1283
+ public var potassiumMilligrams: Double?
1284
+ public var calciumMilligrams: Double?
1285
+ public var ironMilligrams: Double?
1286
+ public var magnesiumMilligrams: Double?
1287
+ public var zincMilligrams: Double?
1288
+ public var waterLiters: Double?
1289
+ public var caffeineMilligrams: Double?
1290
+ public var vitaminAMicrograms: Double?
1291
+ public var vitaminCMilligrams: Double?
1292
+ public var vitaminDMicrograms: Double?
1293
+ public var vitaminEMilligrams: Double?
1294
+ public var vitaminKMicrograms: Double?
1295
+ public var vitaminB6Milligrams: Double?
1296
+ public var vitaminB12Micrograms: Double?
1297
+ public var folateMicrograms: Double?
1298
+ public var thiaminMilligrams: Double?
1299
+ public var riboflavinMilligrams: Double?
1300
+ public var niacinMilligrams: Double?
1301
+ public var biotinMicrograms: Double?
1302
+ public var pantothenicAcidMilligrams: Double?
1303
+ public var phosphorusMilligrams: Double?
1304
+ public var iodineMicrograms: Double?
1305
+ public var chromiumMicrograms: Double?
1306
+ public var copperMilligrams: Double?
1307
+ public var manganeseMilligrams: Double?
1308
+ public var molybdenumMicrograms: Double?
1309
+ public var seleniumMicrograms: Double?
1310
+ public var chlorideMilligrams: Double?
1311
+
1312
+ public init(energyKilocalories: Double? = nil, proteinGrams: Double? = nil, carbohydratesGrams: Double? = nil, fiberGrams: Double? = nil, sugarGrams: Double? = nil, fatTotalGrams: Double? = nil, fatSaturatedGrams: Double? = nil, fatMonounsaturatedGrams: Double? = nil, fatPolyunsaturatedGrams: Double? = nil, cholesterolMilligrams: Double? = nil, sodiumMilligrams: Double? = nil, potassiumMilligrams: Double? = nil, calciumMilligrams: Double? = nil, ironMilligrams: Double? = nil, magnesiumMilligrams: Double? = nil, zincMilligrams: Double? = nil, waterLiters: Double? = nil, caffeineMilligrams: Double? = nil, vitaminAMicrograms: Double? = nil, vitaminCMilligrams: Double? = nil, vitaminDMicrograms: Double? = nil, vitaminEMilligrams: Double? = nil, vitaminKMicrograms: Double? = nil, vitaminB6Milligrams: Double? = nil, vitaminB12Micrograms: Double? = nil, folateMicrograms: Double? = nil, thiaminMilligrams: Double? = nil, riboflavinMilligrams: Double? = nil, niacinMilligrams: Double? = nil, biotinMicrograms: Double? = nil, pantothenicAcidMilligrams: Double? = nil, phosphorusMilligrams: Double? = nil, iodineMicrograms: Double? = nil, chromiumMicrograms: Double? = nil, copperMilligrams: Double? = nil, manganeseMilligrams: Double? = nil, molybdenumMicrograms: Double? = nil, seleniumMicrograms: Double? = nil, chlorideMilligrams: Double? = nil) {
1313
+ self.energyKilocalories = energyKilocalories
1314
+ self.proteinGrams = proteinGrams
1315
+ self.carbohydratesGrams = carbohydratesGrams
1316
+ self.fiberGrams = fiberGrams
1317
+ self.sugarGrams = sugarGrams
1318
+ self.fatTotalGrams = fatTotalGrams
1319
+ self.fatSaturatedGrams = fatSaturatedGrams
1320
+ self.fatMonounsaturatedGrams = fatMonounsaturatedGrams
1321
+ self.fatPolyunsaturatedGrams = fatPolyunsaturatedGrams
1322
+ self.cholesterolMilligrams = cholesterolMilligrams
1323
+ self.sodiumMilligrams = sodiumMilligrams
1324
+ self.potassiumMilligrams = potassiumMilligrams
1325
+ self.calciumMilligrams = calciumMilligrams
1326
+ self.ironMilligrams = ironMilligrams
1327
+ self.magnesiumMilligrams = magnesiumMilligrams
1328
+ self.zincMilligrams = zincMilligrams
1329
+ self.waterLiters = waterLiters
1330
+ self.caffeineMilligrams = caffeineMilligrams
1331
+ self.vitaminAMicrograms = vitaminAMicrograms
1332
+ self.vitaminCMilligrams = vitaminCMilligrams
1333
+ self.vitaminDMicrograms = vitaminDMicrograms
1334
+ self.vitaminEMilligrams = vitaminEMilligrams
1335
+ self.vitaminKMicrograms = vitaminKMicrograms
1336
+ self.vitaminB6Milligrams = vitaminB6Milligrams
1337
+ self.vitaminB12Micrograms = vitaminB12Micrograms
1338
+ self.folateMicrograms = folateMicrograms
1339
+ self.thiaminMilligrams = thiaminMilligrams
1340
+ self.riboflavinMilligrams = riboflavinMilligrams
1341
+ self.niacinMilligrams = niacinMilligrams
1342
+ self.biotinMicrograms = biotinMicrograms
1343
+ self.pantothenicAcidMilligrams = pantothenicAcidMilligrams
1344
+ self.phosphorusMilligrams = phosphorusMilligrams
1345
+ self.iodineMicrograms = iodineMicrograms
1346
+ self.chromiumMicrograms = chromiumMicrograms
1347
+ self.copperMilligrams = copperMilligrams
1348
+ self.manganeseMilligrams = manganeseMilligrams
1349
+ self.molybdenumMicrograms = molybdenumMicrograms
1350
+ self.seleniumMicrograms = seleniumMicrograms
1351
+ self.chlorideMilligrams = chlorideMilligrams
1352
+ }
1353
+ }
1354
+
1355
+ public struct NutritionEntry: Codable, Equatable, Sendable {
1356
+ public var start: String
1357
+ public var end: String
1358
+ public var name: String?
1359
+ public var nutrients: Nutrients
1360
+ public var recordId: String?
1361
+
1362
+ public init(start: String, end: String, name: String? = nil, nutrients: Nutrients, recordId: String? = nil) {
1363
+ self.start = start
1364
+ self.end = end
1365
+ self.name = name
1366
+ self.nutrients = nutrients
1367
+ self.recordId = recordId
1368
+ }
1369
+ }
1370
+
1371
+ /// An error carried as a value rather than thrown, inside a sync result. The same shape the
1372
+ /// bridge rejects a promise with.
1373
+ public struct TerraErrorPayload: Codable, Equatable, Sendable {
1374
+ public var code: String
1375
+ public var retryable: Bool
1376
+ public var message: String
1377
+ public var source: Source?
1378
+ public var details: BridgeJSON?
1379
+
1380
+ public init(code: String, retryable: Bool, message: String, source: Source? = nil, details: BridgeJSON? = nil) {
1381
+ self.code = code
1382
+ self.retryable = retryable
1383
+ self.message = message
1384
+ self.source = source
1385
+ self.details = details
1386
+ }
1387
+ }
1388
+
1389
+ /// Available on ios only.
1390
+ public struct PlannedWorkout: Codable, Equatable, Sendable {
1391
+ public var id: String
1392
+ public var plannedOn: String
1393
+ public var activity: WorkoutActivity
1394
+ public var title: String?
1395
+ public var completedAt: String?
1396
+
1397
+ public init(id: String, plannedOn: String, activity: WorkoutActivity, title: String? = nil, completedAt: String? = nil) {
1398
+ self.id = id
1399
+ self.plannedOn = plannedOn
1400
+ self.activity = activity
1401
+ self.title = title
1402
+ self.completedAt = completedAt
1403
+ }
1404
+ }
1405
+
1406
+ /// The flat object both SDKs already emit: `at`, `event`, and whatever keys that kind carries.
1407
+ /// Carried through unchanged rather than reshaped, because reshaping it would be a hand-written
1408
+ /// second contract on each platform that no vector could pin - and the encoding it would be
1409
+ /// translating is one DiagnosticsWire and Apple's Encodable already agree on, and that
1410
+ /// EventPrivacyTest already guards against carrying a health value.
1411
+ public typealias TerraEvent = BridgeJSON
1412
+
1413
+ /// The redacted report, carried as the wire object DiagnosticsWire.report and Apple's
1414
+ /// SupportReport.json already produce.
1415
+ public struct SupportReport: Codable, Equatable, Sendable {
1416
+ public var sdkVersion: String
1417
+ public var generatedAt: String
1418
+ public var state: DiagnosticsState
1419
+ public var lastDelivery: String?
1420
+ public var isConnected: Bool
1421
+ public var requestedCategories: [String]
1422
+ public var requestedWrites: [String]
1423
+ public var requestedWorkouts: [String]
1424
+ public var recentEvents: [TerraEvent]
1425
+
1426
+ public init(sdkVersion: String, generatedAt: String, state: DiagnosticsState, lastDelivery: String? = nil, isConnected: Bool, requestedCategories: [String], requestedWrites: [String], requestedWorkouts: [String], recentEvents: [TerraEvent]) {
1427
+ self.sdkVersion = sdkVersion
1428
+ self.generatedAt = generatedAt
1429
+ self.state = state
1430
+ self.lastDelivery = lastDelivery
1431
+ self.isConnected = isConnected
1432
+ self.requestedCategories = requestedCategories
1433
+ self.requestedWrites = requestedWrites
1434
+ self.requestedWorkouts = requestedWorkouts
1435
+ self.recentEvents = recentEvents
1436
+ }
1437
+ }
1438
+
1439
+ /// What the JS side needs to know about the native client it is attaching to. bootstrapped
1440
+ /// false is a host setup bug, and the wrapper says so with the exact line to add.
1441
+ public struct BootstrapState: Codable, Equatable, Sendable {
1442
+ public var bootstrapped: Bool
1443
+ public var devId: String?
1444
+ public var platform: String
1445
+ public var sdkVersion: String
1446
+ public var backgroundRegistered: Bool
1447
+
1448
+ public init(bootstrapped: Bool, devId: String? = nil, platform: String, sdkVersion: String, backgroundRegistered: Bool) {
1449
+ self.bootstrapped = bootstrapped
1450
+ self.devId = devId
1451
+ self.platform = platform
1452
+ self.sdkVersion = sdkVersion
1453
+ self.backgroundRegistered = backgroundRegistered
1454
+ }
1455
+ }
1456
+
1457
+ /// The broker asking JS for a link token. Answered by token.resolve or token.reject with the
1458
+ /// same requestId.
1459
+ public struct TokenRequest: Codable, Equatable, Sendable {
1460
+ public var requestId: String
1461
+
1462
+ public init(requestId: String) {
1463
+ self.requestId = requestId
1464
+ }
1465
+ }
1466
+
1467
+ /// No request fields, or no response value.
1468
+ public struct Empty: Codable, Equatable, Sendable {
1469
+ public init() {}
1470
+ }
1471
+
1472
+ public struct SourceRequest: Codable, Equatable, Sendable {
1473
+ public var source: Source?
1474
+
1475
+ public init(source: Source? = nil) {
1476
+ self.source = source
1477
+ }
1478
+ }
1479
+
1480
+ public struct RangeRequest: Codable, Equatable, Sendable {
1481
+ public var range: TimeRange
1482
+
1483
+ public init(range: TimeRange) {
1484
+ self.range = range
1485
+ }
1486
+ }
1487
+
1488
+ public struct CategoryRequest: Codable, Equatable, Sendable {
1489
+ public var category: DataCategory
1490
+ public var source: Source?
1491
+
1492
+ public init(category: DataCategory, source: Source? = nil) {
1493
+ self.category = category
1494
+ self.source = source
1495
+ }
1496
+ }
1497
+
1498
+ public struct RecordIdRequest: Codable, Equatable, Sendable {
1499
+ public var id: String
1500
+
1501
+ public init(id: String) {
1502
+ self.id = id
1503
+ }
1504
+ }
1505
+
1506
+ public struct ConnectRequest: Codable, Equatable, Sendable {
1507
+ public var source: Source?
1508
+ public var endUser: EndUser
1509
+ /// A grant already in hand, from a QR code or a deep link. Absent means the token provider is
1510
+ /// asked.
1511
+ public var token: String?
1512
+
1513
+ public init(source: Source? = nil, endUser: EndUser, token: String? = nil) {
1514
+ self.source = source
1515
+ self.endUser = endUser
1516
+ self.token = token
1517
+ }
1518
+ }
1519
+
1520
+ public struct PermissionRequest: Codable, Equatable, Sendable {
1521
+ @CaseSet public var read: [DataCategory]
1522
+ @CaseSet public var write: [DataCategory]
1523
+ public var workouts: [WorkoutScope]
1524
+ public var source: Source?
1525
+
1526
+ public init(read: [DataCategory], write: [DataCategory], workouts: [WorkoutScope], source: Source? = nil) {
1527
+ self.read = read
1528
+ self.write = write
1529
+ self.workouts = workouts
1530
+ self.source = source
1531
+ }
1532
+ }
1533
+
1534
+ /// What the sheet granted. Android answers with the access this call obtained; HealthKit will
1535
+ /// not report read grants, so Apple answers nothing at all.
1536
+ public struct PermissionGrant: Codable, Equatable, Sendable {
1537
+ /// Available on android only.
1538
+ public var granted: HealthAccess?
1539
+
1540
+ public init(granted: HealthAccess? = nil) {
1541
+ self.granted = granted
1542
+ }
1543
+ }
1544
+
1545
+ public struct TokenResolveRequest: Codable, Equatable, Sendable {
1546
+ public var requestId: String
1547
+ public var token: String
1548
+
1549
+ public init(requestId: String, token: String) {
1550
+ self.requestId = requestId
1551
+ self.token = token
1552
+ }
1553
+ }
1554
+
1555
+ public struct TokenRejectRequest: Codable, Equatable, Sendable {
1556
+ public var requestId: String
1557
+ public var message: String
1558
+
1559
+ public init(requestId: String, message: String) {
1560
+ self.requestId = requestId
1561
+ self.message = message
1562
+ }
1563
+ }
1564
+
1565
+ public struct TokenProviderRequest: Codable, Equatable, Sendable {
1566
+ public var present: Bool
1567
+ public var timeoutMs: Int
1568
+
1569
+ public init(present: Bool, timeoutMs: Int) {
1570
+ self.present = present
1571
+ self.timeoutMs = timeoutMs
1572
+ }
1573
+ }
1574
+
1575
+ /// Available on ios only.
1576
+ public struct MarkCompleteRequest: Codable, Equatable, Sendable {
1577
+ public var id: String
1578
+ public var at: String?
1579
+
1580
+ public init(id: String, at: String? = nil) {
1581
+ self.id = id
1582
+ self.at = at
1583
+ }
1584
+ }
1585
+
1586
+ public struct BoolResult: Codable, Equatable, Sendable {
1587
+ public var value: Bool
1588
+
1589
+ public init(value: Bool) {
1590
+ self.value = value
1591
+ }
1592
+ }
1593
+
1594
+ public struct StatusResult: Codable, Equatable, Sendable {
1595
+ public var status: PermissionStatus
1596
+
1597
+ public init(status: PermissionStatus) {
1598
+ self.status = status
1599
+ }
1600
+ }
1601
+
1602
+ /// Available on android only.
1603
+ public struct StatusesResult: Codable, Equatable, Sendable {
1604
+ public var statuses: [DataCategory: PermissionStatus]?
1605
+
1606
+ public init(statuses: [DataCategory: PermissionStatus]? = nil) {
1607
+ self.statuses = statuses
1608
+ }
1609
+ }
1610
+
1611
+ public struct RecordIdList: Codable, Equatable, Sendable {
1612
+ public var ids: [String]
1613
+
1614
+ public init(ids: [String]) {
1615
+ self.ids = ids
1616
+ }
1617
+ }
1618
+
1619
+ public struct ConnectionList: Codable, Equatable, Sendable {
1620
+ public var connections: [Connection]
1621
+
1622
+ public init(connections: [Connection]) {
1623
+ self.connections = connections
1624
+ }
1625
+ }
1626
+
1627
+ public struct HeartRateList: Codable, Equatable, Sendable {
1628
+ public var samples: [HeartRateSample]
1629
+
1630
+ public init(samples: [HeartRateSample]) {
1631
+ self.samples = samples
1632
+ }
1633
+ }
1634
+
1635
+ public struct StepCountList: Codable, Equatable, Sendable {
1636
+ public var samples: [StepCount]
1637
+
1638
+ public init(samples: [StepCount]) {
1639
+ self.samples = samples
1640
+ }
1641
+ }
1642
+
1643
+ public struct SleepSegmentList: Codable, Equatable, Sendable {
1644
+ public var samples: [SleepSegment]
1645
+
1646
+ public init(samples: [SleepSegment]) {
1647
+ self.samples = samples
1648
+ }
1649
+ }
1650
+
1651
+ public struct WorkoutList: Codable, Equatable, Sendable {
1652
+ public var samples: [Workout]
1653
+
1654
+ public init(samples: [Workout]) {
1655
+ self.samples = samples
1656
+ }
1657
+ }
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
+
1720
+ /// Available on ios only.
1721
+ public struct PlannedWorkoutList: Codable, Equatable, Sendable {
1722
+ public var workouts: [PlannedWorkout]
1723
+
1724
+ public init(workouts: [PlannedWorkout]) {
1725
+ self.workouts = workouts
1726
+ }
1727
+ }
1728
+
1729
+ /// Once authorized, every sync run keeps the Watch schedule in step with Terra.
1730
+ ///
1731
+ /// Available on ios only.
1732
+ public struct WorkoutScheduling: Codable, Equatable, Sendable {
1733
+ public var status: WorkoutSchedulingStatus
1734
+
1735
+ public init(status: WorkoutSchedulingStatus) {
1736
+ self.status = status
1737
+ }
1738
+ }
1739
+
1740
+ public struct LastDeliveryResult: Codable, Equatable, Sendable {
1741
+ public var at: String?
1742
+
1743
+ public init(at: String? = nil) {
1744
+ self.at = at
1745
+ }
1746
+ }
1747
+
1748
+ public struct HeartRateEntryList: Codable, Equatable, Sendable {
1749
+ public var entries: [HeartRateEntry]
1750
+
1751
+ public init(entries: [HeartRateEntry]) {
1752
+ self.entries = entries
1753
+ }
1754
+ }
1755
+
1756
+ public struct BodyEntryList: Codable, Equatable, Sendable {
1757
+ public var entries: [BodyEntry]
1758
+
1759
+ public init(entries: [BodyEntry]) {
1760
+ self.entries = entries
1761
+ }
1762
+ }
1763
+
1764
+ public struct StepEntryList: Codable, Equatable, Sendable {
1765
+ public var entries: [StepEntry]
1766
+
1767
+ public init(entries: [StepEntry]) {
1768
+ self.entries = entries
1769
+ }
1770
+ }
1771
+
1772
+ public struct SleepEntryList: Codable, Equatable, Sendable {
1773
+ public var entries: [SleepEntry]
1774
+
1775
+ public init(entries: [SleepEntry]) {
1776
+ self.entries = entries
1777
+ }
1778
+ }
1779
+
1780
+ public struct WorkoutEntryList: Codable, Equatable, Sendable {
1781
+ public var entries: [WorkoutEntry]
1782
+
1783
+ public init(entries: [WorkoutEntry]) {
1784
+ self.entries = entries
1785
+ }
1786
+ }
1787
+
1788
+ public struct NutritionEntryList: Codable, Equatable, Sendable {
1789
+ public var entries: [NutritionEntry]
1790
+
1791
+ public init(entries: [NutritionEntry]) {
1792
+ self.entries = entries
1793
+ }
1794
+ }
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
+
2028
+ public enum TerraEventEnvelope {
2029
+ public static func validate(_ value: TerraEvent) throws {
2030
+ guard case .string = value.storage["at"] else {
2031
+ throw DecodingError.dataCorrupted(
2032
+ .init(codingPath: [], debugDescription: "TerraEvent.at is not a string")
2033
+ )
2034
+ }
2035
+ guard case .string = value.storage["event"] else {
2036
+ throw DecodingError.dataCorrupted(
2037
+ .init(codingPath: [], debugDescription: "TerraEvent.event is not a string")
2038
+ )
2039
+ }
2040
+ }
2041
+ }
2042
+
2043
+ public enum SupportReportEnvelope {
2044
+ public static func validate(_ value: SupportReport) throws {
2045
+ for level0 in value.recentEvents {
2046
+ try TerraEventEnvelope.validate(level0)
2047
+ }
2048
+ }
2049
+ }
2050
+