@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,594 @@
1
+ import Foundation
2
+ import TerraSDK
3
+
4
+ /// Between the SDK's types and the generated wire DTOs.
5
+ ///
6
+ /// Every `switch` is exhaustive with no `default`, so a case added on either side is a compile
7
+ /// error here rather than a value that maps to something plausible. Unqualified names are the
8
+ /// generated wire types; the SDK's are spelled `TerraSDK.`.
9
+ enum Conversions {
10
+
11
+ // MARK: Input the decoder accepted and the SDK cannot
12
+
13
+ /// A string field that decoded but is not the instant or id it names. Reported as the same
14
+ /// `bridge.invalid_payload` the generated decoder raises, with the path and never the value.
15
+ static func instant(_ text: String, at path: String) throws -> Date {
16
+ guard let date = BridgeInstant.parse(text) else { throw BridgeError.invalidPayload(path: path) }
17
+ return date
18
+ }
19
+
20
+ static func uuid(_ text: String, at path: String) throws -> UUID {
21
+ guard let id = UUID(uuidString: text) else { throw BridgeError.invalidPayload(path: path) }
22
+ return id
23
+ }
24
+
25
+ static func interval(start: String, end: String, at path: String) throws -> DateInterval {
26
+ let from = try instant(start, at: "\(path).start")
27
+ let to = try instant(end, at: "\(path).end")
28
+ guard from <= to else { throw BridgeError.invalidPayload(path: "\(path).end") }
29
+ return DateInterval(start: from, end: to)
30
+ }
31
+
32
+ /// The calendar day an instant falls on in `zone`, as `yyyy-MM-dd`.
33
+ static func localDate(_ date: Date, in zone: TimeZone) -> String {
34
+ var calendar = Calendar(identifier: .gregorian)
35
+ calendar.timeZone = zone
36
+ let parts = calendar.dateComponents([.year, .month, .day], from: date)
37
+ return day(year: parts.year ?? 0, month: parts.month ?? 0, day: parts.day ?? 0)
38
+ }
39
+
40
+ static func localDate(_ components: DateComponents) -> String {
41
+ day(year: components.year ?? 0, month: components.month ?? 0, day: components.day ?? 0)
42
+ }
43
+
44
+ private static func day(year: Int, month: Int, day: Int) -> String {
45
+ String(format: "%04d-%02d-%02d", year, month, day)
46
+ }
47
+
48
+ // MARK: Enums
49
+
50
+ // Swift 6 requires `@unknown default` for TerraSDK's non-frozen binary enums.
51
+
52
+ static func source(_ wire: Source?) throws -> TerraSDK.Source {
53
+ switch wire ?? .health {
54
+ case .health: return .health
55
+ case .samsungHealth: throw BridgeError.unsupportedOnPlatform(op: "samsungHealth")
56
+ }
57
+ }
58
+
59
+ static func source(_ source: TerraSDK.Source) -> Source {
60
+ switch source {
61
+ case .health: return .health
62
+ @unknown default: return .health
63
+ }
64
+ }
65
+
66
+ static func endUser(_ wire: EndUser) -> TerraSDK.EndUser {
67
+ switch wire {
68
+ case .reference(let id): return .reference(id)
69
+ case .anonymous: return .anonymous
70
+ }
71
+ }
72
+
73
+ static func category(_ wire: DataCategory) -> TerraSDK.DataCategory {
74
+ switch wire {
75
+ case .activity: return .activity
76
+ case .body: return .body
77
+ case .daily: return .daily
78
+ case .sleep: return .sleep
79
+ case .nutrition: return .nutrition
80
+ case .menstruation: return .menstruation
81
+ case .athlete: return .athlete
82
+ }
83
+ }
84
+
85
+ /// Nil for the categories the surface excludes: there is no wire name to report them under.
86
+ static func category(_ category: TerraSDK.DataCategory) -> DataCategory? {
87
+ switch category {
88
+ case .activity: return .activity
89
+ case .body: return .body
90
+ case .daily: return .daily
91
+ case .sleep: return .sleep
92
+ case .nutrition: return .nutrition
93
+ case .menstruation: return .menstruation
94
+ case .athlete: return .athlete
95
+ case .healthObservation: return nil
96
+ @unknown default: return nil
97
+ }
98
+ }
99
+
100
+ static func permissionStatus(_ status: TerraSDK.PermissionStatus) -> PermissionStatus {
101
+ switch status {
102
+ case .granted: return .granted
103
+ case .denied: return .denied
104
+ case .notDetermined: return .notDetermined
105
+ case .notDisclosed: return .notDisclosed
106
+ case .unavailable: return .unavailable
107
+ case .unsupported: return .unsupported
108
+ @unknown default: return .unavailable
109
+ }
110
+ }
111
+
112
+ static func motionContext(_ context: TerraSDK.MotionContext) -> MotionContext {
113
+ switch context {
114
+ case .unknown: return .unknown
115
+ case .sedentary: return .sedentary
116
+ case .active: return .active
117
+ @unknown default: return .unknown
118
+ }
119
+ }
120
+
121
+ static func deviceType(_ type: TerraSDK.DeviceType) -> String {
122
+ switch type {
123
+ case .phone: return "phone"
124
+ case .scale: return "scale"
125
+ case .sensor: return "sensor"
126
+ @unknown default: return "unknown"
127
+ }
128
+ }
129
+
130
+ // MARK: Unions
131
+
132
+ static func timeRange(_ wire: TimeRange, at path: String) throws -> TerraSDK.TimeRange {
133
+ switch wire {
134
+ case .lastHours(let hours): return .lastHours(hours)
135
+ case .lastDays(let days): return .lastDays(days)
136
+ case .today: return .today
137
+ case .between(let start, let end):
138
+ let window = try interval(start: start, end: end, at: path)
139
+ return .between(window.start, window.end)
140
+ }
141
+ }
142
+
143
+ static func sleepStage(_ stage: TerraSDK.SleepStage) -> SleepStage {
144
+ switch stage {
145
+ case .inBed: return .inBed
146
+ case .awake: return .awake
147
+ case .asleep: return .asleep
148
+ case .light: return .light
149
+ case .deep: return .deep
150
+ case .rem: return .rem
151
+ case .unknown(let raw): return .unknown(raw: raw)
152
+ @unknown default: return .unknown(raw: String(describing: stage))
153
+ }
154
+ }
155
+
156
+ static func sleepStage(_ wire: SleepStage) -> TerraSDK.SleepStage {
157
+ switch wire {
158
+ case .inBed: return .inBed
159
+ case .awake: return .awake
160
+ case .asleep: return .asleep
161
+ case .light: return .light
162
+ case .deep: return .deep
163
+ case .rem: return .rem
164
+ case .unknown(let raw): return .unknown(raw)
165
+ }
166
+ }
167
+
168
+ static func activity(_ activity: TerraSDK.WorkoutActivity) -> WorkoutActivity {
169
+ switch activity {
170
+ case .running: return .running
171
+ case .walking: return .walking
172
+ case .cycling: return .cycling
173
+ case .swimming: return .swimming
174
+ case .strengthTraining: return .strengthTraining
175
+ case .other(let label): return .other(label: label)
176
+ @unknown default: return .other(label: String(describing: activity))
177
+ }
178
+ }
179
+
180
+ static func activity(_ wire: WorkoutActivity) -> TerraSDK.WorkoutActivity {
181
+ switch wire {
182
+ case .running: return .running
183
+ case .walking: return .walking
184
+ case .cycling: return .cycling
185
+ case .swimming: return .swimming
186
+ case .strengthTraining: return .strengthTraining
187
+ case .other(let label): return .other(label)
188
+ }
189
+ }
190
+
191
+ /// The order a grant is listed in. Android sorts by the same names, so one grant is one byte
192
+ /// sequence on both platforms.
193
+ static func sortKey(_ activity: WorkoutActivity) -> String {
194
+ switch activity {
195
+ case .running: return "Running"
196
+ case .walking: return "Walking"
197
+ case .cycling: return "Cycling"
198
+ case .swimming: return "Swimming"
199
+ case .strengthTraining: return "Strength Training"
200
+ case .other(let label): return label
201
+ }
202
+ }
203
+
204
+ static func diagnosticsState(_ state: TerraSDK.DiagnosticsState) -> DiagnosticsState {
205
+ switch state {
206
+ case .healthy: return .healthy
207
+ case .quiet(let since): return .quiet(since: BridgeInstant.format(since))
208
+ // Apple does not disclose why, so the reason is absent rather than guessed.
209
+ case .backgroundRestricted: return .backgroundRestricted(reason: nil)
210
+ case .backgroundRefreshUnscheduled: return .backgroundRefreshUnscheduled
211
+ case .pendingRetry: return .pendingRetry
212
+ case .linkRevoked: return .linkRevoked
213
+ case .devIDMismatch: return .devIDMismatch
214
+ @unknown default: return .pendingRetry
215
+ }
216
+ }
217
+
218
+ // MARK: Records
219
+
220
+ static func connection(_ connection: TerraSDK.Connection) -> Connection {
221
+ Connection(source: source(connection.source), userId: connection.userID)
222
+ }
223
+
224
+ static func sampleSource(_ source: TerraSDK.SampleSource) -> SampleSource {
225
+ SampleSource(
226
+ name: source.name,
227
+ bundleId: source.bundleID,
228
+ productType: source.productType,
229
+ deviceType: source.deviceType.map(deviceType))
230
+ }
231
+
232
+ static func heartRate(_ sample: TerraSDK.HeartRateSample) -> HeartRateSample {
233
+ HeartRateSample(
234
+ id: sample.id.uuidString,
235
+ time: BridgeInstant.format(sample.time),
236
+ beatsPerMinute: sample.beatsPerMinute,
237
+ source: sampleSource(sample.source),
238
+ motionContext: sample.motionContext.map(motionContext),
239
+ userEntered: sample.userEntered,
240
+ timeZone: sample.timeZone?.identifier)
241
+ }
242
+
243
+ static func steps(_ sample: TerraSDK.StepCount) -> StepCount {
244
+ StepCount(
245
+ id: sample.id,
246
+ start: BridgeInstant.format(sample.interval.start),
247
+ end: BridgeInstant.format(sample.interval.end),
248
+ steps: sample.steps,
249
+ source: sampleSource(sample.source),
250
+ userEntered: sample.userEntered,
251
+ timeZone: sample.timeZone?.identifier)
252
+ }
253
+
254
+ static func segment(_ segment: TerraSDK.SleepSegment) -> SleepSegment {
255
+ SleepSegment(
256
+ id: segment.id.uuidString,
257
+ start: BridgeInstant.format(segment.interval.start),
258
+ end: BridgeInstant.format(segment.interval.end),
259
+ stage: sleepStage(segment.stage),
260
+ source: sampleSource(segment.source),
261
+ userEntered: segment.userEntered,
262
+ timeZone: segment.timeZone?.identifier)
263
+ }
264
+
265
+ static func workout(_ workout: TerraSDK.Workout) -> Workout {
266
+ Workout(
267
+ id: workout.id.uuidString,
268
+ start: BridgeInstant.format(workout.interval.start),
269
+ end: BridgeInstant.format(workout.interval.end),
270
+ activity: activity(workout.activity),
271
+ activityTypeCode: workout.activityTypeCode,
272
+ indoor: workout.indoor,
273
+ activeEnergyKilocalories: workout.activeEnergyKilocalories,
274
+ distanceMeters: workout.distanceMeters,
275
+ source: sampleSource(workout.source),
276
+ userEntered: workout.userEntered,
277
+ timeZone: workout.timeZone?.identifier,
278
+ title: workout.title)
279
+ }
280
+
281
+ static func nutrient(_ nutrient: TerraSDK.Nutrient) -> Nutrient? {
282
+ switch nutrient {
283
+ case .energyConsumed: return .energyConsumed
284
+ case .protein: return .protein
285
+ case .carbohydrates: return .carbohydrates
286
+ case .fatTotal: return .fatTotal
287
+ case .fatSaturated: return .fatSaturated
288
+ case .fatMonounsaturated: return .fatMonounsaturated
289
+ case .fatPolyunsaturated: return .fatPolyunsaturated
290
+ case .sugar: return .sugar
291
+ case .fiber: return .fiber
292
+ case .cholesterol: return .cholesterol
293
+ case .sodium: return .sodium
294
+ case .potassium: return .potassium
295
+ case .calcium: return .calcium
296
+ case .iron: return .iron
297
+ case .magnesium: return .magnesium
298
+ case .zinc: return .zinc
299
+ case .phosphorus: return .phosphorus
300
+ case .iodine: return .iodine
301
+ case .manganese: return .manganese
302
+ case .molybdenum: return .molybdenum
303
+ case .selenium: return .selenium
304
+ case .copper: return .copper
305
+ case .chromium: return .chromium
306
+ case .chloride: return .chloride
307
+ case .caffeine: return .caffeine
308
+ case .vitaminA: return .vitaminA
309
+ case .vitaminC: return .vitaminC
310
+ case .vitaminD: return .vitaminD
311
+ case .vitaminE: return .vitaminE
312
+ case .vitaminK: return .vitaminK
313
+ case .vitaminB6: return .vitaminB6
314
+ case .vitaminB12: return .vitaminB12
315
+ case .thiamin: return .thiamin
316
+ case .riboflavin: return .riboflavin
317
+ case .niacin: return .niacin
318
+ case .folate: return .folate
319
+ case .biotin: return .biotin
320
+ case .pantothenicAcid: return .pantothenicAcid
321
+ @unknown default: return nil
322
+ }
323
+ }
324
+
325
+ static func mealType(_ type: TerraSDK.MealType) -> MealType {
326
+ switch type {
327
+ case .breakfast: return .breakfast
328
+ case .lunch: return .lunch
329
+ case .dinner: return .dinner
330
+ case .snack: return .snack
331
+ case .unknown: return .unknown
332
+ @unknown default: return .unknown
333
+ }
334
+ }
335
+
336
+ static func flow(_ flow: TerraSDK.MenstruationFlowDay.Flow) -> MenstruationFlow {
337
+ switch flow {
338
+ case .unspecified: return .unspecified
339
+ case .light: return .light
340
+ case .medium: return .medium
341
+ case .heavy: return .heavy
342
+ case .noFlow: return .noFlow
343
+ @unknown default: return .unspecified
344
+ }
345
+ }
346
+
347
+ static func biologicalSex(_ sex: TerraSDK.AthleteProfile.BiologicalSex) -> BiologicalSex {
348
+ switch sex {
349
+ case .female: return .female
350
+ case .male: return .male
351
+ case .other: return .other
352
+ case .notSet: return .notSet
353
+ @unknown default: return .notSet
354
+ }
355
+ }
356
+
357
+ static func moveMode(_ mode: TerraSDK.AthleteProfile.ActivityMoveMode) -> ActivityMoveMode? {
358
+ switch mode {
359
+ case .activeEnergy: return .activeEnergy
360
+ case .appleMoveTime: return .appleMoveTime
361
+ @unknown default: return nil
362
+ }
363
+ }
364
+
365
+ static func quantity(_ quantity: TerraSDK.DietaryQuantity) -> DietaryQuantity? {
366
+ guard let nutrient = nutrient(quantity.nutrient) else { return nil }
367
+ return DietaryQuantity(
368
+ id: quantity.id.uuidString,
369
+ nutrient: nutrient,
370
+ value: quantity.value,
371
+ start: BridgeInstant.format(quantity.interval.start),
372
+ end: BridgeInstant.format(quantity.interval.end),
373
+ mealType: mealType(quantity.mealType),
374
+ name: quantity.name,
375
+ foodCorrelationId: quantity.foodCorrelationID?.uuidString,
376
+ source: sampleSource(quantity.source),
377
+ userEntered: quantity.userEntered,
378
+ timeZone: quantity.timeZone?.identifier)
379
+ }
380
+
381
+ static func flowDay(_ day: TerraSDK.MenstruationFlowDay) -> MenstruationFlowDay {
382
+ MenstruationFlowDay(
383
+ id: day.id.uuidString,
384
+ start: BridgeInstant.format(day.interval.start),
385
+ end: BridgeInstant.format(day.interval.end),
386
+ flow: flow(day.flow),
387
+ cycleStart: day.cycleStart,
388
+ source: sampleSource(day.source),
389
+ userEntered: day.userEntered,
390
+ timeZone: day.timeZone?.identifier)
391
+ }
392
+
393
+ /// The birth date is a calendar day. HealthKit hands back components, which the store turns into
394
+ /// a `Date` at midnight in the device's zone, so the same zone reads the same day back out.
395
+ static func athleteProfile(_ profile: TerraSDK.AthleteProfile, zone: TimeZone) -> AthleteProfile {
396
+ AthleteProfile(
397
+ biologicalSex: biologicalSex(profile.biologicalSex),
398
+ dateOfBirth: profile.dateOfBirth.map { localDate($0, in: zone) },
399
+ wheelchairUse: profile.wheelchairUse,
400
+ activityMoveMode: profile.activityMoveMode.flatMap(moveMode))
401
+ }
402
+
403
+ /// A day, not an instant: sending the instant would let the receiver re-derive the day in its
404
+ /// own zone and get a different one either side of midnight.
405
+ static func change(_ change: TerraSDK.HealthChange, zone: TimeZone) -> HealthChange? {
406
+ category(change.category).map { HealthChange(category: $0, day: localDate(change.day, in: zone)) }
407
+ }
408
+
409
+ /// `@unknown default` because the binary SDK is built with library evolution, where this enum
410
+ /// can grow; `SchemaParityTests` is what fails when it does. A status this build predates
411
+ /// reads as one it cannot schedule on.
412
+ static func schedulingStatus(_ status: TerraSDK.WorkoutSchedulingStatus) -> WorkoutSchedulingStatus {
413
+ switch status {
414
+ case .unsupported: .unsupported
415
+ case .notDetermined: .notDetermined
416
+ case .denied: .denied
417
+ case .authorized: .authorized
418
+ @unknown default: .unsupported
419
+ }
420
+ }
421
+
422
+ static func plannedWorkout(_ plan: TerraSDK.PlannedWorkout) -> PlannedWorkout {
423
+ PlannedWorkout(
424
+ id: plan.id,
425
+ plannedOn: localDate(plan.plannedOn),
426
+ activity: activity(plan.activity),
427
+ title: plan.title,
428
+ completedAt: plan.completedAt.map(BridgeInstant.format))
429
+ }
430
+
431
+ /// Sorted, because a `Set` has no order and JSON has no set.
432
+ static func access(_ access: TerraSDK.HealthAccess) -> HealthAccess {
433
+ HealthAccess(
434
+ read: access.read.compactMap(category).sorted { $0.rawValue < $1.rawValue },
435
+ write: access.write.compactMap(category).sorted { $0.rawValue < $1.rawValue },
436
+ workouts: ordered(access.workouts
437
+ .map { WorkoutScope(activity: activity($0.activity), energy: $0.energy, distance: $0.distance) }))
438
+ }
439
+
440
+ static func ordered(_ scopes: [WorkoutScope]) -> [WorkoutScope] {
441
+ scopes.sorted { ($0.sortKey, $0.wireKey) < ($1.sortKey, $1.wireKey) }
442
+ }
443
+
444
+ static func scope(_ wire: WorkoutScope) -> TerraSDK.WorkoutScope {
445
+ TerraSDK.WorkoutScope(activity: activity(wire.activity), energy: wire.energy, distance: wire.distance)
446
+ }
447
+
448
+ // MARK: Diagnostics
449
+
450
+ /// The SDK's own `Encodable` spelling, with the bridge's instant format. Not reshaped: a second
451
+ /// encoding here would be a contract no vector pins.
452
+ static func event(_ event: TerraSDK.TerraEvent) throws -> TerraEvent {
453
+ let encoder = JSONEncoder()
454
+ encoder.dateEncodingStrategy = .custom { date, encoder in
455
+ var container = encoder.singleValueContainer()
456
+ try container.encode(BridgeInstant.format(date))
457
+ }
458
+ let json = try BridgeDispatch.makeDecoder().decode(TerraEvent.self, from: encoder.encode(event))
459
+ try TerraEventEnvelope.validate(json)
460
+ return json
461
+ }
462
+
463
+ static func report(_ report: TerraSDK.SupportReport) throws -> SupportReport {
464
+ SupportReport(
465
+ sdkVersion: report.sdkVersion,
466
+ generatedAt: BridgeInstant.format(report.generatedAt),
467
+ state: diagnosticsState(report.state),
468
+ lastDelivery: report.lastDelivery.map(BridgeInstant.format),
469
+ isConnected: report.isConnected,
470
+ requestedCategories: report.requestedCategories,
471
+ requestedWrites: report.requestedWrites,
472
+ requestedWorkouts: report.requestedWorkouts,
473
+ recentEvents: try report.recentEvents.map(event))
474
+ }
475
+
476
+ // MARK: Write entries
477
+
478
+ /// An absent record id is left to the SDK's own default rather than minted here.
479
+ static func entry(_ wire: HeartRateEntry, at path: String) throws -> TerraSDK.HeartRateEntry {
480
+ let time = try instant(wire.time, at: "\(path).time")
481
+ guard let id = wire.recordId else { return .init(time: time, beatsPerMinute: wire.beatsPerMinute) }
482
+ return .init(time: time, beatsPerMinute: wire.beatsPerMinute, recordID: try uuid(id, at: "\(path).recordId"))
483
+ }
484
+
485
+ static func entry(_ wire: StepEntry, at path: String) throws -> TerraSDK.StepEntry {
486
+ let interval = try Self.interval(start: wire.start, end: wire.end, at: path)
487
+ guard let id = wire.recordId else { return .init(interval: interval, steps: wire.steps) }
488
+ return .init(interval: interval, steps: wire.steps, recordID: try uuid(id, at: "\(path).recordId"))
489
+ }
490
+
491
+ static func entry(_ wire: SleepEntry, at path: String) throws -> TerraSDK.SleepEntry {
492
+ let interval = try Self.interval(start: wire.start, end: wire.end, at: path)
493
+ let stage = sleepStage(wire.stage)
494
+ guard let id = wire.recordId else { return .init(interval: interval, stage: stage) }
495
+ return .init(interval: interval, stage: stage, recordID: try uuid(id, at: "\(path).recordId"))
496
+ }
497
+
498
+ static func entry(_ wire: WorkoutEntry, at path: String) throws -> TerraSDK.WorkoutEntry {
499
+ let interval = try Self.interval(start: wire.start, end: wire.end, at: path)
500
+ let activity = Self.activity(wire.activity)
501
+ guard let id = wire.recordId else {
502
+ return .init(
503
+ interval: interval, activity: activity,
504
+ totalEnergyKilocalories: wire.totalEnergyKilocalories,
505
+ totalDistanceMeters: wire.totalDistanceMeters)
506
+ }
507
+ return .init(
508
+ interval: interval, activity: activity,
509
+ totalEnergyKilocalories: wire.totalEnergyKilocalories,
510
+ totalDistanceMeters: wire.totalDistanceMeters,
511
+ recordID: try uuid(id, at: "\(path).recordId"))
512
+ }
513
+
514
+ static func entry(_ wire: BodyEntry, at path: String) throws -> TerraSDK.BodyEntry {
515
+ let time = try instant(wire.time, at: "\(path).time")
516
+ var entry = try wire.recordId.map { TerraSDK.BodyEntry(time: time, recordID: try uuid($0, at: "\(path).recordId")) }
517
+ ?? TerraSDK.BodyEntry(time: time)
518
+ entry.bodyMassKilograms = wire.bodyMassKilograms
519
+ entry.bodyFatPercent = wire.bodyFatPercent
520
+ entry.bodyMassIndex = wire.bodyMassIndex
521
+ entry.heightMeters = wire.heightMeters
522
+ entry.leanBodyMassKilograms = wire.leanBodyMassKilograms
523
+ entry.bodyTemperatureCelsius = wire.bodyTemperatureCelsius
524
+ entry.oxygenSaturationPercent = wire.oxygenSaturationPercent
525
+ entry.bloodGlucoseMilligramsPerDeciliter = wire.bloodGlucoseMilligramsPerDeciliter
526
+ entry.bloodPressureSystolicMillimetersOfMercury = wire.bloodPressureSystolicMillimetersOfMercury
527
+ entry.bloodPressureDiastolicMillimetersOfMercury = wire.bloodPressureDiastolicMillimetersOfMercury
528
+ return entry
529
+ }
530
+
531
+ static func entry(_ wire: NutritionEntry, at path: String) throws -> TerraSDK.NutritionEntry {
532
+ let interval = try Self.interval(start: wire.start, end: wire.end, at: path)
533
+ let nutrients = Self.nutrients(wire.nutrients)
534
+ guard let id = wire.recordId else {
535
+ return .init(interval: interval, name: wire.name, nutrients: nutrients)
536
+ }
537
+ return .init(
538
+ interval: interval, name: wire.name, nutrients: nutrients, recordID: try uuid(id, at: "\(path).recordId"))
539
+ }
540
+
541
+ /// All thirty-nine, by name. The unit is in every name on both sides, so a mismatched pair
542
+ /// does not compile.
543
+ private static func nutrients(_ wire: Nutrients) -> TerraSDK.NutritionEntry.Nutrients {
544
+ var out = TerraSDK.NutritionEntry.Nutrients()
545
+ out.energyKilocalories = wire.energyKilocalories
546
+ out.proteinGrams = wire.proteinGrams
547
+ out.carbohydratesGrams = wire.carbohydratesGrams
548
+ out.fiberGrams = wire.fiberGrams
549
+ out.sugarGrams = wire.sugarGrams
550
+ out.fatTotalGrams = wire.fatTotalGrams
551
+ out.fatSaturatedGrams = wire.fatSaturatedGrams
552
+ out.fatMonounsaturatedGrams = wire.fatMonounsaturatedGrams
553
+ out.fatPolyunsaturatedGrams = wire.fatPolyunsaturatedGrams
554
+ out.cholesterolMilligrams = wire.cholesterolMilligrams
555
+ out.sodiumMilligrams = wire.sodiumMilligrams
556
+ out.potassiumMilligrams = wire.potassiumMilligrams
557
+ out.calciumMilligrams = wire.calciumMilligrams
558
+ out.ironMilligrams = wire.ironMilligrams
559
+ out.magnesiumMilligrams = wire.magnesiumMilligrams
560
+ out.zincMilligrams = wire.zincMilligrams
561
+ out.waterLiters = wire.waterLiters
562
+ out.caffeineMilligrams = wire.caffeineMilligrams
563
+ out.vitaminAMicrograms = wire.vitaminAMicrograms
564
+ out.vitaminCMilligrams = wire.vitaminCMilligrams
565
+ out.vitaminDMicrograms = wire.vitaminDMicrograms
566
+ out.vitaminEMilligrams = wire.vitaminEMilligrams
567
+ out.vitaminKMicrograms = wire.vitaminKMicrograms
568
+ out.vitaminB6Milligrams = wire.vitaminB6Milligrams
569
+ out.vitaminB12Micrograms = wire.vitaminB12Micrograms
570
+ out.folateMicrograms = wire.folateMicrograms
571
+ out.thiaminMilligrams = wire.thiaminMilligrams
572
+ out.riboflavinMilligrams = wire.riboflavinMilligrams
573
+ out.niacinMilligrams = wire.niacinMilligrams
574
+ out.biotinMicrograms = wire.biotinMicrograms
575
+ out.pantothenicAcidMilligrams = wire.pantothenicAcidMilligrams
576
+ out.phosphorusMilligrams = wire.phosphorusMilligrams
577
+ out.iodineMicrograms = wire.iodineMicrograms
578
+ out.chromiumMicrograms = wire.chromiumMicrograms
579
+ out.copperMilligrams = wire.copperMilligrams
580
+ out.manganeseMilligrams = wire.manganeseMilligrams
581
+ out.molybdenumMicrograms = wire.molybdenumMicrograms
582
+ out.seleniumMicrograms = wire.seleniumMicrograms
583
+ out.chlorideMilligrams = wire.chlorideMilligrams
584
+ return out
585
+ }
586
+ }
587
+
588
+ private extension WorkoutScope {
589
+ var sortKey: String { Conversions.sortKey(activity) }
590
+
591
+ var wireKey: String {
592
+ (try? BridgeDispatch.makeEncoder().encode(self)).map { String(decoding: $0, as: UTF8.self) } ?? ""
593
+ }
594
+ }