@tryterra/react-native 0.0.0-stage → 2.1.0

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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -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 +468 -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/Rejections.kt +150 -0
  11. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/StreamRegistry.kt +77 -0
  12. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridge.kt +86 -0
  13. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeOpsImpl.kt +258 -0
  14. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeStreamsImpl.kt +52 -0
  15. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TokenBroker.kt +198 -0
  16. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeErrors.kt +217 -0
  17. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeOps.kt +493 -0
  18. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeTypes.kt +897 -0
  19. package/android/src/healthConnectOnly/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +11 -0
  20. package/android/src/main/AndroidManifest.xml +24 -0
  21. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraBridgeModule.kt +96 -0
  22. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraReactNativePackage.kt +26 -0
  23. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraStartup.kt +46 -0
  24. package/android/src/samsung/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +13 -0
  25. package/docs/availability.md +246 -0
  26. package/frameworks/TerraSDK.xcframework/Info.plist +48 -0
  27. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Info.plist +0 -0
  28. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.abi.json +81513 -0
  29. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.package.swiftinterface +2150 -0
  30. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +2150 -0
  31. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  32. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftinterface +2127 -0
  33. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
  34. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/TerraSDK +0 -0
  35. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
  36. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
  37. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6473 -0
  38. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Info.plist +0 -0
  39. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +81513 -0
  40. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.package.swiftinterface +2150 -0
  41. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +2150 -0
  42. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  43. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +2127 -0
  44. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +81513 -0
  45. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.package.swiftinterface +2150 -0
  46. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +2150 -0
  47. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  48. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +2127 -0
  49. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
  50. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/TerraSDK +0 -0
  51. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/_CodeSignature/CodeResources +223 -0
  52. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
  53. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
  54. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6474 -0
  55. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/TerraSDK.yml +6392 -0
  56. package/ios/TerraBridgeCore/BridgeBootstrap.swift +89 -0
  57. package/ios/TerraBridgeCore/BridgeHost.swift +13 -0
  58. package/ios/TerraBridgeCore/Conversions.swift +467 -0
  59. package/ios/TerraBridgeCore/Generated/BridgeErrors.swift +179 -0
  60. package/ios/TerraBridgeCore/Generated/BridgeOps.swift +795 -0
  61. package/ios/TerraBridgeCore/Generated/BridgeTypes.swift +1317 -0
  62. package/ios/TerraBridgeCore/Locked.swift +15 -0
  63. package/ios/TerraBridgeCore/README.md +1 -0
  64. package/ios/TerraBridgeCore/Rejections.swift +130 -0
  65. package/ios/TerraBridgeCore/RuntimeAttachment.swift +60 -0
  66. package/ios/TerraBridgeCore/StreamRegistry.swift +59 -0
  67. package/ios/TerraBridgeCore/TerraBridge.swift +122 -0
  68. package/ios/TerraBridgeCore/TerraBridgeOpsImpl.swift +254 -0
  69. package/ios/TerraBridgeCore/TerraBridgeStreamsImpl.swift +60 -0
  70. package/ios/TerraBridgeCore/TokenBroker.swift +147 -0
  71. package/ios/TerraBridgeModule.mm +79 -0
  72. package/ios/TerraBridgeModuleImpl.swift +49 -0
  73. package/ios/TerraReactNative.swift +19 -0
  74. package/lib/EndUser.d.ts +12 -0
  75. package/lib/EndUser.js +13 -0
  76. package/lib/NativeTerraBridge.d.ts +23 -0
  77. package/lib/NativeTerraBridge.js +13 -0
  78. package/lib/Terra.d.ts +154 -0
  79. package/lib/Terra.js +280 -0
  80. package/lib/TerraError.d.ts +42 -0
  81. package/lib/TerraError.js +95 -0
  82. package/lib/createTerra.d.ts +8 -0
  83. package/lib/createTerra.js +19 -0
  84. package/lib/generated/client.d.ts +92 -0
  85. package/lib/generated/client.js +196 -0
  86. package/lib/generated/codecs.d.ts +135 -0
  87. package/lib/generated/codecs.js +1358 -0
  88. package/lib/generated/errors.d.ts +13 -0
  89. package/lib/generated/errors.js +68 -0
  90. package/lib/generated/ops.d.ts +13 -0
  91. package/lib/generated/ops.js +137 -0
  92. package/lib/generated/streams.d.ts +23 -0
  93. package/lib/generated/streams.js +58 -0
  94. package/lib/generated/types.d.ts +481 -0
  95. package/lib/generated/types.js +92 -0
  96. package/lib/index.d.ts +29 -0
  97. package/lib/index.js +55 -0
  98. package/package.json +71 -4
  99. package/src/EndUser.ts +15 -0
  100. package/src/NativeTerraBridge.ts +27 -0
  101. package/src/Terra.ts +462 -0
  102. package/src/TerraError.ts +116 -0
  103. package/src/createTerra.ts +18 -0
  104. package/src/generated/client.ts +243 -0
  105. package/src/generated/codecs.ts +1287 -0
  106. package/src/generated/errors.ts +113 -0
  107. package/src/generated/ops.ts +197 -0
  108. package/src/generated/streams.ts +74 -0
  109. package/src/generated/types.ts +667 -0
  110. package/src/index.ts +52 -0
@@ -0,0 +1,1317 @@
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
+ }
140
+
141
+ public enum PermissionStatus: String, Codable, Sendable, CaseIterable {
142
+ case granted = "granted"
143
+ case denied = "denied"
144
+ case notDetermined = "notDetermined"
145
+ /// A read grant HealthKit will not report. Not an error: it is the documented answer for reads
146
+ /// on iOS.
147
+ case notDisclosed = "notDisclosed"
148
+ case unsupported = "unsupported"
149
+ /// Android answers unsupported where Apple would answer unavailable.
150
+ ///
151
+ /// Available on ios only.
152
+ case unavailable = "unavailable"
153
+ }
154
+
155
+ public enum MotionContext: String, Codable, Sendable, CaseIterable {
156
+ case unknown = "unknown"
157
+ case sedentary = "sedentary"
158
+ case active = "active"
159
+ }
160
+
161
+ /// Why background delivery is restricted. Android discloses which; Apple reports the state
162
+ /// without a reason.
163
+ ///
164
+ /// Available on android only.
165
+ public enum BackgroundRestrictionReason: String, Codable, Sendable, CaseIterable {
166
+ case oemRestricted = "oemRestricted"
167
+ case backgroundReadDenied = "backgroundReadDenied"
168
+ case healthConnectFeatureUnavailable = "healthConnectFeatureUnavailable"
169
+ case storeFeatureUnavailable = "storeFeatureUnavailable"
170
+ case ticksNotRunning = "ticksNotRunning"
171
+ }
172
+
173
+ public enum DiagnosticsReason: String, Codable, Sendable, CaseIterable {
174
+ case uploadFailed = "uploadFailed"
175
+ case backfillAbandoned = "backfillAbandoned"
176
+ case backgroundObservationUnavailable = "backgroundObservationUnavailable"
177
+ case backgroundRestricted = "backgroundRestricted"
178
+ case backgroundRefreshUnscheduled = "backgroundRefreshUnscheduled"
179
+ case quiet = "quiet"
180
+ case requested = "requested"
181
+ }
182
+
183
+ public enum DeviceKeyRevokeOutcome: String, Codable, Sendable, CaseIterable {
184
+ case revoked = "revoked"
185
+ case alreadyRevoked = "alreadyRevoked"
186
+ case routeAbsent = "routeAbsent"
187
+ }
188
+
189
+ /// The redacted error name an event carries. Deliberately not TerraErrorCode: an event names
190
+ /// the failure without the detail bag, because a support report leaves the device.
191
+ public enum FailureCode: String, Codable, Sendable, CaseIterable {
192
+ case healthDataUnavailable = "healthDataUnavailable"
193
+ case notConnected = "notConnected"
194
+ case tokenUnavailable = "tokenUnavailable"
195
+ case tokenRejected = "tokenRejected"
196
+ case tokenProviderFailed = "tokenProviderFailed"
197
+ case offline = "offline"
198
+ case timedOut = "timedOut"
199
+ case serverError = "serverError"
200
+ case transport = "transport"
201
+ case malformedResponse = "malformedResponse"
202
+ case userLimitReached = "userLimitReached"
203
+ case upgradeRequired = "upgradeRequired"
204
+ case platform = "platform"
205
+ case deviceKeyUnavailable = "deviceKeyUnavailable"
206
+ case signatureRejected = "signatureRejected"
207
+ case linkRevoked = "linkRevoked"
208
+ case linkConflict = "linkConflict"
209
+ case clockSkewed = "clockSkewed"
210
+ case devIDMismatch = "devIDMismatch"
211
+ case syncAlreadyRunning = "syncAlreadyRunning"
212
+ case linkAlreadyRunning = "linkAlreadyRunning"
213
+ case storeLocked = "storeLocked"
214
+ case storeUnreadable = "storeUnreadable"
215
+ case deadlineExceeded = "deadlineExceeded"
216
+ case permissionDenied = "permissionDenied"
217
+ case authorizationNotDetermined = "authorizationNotDetermined"
218
+ case sampleNotFound = "sampleNotFound"
219
+ case rollbackIncomplete = "rollbackIncomplete"
220
+ case distanceNotAttributable = "distanceNotAttributable"
221
+ case bloodPressureIncomplete = "bloodPressureIncomplete"
222
+ case authorizationUnavailableInExtension = "authorizationUnavailableInExtension"
223
+ case routeAwaitingConverter = "routeAwaitingConverter"
224
+ /// Available on android only.
225
+ case readQuotaExceeded = "readQuotaExceeded"
226
+ /// Apple's FailureCode carries it; Android's does not, because there is no Android
227
+ /// planned-workout model.
228
+ ///
229
+ /// Available on ios only.
230
+ case plannedWorkoutUnnamed = "plannedWorkoutUnnamed"
231
+ }
232
+
233
+ /// Whether Terra's plans can be put on the person's Apple Watch. unsupported before iOS 17 or
234
+ /// where WorkoutKit cannot schedule.
235
+ ///
236
+ /// Available on ios only.
237
+ public enum WorkoutSchedulingStatus: String, Codable, Sendable, CaseIterable {
238
+ case unsupported = "unsupported"
239
+ case notDetermined = "notDetermined"
240
+ case denied = "denied"
241
+ case authorized = "authorized"
242
+ }
243
+
244
+ /// A `CodingKey` over a plain string, for the dictionary-key conformances below.
245
+ public struct StringCodingKey: CodingKey {
246
+ public let stringValue: String
247
+ public var intValue: Int? { nil }
248
+ public init(_ value: String) { stringValue = value }
249
+ public init?(stringValue: String) { self.stringValue = stringValue }
250
+ public init?(intValue: Int) { return nil }
251
+ }
252
+
253
+ /// So that a `[DataCategory: T]` encodes as a JSON object rather than an alternating array.
254
+ extension DataCategory: CodingKeyRepresentable {
255
+ public var codingKey: any CodingKey { StringCodingKey(rawValue) }
256
+ public init?<T: CodingKey>(codingKey: T) {
257
+ self.init(rawValue: codingKey.stringValue)
258
+ }
259
+ }
260
+
261
+ /// The person a connect is for. A stored link is restored only for the person it was made for;
262
+ /// anybody else gets a fresh link. Never sent to Terra as identity.
263
+ public enum EndUser: Codable, Equatable, Sendable {
264
+ case reference(id: String)
265
+ case anonymous
266
+
267
+ private enum CodingKeys: String, CodingKey {
268
+ case kind
269
+ case id
270
+ }
271
+
272
+ public init(from decoder: any Decoder) throws {
273
+ let container = try decoder.container(keyedBy: CodingKeys.self)
274
+ let tag = try container.decode(String.self, forKey: .kind)
275
+ switch tag {
276
+ case "reference":
277
+ let id = try container.decode(String.self, forKey: .id)
278
+ self = .reference(id: id)
279
+ case "anonymous":
280
+ self = .anonymous
281
+ default:
282
+ throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "unknown EndUser \(tag)")
283
+ }
284
+ }
285
+
286
+ public func encode(to encoder: any Encoder) throws {
287
+ var container = encoder.container(keyedBy: CodingKeys.self)
288
+ switch self {
289
+ case .reference(let id):
290
+ try container.encode("reference", forKey: .kind)
291
+ try container.encode(id, forKey: .id)
292
+ case .anonymous:
293
+ try container.encode("anonymous", forKey: .kind)
294
+ }
295
+ }
296
+ }
297
+
298
+ /// A half-open window [start, end). Resolved against the device's calendar natively.
299
+ public enum TimeRange: Codable, Equatable, Sendable {
300
+ case lastHours(hours: Int)
301
+ case lastDays(days: Int)
302
+ case today
303
+ case between(start: String, end: String)
304
+
305
+ private enum CodingKeys: String, CodingKey {
306
+ case kind
307
+ case hours
308
+ case days
309
+ case start
310
+ case end
311
+ }
312
+
313
+ public init(from decoder: any Decoder) throws {
314
+ let container = try decoder.container(keyedBy: CodingKeys.self)
315
+ let tag = try container.decode(String.self, forKey: .kind)
316
+ switch tag {
317
+ case "lastHours":
318
+ let hours = try container.decode(Int.self, forKey: .hours)
319
+ self = .lastHours(hours: hours)
320
+ case "lastDays":
321
+ let days = try container.decode(Int.self, forKey: .days)
322
+ self = .lastDays(days: days)
323
+ case "today":
324
+ self = .today
325
+ case "between":
326
+ let start = try container.decode(String.self, forKey: .start)
327
+ let end = try container.decode(String.self, forKey: .end)
328
+ self = .between(start: start, end: end)
329
+ default:
330
+ throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "unknown TimeRange \(tag)")
331
+ }
332
+ }
333
+
334
+ public func encode(to encoder: any Encoder) throws {
335
+ var container = encoder.container(keyedBy: CodingKeys.self)
336
+ switch self {
337
+ case .lastHours(let hours):
338
+ try container.encode("lastHours", forKey: .kind)
339
+ try container.encode(hours, forKey: .hours)
340
+ case .lastDays(let days):
341
+ try container.encode("lastDays", forKey: .kind)
342
+ try container.encode(days, forKey: .days)
343
+ case .today:
344
+ try container.encode("today", forKey: .kind)
345
+ case .between(let start, let end):
346
+ try container.encode("between", forKey: .kind)
347
+ try container.encode(start, forKey: .start)
348
+ try container.encode(end, forKey: .end)
349
+ }
350
+ }
351
+ }
352
+
353
+ public enum SleepStage: Codable, Equatable, Sendable {
354
+ case inBed
355
+ case awake
356
+ case asleep
357
+ case light
358
+ case deep
359
+ case rem
360
+ case unknown(raw: String)
361
+
362
+ private enum CodingKeys: String, CodingKey {
363
+ case kind
364
+ case raw
365
+ }
366
+
367
+ public init(from decoder: any Decoder) throws {
368
+ let container = try decoder.container(keyedBy: CodingKeys.self)
369
+ let tag = try container.decode(String.self, forKey: .kind)
370
+ switch tag {
371
+ case "inBed":
372
+ self = .inBed
373
+ case "awake":
374
+ self = .awake
375
+ case "asleep":
376
+ self = .asleep
377
+ case "light":
378
+ self = .light
379
+ case "deep":
380
+ self = .deep
381
+ case "rem":
382
+ self = .rem
383
+ case "unknown":
384
+ let raw = try container.decode(String.self, forKey: .raw)
385
+ self = .unknown(raw: raw)
386
+ default:
387
+ throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "unknown SleepStage \(tag)")
388
+ }
389
+ }
390
+
391
+ public func encode(to encoder: any Encoder) throws {
392
+ var container = encoder.container(keyedBy: CodingKeys.self)
393
+ switch self {
394
+ case .inBed:
395
+ try container.encode("inBed", forKey: .kind)
396
+ case .awake:
397
+ try container.encode("awake", forKey: .kind)
398
+ case .asleep:
399
+ try container.encode("asleep", forKey: .kind)
400
+ case .light:
401
+ try container.encode("light", forKey: .kind)
402
+ case .deep:
403
+ try container.encode("deep", forKey: .kind)
404
+ case .rem:
405
+ try container.encode("rem", forKey: .kind)
406
+ case .unknown(let raw):
407
+ try container.encode("unknown", forKey: .kind)
408
+ try container.encode(raw, forKey: .raw)
409
+ }
410
+ }
411
+ }
412
+
413
+ public enum WorkoutActivity: Codable, Equatable, Sendable {
414
+ case running
415
+ case walking
416
+ case cycling
417
+ case swimming
418
+ case strengthTraining
419
+ case other(label: String)
420
+
421
+ private enum CodingKeys: String, CodingKey {
422
+ case kind
423
+ case label
424
+ }
425
+
426
+ public init(from decoder: any Decoder) throws {
427
+ let container = try decoder.container(keyedBy: CodingKeys.self)
428
+ let tag = try container.decode(String.self, forKey: .kind)
429
+ switch tag {
430
+ case "running":
431
+ self = .running
432
+ case "walking":
433
+ self = .walking
434
+ case "cycling":
435
+ self = .cycling
436
+ case "swimming":
437
+ self = .swimming
438
+ case "strengthTraining":
439
+ self = .strengthTraining
440
+ case "other":
441
+ let label = try container.decode(String.self, forKey: .label)
442
+ self = .other(label: label)
443
+ default:
444
+ throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "unknown WorkoutActivity \(tag)")
445
+ }
446
+ }
447
+
448
+ public func encode(to encoder: any Encoder) throws {
449
+ var container = encoder.container(keyedBy: CodingKeys.self)
450
+ switch self {
451
+ case .running:
452
+ try container.encode("running", forKey: .kind)
453
+ case .walking:
454
+ try container.encode("walking", forKey: .kind)
455
+ case .cycling:
456
+ try container.encode("cycling", forKey: .kind)
457
+ case .swimming:
458
+ try container.encode("swimming", forKey: .kind)
459
+ case .strengthTraining:
460
+ try container.encode("strengthTraining", forKey: .kind)
461
+ case .other(let label):
462
+ try container.encode("other", forKey: .kind)
463
+ try container.encode(label, forKey: .label)
464
+ }
465
+ }
466
+ }
467
+
468
+ /// What the SDK believes about delivery right now. Wire spelling matches DiagnosticsWire.state
469
+ /// and Apple's DiagnosticsState Encodable.
470
+ public enum DiagnosticsState: Codable, Equatable, Sendable {
471
+ case healthy
472
+ case quiet(since: String)
473
+ case backgroundRestricted(reason: BackgroundRestrictionReason?)
474
+ case backgroundRefreshUnscheduled
475
+ case pendingRetry
476
+ case linkRevoked
477
+ case devIDMismatch
478
+
479
+ private enum CodingKeys: String, CodingKey {
480
+ case state
481
+ case since
482
+ case reason
483
+ }
484
+
485
+ public init(from decoder: any Decoder) throws {
486
+ let container = try decoder.container(keyedBy: CodingKeys.self)
487
+ let tag = try container.decode(String.self, forKey: .state)
488
+ switch tag {
489
+ case "healthy":
490
+ self = .healthy
491
+ case "quiet":
492
+ let since = try container.decode(String.self, forKey: .since)
493
+ self = .quiet(since: since)
494
+ case "backgroundRestricted":
495
+ let reason = try container.decodeIfPresent(BackgroundRestrictionReason.self, forKey: .reason)
496
+ self = .backgroundRestricted(reason: reason)
497
+ case "backgroundRefreshUnscheduled":
498
+ self = .backgroundRefreshUnscheduled
499
+ case "pendingRetry":
500
+ self = .pendingRetry
501
+ case "linkRevoked":
502
+ self = .linkRevoked
503
+ case "devIDMismatch":
504
+ self = .devIDMismatch
505
+ default:
506
+ throw DecodingError.dataCorruptedError(forKey: .state, in: container, debugDescription: "unknown DiagnosticsState \(tag)")
507
+ }
508
+ }
509
+
510
+ public func encode(to encoder: any Encoder) throws {
511
+ var container = encoder.container(keyedBy: CodingKeys.self)
512
+ switch self {
513
+ case .healthy:
514
+ try container.encode("healthy", forKey: .state)
515
+ case .quiet(let since):
516
+ try container.encode("quiet", forKey: .state)
517
+ try container.encode(since, forKey: .since)
518
+ case .backgroundRestricted(let reason):
519
+ try container.encode("backgroundRestricted", forKey: .state)
520
+ try container.encodeIfPresent(reason, forKey: .reason)
521
+ case .backgroundRefreshUnscheduled:
522
+ try container.encode("backgroundRefreshUnscheduled", forKey: .state)
523
+ case .pendingRetry:
524
+ try container.encode("pendingRetry", forKey: .state)
525
+ case .linkRevoked:
526
+ try container.encode("linkRevoked", forKey: .state)
527
+ case .devIDMismatch:
528
+ try container.encode("devIDMismatch", forKey: .state)
529
+ }
530
+ }
531
+ }
532
+
533
+ /// What a subscription delivers. A stream can fail or finish, and without saying so the
534
+ /// framework layer has to invent a convention for both - two of those, one per wrapper, is a
535
+ /// contract nobody wrote down.
536
+ public enum StreamMessage: Codable, Equatable, Sendable {
537
+ case data(element: BridgeJSON)
538
+ case error(error: TerraErrorPayload)
539
+ case complete
540
+
541
+ private enum CodingKeys: String, CodingKey {
542
+ case kind
543
+ case element
544
+ case error
545
+ }
546
+
547
+ public init(from decoder: any Decoder) throws {
548
+ let container = try decoder.container(keyedBy: CodingKeys.self)
549
+ let tag = try container.decode(String.self, forKey: .kind)
550
+ switch tag {
551
+ case "data":
552
+ let element = try container.decode(BridgeJSON.self, forKey: .element)
553
+ self = .data(element: element)
554
+ case "error":
555
+ let error = try container.decode(TerraErrorPayload.self, forKey: .error)
556
+ self = .error(error: error)
557
+ case "complete":
558
+ self = .complete
559
+ default:
560
+ throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "unknown StreamMessage \(tag)")
561
+ }
562
+ }
563
+
564
+ public func encode(to encoder: any Encoder) throws {
565
+ var container = encoder.container(keyedBy: CodingKeys.self)
566
+ switch self {
567
+ case .data(let element):
568
+ try container.encode("data", forKey: .kind)
569
+ try container.encode(element, forKey: .element)
570
+ case .error(let error):
571
+ try container.encode("error", forKey: .kind)
572
+ try container.encode(error, forKey: .error)
573
+ case .complete:
574
+ try container.encode("complete", forKey: .kind)
575
+ }
576
+ }
577
+ }
578
+
579
+ /// Who recorded a sample. The only stable handle an app has for filtering by app or device.
580
+ public struct SampleSource: Codable, Equatable, Sendable {
581
+ public var name: String
582
+ public var bundleId: String
583
+ public var productType: String?
584
+ public var deviceType: String?
585
+
586
+ public init(name: String, bundleId: String, productType: String? = nil, deviceType: String? = nil) {
587
+ self.name = name
588
+ self.bundleId = bundleId
589
+ self.productType = productType
590
+ self.deviceType = deviceType
591
+ }
592
+ }
593
+
594
+ /// A link record. A value, not a handle: Apple's Connection carries sync and disconnect
595
+ /// methods, and an object with behaviour cannot cross the bridge, so those are ops on the
596
+ /// client.
597
+ public struct Connection: Codable, Equatable, Sendable {
598
+ public var source: Source
599
+ public var userId: String
600
+ /// Android's StoredConnection carries the store-side id. Apple's Connection does not.
601
+ ///
602
+ /// Available on android only.
603
+ public var providerUserId: String?
604
+
605
+ public init(source: Source, userId: String, providerUserId: String? = nil) {
606
+ self.source = source
607
+ self.userId = userId
608
+ self.providerUserId = providerUserId
609
+ }
610
+ }
611
+
612
+ public struct HeartRateSample: Codable, Equatable, Sendable {
613
+ public var id: String
614
+ public var time: String
615
+ public var beatsPerMinute: Double
616
+ public var source: SampleSource
617
+ public var motionContext: MotionContext?
618
+ public var userEntered: Bool
619
+ public var timeZone: String?
620
+
621
+ public init(id: String, time: String, beatsPerMinute: Double, source: SampleSource, motionContext: MotionContext? = nil, userEntered: Bool, timeZone: String? = nil) {
622
+ self.id = id
623
+ self.time = time
624
+ self.beatsPerMinute = beatsPerMinute
625
+ self.source = source
626
+ self.motionContext = motionContext
627
+ self.userEntered = userEntered
628
+ self.timeZone = timeZone
629
+ }
630
+ }
631
+
632
+ /// A step total over an interval. Apple derives the id from the source and start; Android
633
+ /// carries a RecordId when it has one, so the id is optional on the wire.
634
+ public struct StepCount: Codable, Equatable, Sendable {
635
+ public var id: String?
636
+ public var start: String
637
+ public var end: String
638
+ public var steps: Int
639
+ public var source: SampleSource
640
+ public var userEntered: Bool
641
+ public var timeZone: String?
642
+
643
+ public init(id: String? = nil, start: String, end: String, steps: Int, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
644
+ self.id = id
645
+ self.start = start
646
+ self.end = end
647
+ self.steps = steps
648
+ self.source = source
649
+ self.userEntered = userEntered
650
+ self.timeZone = timeZone
651
+ }
652
+ }
653
+
654
+ public struct SleepSegment: Codable, Equatable, Sendable {
655
+ public var id: String
656
+ public var start: String
657
+ public var end: String
658
+ public var stage: SleepStage
659
+ public var source: SampleSource
660
+ public var userEntered: Bool
661
+ public var timeZone: String?
662
+
663
+ public init(id: String, start: String, end: String, stage: SleepStage, source: SampleSource, userEntered: Bool, timeZone: String? = nil) {
664
+ self.id = id
665
+ self.start = start
666
+ self.end = end
667
+ self.stage = stage
668
+ self.source = source
669
+ self.userEntered = userEntered
670
+ self.timeZone = timeZone
671
+ }
672
+ }
673
+
674
+ /// indoor is optional because Health Connect does not always record it; Apple always has an
675
+ /// answer.
676
+ public struct Workout: Codable, Equatable, Sendable {
677
+ public var id: String
678
+ public var start: String
679
+ public var end: String
680
+ public var activity: WorkoutActivity
681
+ public var activityTypeCode: Int
682
+ public var indoor: Bool?
683
+ public var activeEnergyKilocalories: Double?
684
+ public var distanceMeters: Double?
685
+ public var source: SampleSource
686
+ public var userEntered: Bool
687
+ public var timeZone: String?
688
+
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) {
690
+ self.id = id
691
+ self.start = start
692
+ self.end = end
693
+ self.activity = activity
694
+ self.activityTypeCode = activityTypeCode
695
+ self.indoor = indoor
696
+ self.activeEnergyKilocalories = activeEnergyKilocalories
697
+ self.distanceMeters = distanceMeters
698
+ self.source = source
699
+ self.userEntered = userEntered
700
+ self.timeZone = timeZone
701
+ }
702
+ }
703
+
704
+ /// A day whose data changed. Carries no health value by construction.
705
+ public struct HealthChange: Codable, Equatable, Sendable {
706
+ public var category: DataCategory
707
+ public var day: String
708
+
709
+ public init(category: DataCategory, day: String) {
710
+ self.category = category
711
+ self.day = day
712
+ }
713
+ }
714
+
715
+ public struct WorkoutScope: Codable, Equatable, Sendable {
716
+ public var activity: WorkoutActivity
717
+ public var energy: Bool
718
+ public var distance: Bool
719
+
720
+ public init(activity: WorkoutActivity, energy: Bool, distance: Bool) {
721
+ self.activity = activity
722
+ self.energy = energy
723
+ self.distance = distance
724
+ }
725
+ }
726
+
727
+ /// Lists rather than sets: JSON has no set, so the category lists are marked `set` and every
728
+ /// generated codec de-duplicates and sorts them, so two orderings compare equal.
729
+ public struct HealthAccess: Codable, Equatable, Sendable {
730
+ @CaseSet public var read: [DataCategory]
731
+ @CaseSet public var write: [DataCategory]
732
+ public var workouts: [WorkoutScope]
733
+
734
+ public init(read: [DataCategory], write: [DataCategory], workouts: [WorkoutScope]) {
735
+ self.read = read
736
+ self.write = write
737
+ self.workouts = workouts
738
+ }
739
+ }
740
+
741
+ public struct HeartRateEntry: Codable, Equatable, Sendable {
742
+ public var time: String
743
+ public var beatsPerMinute: Double
744
+ public var recordId: String?
745
+
746
+ public init(time: String, beatsPerMinute: Double, recordId: String? = nil) {
747
+ self.time = time
748
+ self.beatsPerMinute = beatsPerMinute
749
+ self.recordId = recordId
750
+ }
751
+ }
752
+
753
+ public struct StepEntry: Codable, Equatable, Sendable {
754
+ public var start: String
755
+ public var end: String
756
+ public var steps: Int
757
+ public var recordId: String?
758
+
759
+ public init(start: String, end: String, steps: Int, recordId: String? = nil) {
760
+ self.start = start
761
+ self.end = end
762
+ self.steps = steps
763
+ self.recordId = recordId
764
+ }
765
+ }
766
+
767
+ public struct SleepEntry: Codable, Equatable, Sendable {
768
+ public var start: String
769
+ public var end: String
770
+ public var stage: SleepStage
771
+ public var recordId: String?
772
+
773
+ public init(start: String, end: String, stage: SleepStage, recordId: String? = nil) {
774
+ self.start = start
775
+ self.end = end
776
+ self.stage = stage
777
+ self.recordId = recordId
778
+ }
779
+ }
780
+
781
+ public struct WorkoutEntry: Codable, Equatable, Sendable {
782
+ public var start: String
783
+ public var end: String
784
+ public var activity: WorkoutActivity
785
+ public var totalEnergyKilocalories: Double?
786
+ public var totalDistanceMeters: Double?
787
+ public var recordId: String?
788
+
789
+ public init(start: String, end: String, activity: WorkoutActivity, totalEnergyKilocalories: Double? = nil, totalDistanceMeters: Double? = nil, recordId: String? = nil) {
790
+ self.start = start
791
+ self.end = end
792
+ self.activity = activity
793
+ self.totalEnergyKilocalories = totalEnergyKilocalories
794
+ self.totalDistanceMeters = totalDistanceMeters
795
+ self.recordId = recordId
796
+ }
797
+ }
798
+
799
+ public struct BodyEntry: Codable, Equatable, Sendable {
800
+ public var time: String
801
+ public var bodyMassKilograms: Double?
802
+ public var bodyFatPercent: Double?
803
+ public var bodyMassIndex: Double?
804
+ public var heightMeters: Double?
805
+ public var leanBodyMassKilograms: Double?
806
+ public var bodyTemperatureCelsius: Double?
807
+ public var oxygenSaturationPercent: Double?
808
+ public var bloodGlucoseMilligramsPerDeciliter: Double?
809
+ public var bloodPressureSystolicMillimetersOfMercury: Double?
810
+ public var bloodPressureDiastolicMillimetersOfMercury: Double?
811
+ public var recordId: String?
812
+
813
+ 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) {
814
+ self.time = time
815
+ self.bodyMassKilograms = bodyMassKilograms
816
+ self.bodyFatPercent = bodyFatPercent
817
+ self.bodyMassIndex = bodyMassIndex
818
+ self.heightMeters = heightMeters
819
+ self.leanBodyMassKilograms = leanBodyMassKilograms
820
+ self.bodyTemperatureCelsius = bodyTemperatureCelsius
821
+ self.oxygenSaturationPercent = oxygenSaturationPercent
822
+ self.bloodGlucoseMilligramsPerDeciliter = bloodGlucoseMilligramsPerDeciliter
823
+ self.bloodPressureSystolicMillimetersOfMercury = bloodPressureSystolicMillimetersOfMercury
824
+ self.bloodPressureDiastolicMillimetersOfMercury = bloodPressureDiastolicMillimetersOfMercury
825
+ self.recordId = recordId
826
+ }
827
+ }
828
+
829
+ /// Every field optional and every unit in the name, matching NutritionEntry.Nutrients on both
830
+ /// platforms field for field.
831
+ public struct Nutrients: Codable, Equatable, Sendable {
832
+ public var energyKilocalories: Double?
833
+ public var proteinGrams: Double?
834
+ public var carbohydratesGrams: Double?
835
+ public var fiberGrams: Double?
836
+ public var sugarGrams: Double?
837
+ public var fatTotalGrams: Double?
838
+ public var fatSaturatedGrams: Double?
839
+ public var fatMonounsaturatedGrams: Double?
840
+ public var fatPolyunsaturatedGrams: Double?
841
+ public var cholesterolMilligrams: Double?
842
+ public var sodiumMilligrams: Double?
843
+ public var potassiumMilligrams: Double?
844
+ public var calciumMilligrams: Double?
845
+ public var ironMilligrams: Double?
846
+ public var magnesiumMilligrams: Double?
847
+ public var zincMilligrams: Double?
848
+ public var waterLiters: Double?
849
+ public var caffeineMilligrams: Double?
850
+ public var vitaminAMicrograms: Double?
851
+ public var vitaminCMilligrams: Double?
852
+ public var vitaminDMicrograms: Double?
853
+ public var vitaminEMilligrams: Double?
854
+ public var vitaminKMicrograms: Double?
855
+ public var vitaminB6Milligrams: Double?
856
+ public var vitaminB12Micrograms: Double?
857
+ public var folateMicrograms: Double?
858
+ public var thiaminMilligrams: Double?
859
+ public var riboflavinMilligrams: Double?
860
+ public var niacinMilligrams: Double?
861
+ public var biotinMicrograms: Double?
862
+ public var pantothenicAcidMilligrams: Double?
863
+ public var phosphorusMilligrams: Double?
864
+ public var iodineMicrograms: Double?
865
+ public var chromiumMicrograms: Double?
866
+ public var copperMilligrams: Double?
867
+ public var manganeseMilligrams: Double?
868
+ public var molybdenumMicrograms: Double?
869
+ public var seleniumMicrograms: Double?
870
+ public var chlorideMilligrams: Double?
871
+
872
+ 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) {
873
+ self.energyKilocalories = energyKilocalories
874
+ self.proteinGrams = proteinGrams
875
+ self.carbohydratesGrams = carbohydratesGrams
876
+ self.fiberGrams = fiberGrams
877
+ self.sugarGrams = sugarGrams
878
+ self.fatTotalGrams = fatTotalGrams
879
+ self.fatSaturatedGrams = fatSaturatedGrams
880
+ self.fatMonounsaturatedGrams = fatMonounsaturatedGrams
881
+ self.fatPolyunsaturatedGrams = fatPolyunsaturatedGrams
882
+ self.cholesterolMilligrams = cholesterolMilligrams
883
+ self.sodiumMilligrams = sodiumMilligrams
884
+ self.potassiumMilligrams = potassiumMilligrams
885
+ self.calciumMilligrams = calciumMilligrams
886
+ self.ironMilligrams = ironMilligrams
887
+ self.magnesiumMilligrams = magnesiumMilligrams
888
+ self.zincMilligrams = zincMilligrams
889
+ self.waterLiters = waterLiters
890
+ self.caffeineMilligrams = caffeineMilligrams
891
+ self.vitaminAMicrograms = vitaminAMicrograms
892
+ self.vitaminCMilligrams = vitaminCMilligrams
893
+ self.vitaminDMicrograms = vitaminDMicrograms
894
+ self.vitaminEMilligrams = vitaminEMilligrams
895
+ self.vitaminKMicrograms = vitaminKMicrograms
896
+ self.vitaminB6Milligrams = vitaminB6Milligrams
897
+ self.vitaminB12Micrograms = vitaminB12Micrograms
898
+ self.folateMicrograms = folateMicrograms
899
+ self.thiaminMilligrams = thiaminMilligrams
900
+ self.riboflavinMilligrams = riboflavinMilligrams
901
+ self.niacinMilligrams = niacinMilligrams
902
+ self.biotinMicrograms = biotinMicrograms
903
+ self.pantothenicAcidMilligrams = pantothenicAcidMilligrams
904
+ self.phosphorusMilligrams = phosphorusMilligrams
905
+ self.iodineMicrograms = iodineMicrograms
906
+ self.chromiumMicrograms = chromiumMicrograms
907
+ self.copperMilligrams = copperMilligrams
908
+ self.manganeseMilligrams = manganeseMilligrams
909
+ self.molybdenumMicrograms = molybdenumMicrograms
910
+ self.seleniumMicrograms = seleniumMicrograms
911
+ self.chlorideMilligrams = chlorideMilligrams
912
+ }
913
+ }
914
+
915
+ public struct NutritionEntry: Codable, Equatable, Sendable {
916
+ public var start: String
917
+ public var end: String
918
+ public var name: String?
919
+ public var nutrients: Nutrients
920
+ public var recordId: String?
921
+
922
+ public init(start: String, end: String, name: String? = nil, nutrients: Nutrients, recordId: String? = nil) {
923
+ self.start = start
924
+ self.end = end
925
+ self.name = name
926
+ self.nutrients = nutrients
927
+ self.recordId = recordId
928
+ }
929
+ }
930
+
931
+ /// An error carried as a value rather than thrown, inside a sync result. The same shape the
932
+ /// bridge rejects a promise with.
933
+ public struct TerraErrorPayload: Codable, Equatable, Sendable {
934
+ public var code: String
935
+ public var retryable: Bool
936
+ public var message: String
937
+ public var source: Source?
938
+ public var details: BridgeJSON?
939
+
940
+ public init(code: String, retryable: Bool, message: String, source: Source? = nil, details: BridgeJSON? = nil) {
941
+ self.code = code
942
+ self.retryable = retryable
943
+ self.message = message
944
+ self.source = source
945
+ self.details = details
946
+ }
947
+ }
948
+
949
+ /// Available on ios only.
950
+ public struct PlannedWorkout: Codable, Equatable, Sendable {
951
+ public var id: String
952
+ public var plannedOn: String
953
+ public var activity: WorkoutActivity
954
+ public var title: String?
955
+ public var completedAt: String?
956
+
957
+ public init(id: String, plannedOn: String, activity: WorkoutActivity, title: String? = nil, completedAt: String? = nil) {
958
+ self.id = id
959
+ self.plannedOn = plannedOn
960
+ self.activity = activity
961
+ self.title = title
962
+ self.completedAt = completedAt
963
+ }
964
+ }
965
+
966
+ /// The flat object both SDKs already emit: `at`, `event`, and whatever keys that kind carries.
967
+ /// Carried through unchanged rather than reshaped, because reshaping it would be a hand-written
968
+ /// second contract on each platform that no vector could pin - and the encoding it would be
969
+ /// translating is one DiagnosticsWire and Apple's Encodable already agree on, and that
970
+ /// EventPrivacyTest already guards against carrying a health value.
971
+ public typealias TerraEvent = BridgeJSON
972
+
973
+ /// The redacted report, carried as the wire object DiagnosticsWire.report and Apple's
974
+ /// SupportReport.json already produce.
975
+ public struct SupportReport: Codable, Equatable, Sendable {
976
+ public var sdkVersion: String
977
+ public var generatedAt: String
978
+ public var state: DiagnosticsState
979
+ public var lastDelivery: String?
980
+ public var isConnected: Bool
981
+ public var requestedCategories: [String]
982
+ public var requestedWrites: [String]
983
+ public var requestedWorkouts: [String]
984
+ public var recentEvents: [TerraEvent]
985
+
986
+ public init(sdkVersion: String, generatedAt: String, state: DiagnosticsState, lastDelivery: String? = nil, isConnected: Bool, requestedCategories: [String], requestedWrites: [String], requestedWorkouts: [String], recentEvents: [TerraEvent]) {
987
+ self.sdkVersion = sdkVersion
988
+ self.generatedAt = generatedAt
989
+ self.state = state
990
+ self.lastDelivery = lastDelivery
991
+ self.isConnected = isConnected
992
+ self.requestedCategories = requestedCategories
993
+ self.requestedWrites = requestedWrites
994
+ self.requestedWorkouts = requestedWorkouts
995
+ self.recentEvents = recentEvents
996
+ }
997
+ }
998
+
999
+ /// What the JS side needs to know about the native client it is attaching to. bootstrapped
1000
+ /// false is a host setup bug, and the wrapper says so with the exact line to add.
1001
+ public struct BootstrapState: Codable, Equatable, Sendable {
1002
+ public var bootstrapped: Bool
1003
+ public var devId: String?
1004
+ public var platform: String
1005
+ public var sdkVersion: String
1006
+ public var backgroundRegistered: Bool
1007
+
1008
+ public init(bootstrapped: Bool, devId: String? = nil, platform: String, sdkVersion: String, backgroundRegistered: Bool) {
1009
+ self.bootstrapped = bootstrapped
1010
+ self.devId = devId
1011
+ self.platform = platform
1012
+ self.sdkVersion = sdkVersion
1013
+ self.backgroundRegistered = backgroundRegistered
1014
+ }
1015
+ }
1016
+
1017
+ /// The broker asking JS for a link token. Answered by token.resolve or token.reject with the
1018
+ /// same requestId.
1019
+ public struct TokenRequest: Codable, Equatable, Sendable {
1020
+ public var requestId: String
1021
+
1022
+ public init(requestId: String) {
1023
+ self.requestId = requestId
1024
+ }
1025
+ }
1026
+
1027
+ /// No request fields, or no response value.
1028
+ public struct Empty: Codable, Equatable, Sendable {
1029
+ public init() {}
1030
+ }
1031
+
1032
+ public struct SourceRequest: Codable, Equatable, Sendable {
1033
+ public var source: Source?
1034
+
1035
+ public init(source: Source? = nil) {
1036
+ self.source = source
1037
+ }
1038
+ }
1039
+
1040
+ public struct RangeRequest: Codable, Equatable, Sendable {
1041
+ public var range: TimeRange
1042
+
1043
+ public init(range: TimeRange) {
1044
+ self.range = range
1045
+ }
1046
+ }
1047
+
1048
+ public struct CategoryRequest: Codable, Equatable, Sendable {
1049
+ public var category: DataCategory
1050
+ public var source: Source?
1051
+
1052
+ public init(category: DataCategory, source: Source? = nil) {
1053
+ self.category = category
1054
+ self.source = source
1055
+ }
1056
+ }
1057
+
1058
+ public struct RecordIdRequest: Codable, Equatable, Sendable {
1059
+ public var id: String
1060
+
1061
+ public init(id: String) {
1062
+ self.id = id
1063
+ }
1064
+ }
1065
+
1066
+ public struct ConnectRequest: Codable, Equatable, Sendable {
1067
+ public var source: Source?
1068
+ public var endUser: EndUser
1069
+ /// A grant already in hand, from a QR code or a deep link. Absent means the token provider is
1070
+ /// asked.
1071
+ public var token: String?
1072
+
1073
+ public init(source: Source? = nil, endUser: EndUser, token: String? = nil) {
1074
+ self.source = source
1075
+ self.endUser = endUser
1076
+ self.token = token
1077
+ }
1078
+ }
1079
+
1080
+ public struct PermissionRequest: Codable, Equatable, Sendable {
1081
+ @CaseSet public var read: [DataCategory]
1082
+ @CaseSet public var write: [DataCategory]
1083
+ public var workouts: [WorkoutScope]
1084
+ public var source: Source?
1085
+
1086
+ public init(read: [DataCategory], write: [DataCategory], workouts: [WorkoutScope], source: Source? = nil) {
1087
+ self.read = read
1088
+ self.write = write
1089
+ self.workouts = workouts
1090
+ self.source = source
1091
+ }
1092
+ }
1093
+
1094
+ /// What the sheet granted. Android answers with the access this call obtained; HealthKit will
1095
+ /// not report read grants, so Apple answers nothing at all.
1096
+ public struct PermissionGrant: Codable, Equatable, Sendable {
1097
+ /// Available on android only.
1098
+ public var granted: HealthAccess?
1099
+
1100
+ public init(granted: HealthAccess? = nil) {
1101
+ self.granted = granted
1102
+ }
1103
+ }
1104
+
1105
+ public struct TokenResolveRequest: Codable, Equatable, Sendable {
1106
+ public var requestId: String
1107
+ public var token: String
1108
+
1109
+ public init(requestId: String, token: String) {
1110
+ self.requestId = requestId
1111
+ self.token = token
1112
+ }
1113
+ }
1114
+
1115
+ public struct TokenRejectRequest: Codable, Equatable, Sendable {
1116
+ public var requestId: String
1117
+ public var message: String
1118
+
1119
+ public init(requestId: String, message: String) {
1120
+ self.requestId = requestId
1121
+ self.message = message
1122
+ }
1123
+ }
1124
+
1125
+ public struct TokenProviderRequest: Codable, Equatable, Sendable {
1126
+ public var present: Bool
1127
+ public var timeoutMs: Int
1128
+
1129
+ public init(present: Bool, timeoutMs: Int) {
1130
+ self.present = present
1131
+ self.timeoutMs = timeoutMs
1132
+ }
1133
+ }
1134
+
1135
+ /// Available on ios only.
1136
+ public struct MarkCompleteRequest: Codable, Equatable, Sendable {
1137
+ public var id: String
1138
+ public var at: String?
1139
+
1140
+ public init(id: String, at: String? = nil) {
1141
+ self.id = id
1142
+ self.at = at
1143
+ }
1144
+ }
1145
+
1146
+ public struct BoolResult: Codable, Equatable, Sendable {
1147
+ public var value: Bool
1148
+
1149
+ public init(value: Bool) {
1150
+ self.value = value
1151
+ }
1152
+ }
1153
+
1154
+ public struct StatusResult: Codable, Equatable, Sendable {
1155
+ public var status: PermissionStatus
1156
+
1157
+ public init(status: PermissionStatus) {
1158
+ self.status = status
1159
+ }
1160
+ }
1161
+
1162
+ /// Available on android only.
1163
+ public struct StatusesResult: Codable, Equatable, Sendable {
1164
+ public var statuses: [DataCategory: PermissionStatus]?
1165
+
1166
+ public init(statuses: [DataCategory: PermissionStatus]? = nil) {
1167
+ self.statuses = statuses
1168
+ }
1169
+ }
1170
+
1171
+ public struct RecordIdList: Codable, Equatable, Sendable {
1172
+ public var ids: [String]
1173
+
1174
+ public init(ids: [String]) {
1175
+ self.ids = ids
1176
+ }
1177
+ }
1178
+
1179
+ public struct ConnectionList: Codable, Equatable, Sendable {
1180
+ public var connections: [Connection]
1181
+
1182
+ public init(connections: [Connection]) {
1183
+ self.connections = connections
1184
+ }
1185
+ }
1186
+
1187
+ public struct HeartRateList: Codable, Equatable, Sendable {
1188
+ public var samples: [HeartRateSample]
1189
+
1190
+ public init(samples: [HeartRateSample]) {
1191
+ self.samples = samples
1192
+ }
1193
+ }
1194
+
1195
+ public struct StepCountList: Codable, Equatable, Sendable {
1196
+ public var samples: [StepCount]
1197
+
1198
+ public init(samples: [StepCount]) {
1199
+ self.samples = samples
1200
+ }
1201
+ }
1202
+
1203
+ public struct SleepSegmentList: Codable, Equatable, Sendable {
1204
+ public var samples: [SleepSegment]
1205
+
1206
+ public init(samples: [SleepSegment]) {
1207
+ self.samples = samples
1208
+ }
1209
+ }
1210
+
1211
+ public struct WorkoutList: Codable, Equatable, Sendable {
1212
+ public var samples: [Workout]
1213
+
1214
+ public init(samples: [Workout]) {
1215
+ self.samples = samples
1216
+ }
1217
+ }
1218
+
1219
+ /// Available on ios only.
1220
+ public struct PlannedWorkoutList: Codable, Equatable, Sendable {
1221
+ public var workouts: [PlannedWorkout]
1222
+
1223
+ public init(workouts: [PlannedWorkout]) {
1224
+ self.workouts = workouts
1225
+ }
1226
+ }
1227
+
1228
+ /// Once authorized, every sync run keeps the Watch schedule in step with Terra.
1229
+ ///
1230
+ /// Available on ios only.
1231
+ public struct WorkoutScheduling: Codable, Equatable, Sendable {
1232
+ public var status: WorkoutSchedulingStatus
1233
+
1234
+ public init(status: WorkoutSchedulingStatus) {
1235
+ self.status = status
1236
+ }
1237
+ }
1238
+
1239
+ public struct LastDeliveryResult: Codable, Equatable, Sendable {
1240
+ public var at: String?
1241
+
1242
+ public init(at: String? = nil) {
1243
+ self.at = at
1244
+ }
1245
+ }
1246
+
1247
+ public struct HeartRateEntryList: Codable, Equatable, Sendable {
1248
+ public var entries: [HeartRateEntry]
1249
+
1250
+ public init(entries: [HeartRateEntry]) {
1251
+ self.entries = entries
1252
+ }
1253
+ }
1254
+
1255
+ public struct BodyEntryList: Codable, Equatable, Sendable {
1256
+ public var entries: [BodyEntry]
1257
+
1258
+ public init(entries: [BodyEntry]) {
1259
+ self.entries = entries
1260
+ }
1261
+ }
1262
+
1263
+ public struct StepEntryList: Codable, Equatable, Sendable {
1264
+ public var entries: [StepEntry]
1265
+
1266
+ public init(entries: [StepEntry]) {
1267
+ self.entries = entries
1268
+ }
1269
+ }
1270
+
1271
+ public struct SleepEntryList: Codable, Equatable, Sendable {
1272
+ public var entries: [SleepEntry]
1273
+
1274
+ public init(entries: [SleepEntry]) {
1275
+ self.entries = entries
1276
+ }
1277
+ }
1278
+
1279
+ public struct WorkoutEntryList: Codable, Equatable, Sendable {
1280
+ public var entries: [WorkoutEntry]
1281
+
1282
+ public init(entries: [WorkoutEntry]) {
1283
+ self.entries = entries
1284
+ }
1285
+ }
1286
+
1287
+ public struct NutritionEntryList: Codable, Equatable, Sendable {
1288
+ public var entries: [NutritionEntry]
1289
+
1290
+ public init(entries: [NutritionEntry]) {
1291
+ self.entries = entries
1292
+ }
1293
+ }
1294
+
1295
+ public enum TerraEventEnvelope {
1296
+ public static func validate(_ value: TerraEvent) throws {
1297
+ guard case .string = value.storage["at"] else {
1298
+ throw DecodingError.dataCorrupted(
1299
+ .init(codingPath: [], debugDescription: "TerraEvent.at is not a string")
1300
+ )
1301
+ }
1302
+ guard case .string = value.storage["event"] else {
1303
+ throw DecodingError.dataCorrupted(
1304
+ .init(codingPath: [], debugDescription: "TerraEvent.event is not a string")
1305
+ )
1306
+ }
1307
+ }
1308
+ }
1309
+
1310
+ public enum SupportReportEnvelope {
1311
+ public static func validate(_ value: SupportReport) throws {
1312
+ for level0 in value.recentEvents {
1313
+ try TerraEventEnvelope.validate(level0)
1314
+ }
1315
+ }
1316
+ }
1317
+