@nativesquare/soma 0.7.3 → 0.8.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 (134) hide show
  1. package/dist/client/index.d.ts +83 -0
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +131 -0
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/component/_generated/component.d.ts +159 -0
  6. package/dist/component/_generated/component.d.ts.map +1 -1
  7. package/dist/component/garmin.d.ts +190 -6
  8. package/dist/component/garmin.d.ts.map +1 -1
  9. package/dist/component/garmin.js +805 -25
  10. package/dist/component/garmin.js.map +1 -1
  11. package/dist/component/private.d.ts +18 -0
  12. package/dist/component/private.d.ts.map +1 -1
  13. package/dist/component/private.js +18 -0
  14. package/dist/component/private.js.map +1 -1
  15. package/dist/component/public.d.ts +88 -42
  16. package/dist/component/public.d.ts.map +1 -1
  17. package/dist/component/public.js +12 -2
  18. package/dist/component/public.js.map +1 -1
  19. package/dist/component/schema.d.ts +87 -32
  20. package/dist/component/schema.d.ts.map +1 -1
  21. package/dist/component/schema.js +2 -1
  22. package/dist/component/schema.js.map +1 -1
  23. package/dist/component/validators/connection.d.ts +1 -0
  24. package/dist/component/validators/connection.d.ts.map +1 -1
  25. package/dist/component/validators/connection.js +2 -0
  26. package/dist/component/validators/connection.js.map +1 -1
  27. package/dist/component/validators/daily.d.ts +40 -5
  28. package/dist/component/validators/daily.d.ts.map +1 -1
  29. package/dist/component/validators/daily.js +10 -1
  30. package/dist/component/validators/daily.js.map +1 -1
  31. package/dist/component/validators/enums.d.ts +1 -1
  32. package/dist/component/validators/plannedWorkout.d.ts +5 -1
  33. package/dist/component/validators/plannedWorkout.d.ts.map +1 -1
  34. package/dist/component/validators/plannedWorkout.js +4 -0
  35. package/dist/component/validators/plannedWorkout.js.map +1 -1
  36. package/dist/component/validators/sleep.d.ts +8 -8
  37. package/dist/garmin/activity.d.ts +7 -16
  38. package/dist/garmin/activity.d.ts.map +1 -1
  39. package/dist/garmin/activity.js +17 -23
  40. package/dist/garmin/activity.js.map +1 -1
  41. package/dist/garmin/bloodPressure.d.ts +28 -0
  42. package/dist/garmin/bloodPressure.d.ts.map +1 -0
  43. package/dist/garmin/bloodPressure.js +34 -0
  44. package/dist/garmin/bloodPressure.js.map +1 -0
  45. package/dist/garmin/body.js +1 -1
  46. package/dist/garmin/body.js.map +1 -1
  47. package/dist/garmin/client.d.ts +117 -17
  48. package/dist/garmin/client.d.ts.map +1 -1
  49. package/dist/garmin/client.js +337 -43
  50. package/dist/garmin/client.js.map +1 -1
  51. package/dist/garmin/daily.d.ts.map +1 -1
  52. package/dist/garmin/daily.js +3 -3
  53. package/dist/garmin/daily.js.map +1 -1
  54. package/dist/garmin/hrv.d.ts +30 -0
  55. package/dist/garmin/hrv.d.ts.map +1 -0
  56. package/dist/garmin/hrv.js +45 -0
  57. package/dist/garmin/hrv.js.map +1 -0
  58. package/dist/garmin/index.d.ts +16 -2
  59. package/dist/garmin/index.d.ts.map +1 -1
  60. package/dist/garmin/index.js +8 -1
  61. package/dist/garmin/index.js.map +1 -1
  62. package/dist/garmin/maps/activity-type.d.ts +1 -2
  63. package/dist/garmin/maps/activity-type.d.ts.map +1 -1
  64. package/dist/garmin/maps/activity-type.js +1 -0
  65. package/dist/garmin/maps/activity-type.js.map +1 -1
  66. package/dist/garmin/menstruation.d.ts +6 -4
  67. package/dist/garmin/menstruation.d.ts.map +1 -1
  68. package/dist/garmin/menstruation.js +12 -8
  69. package/dist/garmin/menstruation.js.map +1 -1
  70. package/dist/garmin/pulseOx.d.ts +24 -0
  71. package/dist/garmin/pulseOx.d.ts.map +1 -0
  72. package/dist/garmin/pulseOx.js +33 -0
  73. package/dist/garmin/pulseOx.js.map +1 -0
  74. package/dist/garmin/respiration.d.ts +29 -0
  75. package/dist/garmin/respiration.d.ts.map +1 -0
  76. package/dist/garmin/respiration.js +42 -0
  77. package/dist/garmin/respiration.js.map +1 -0
  78. package/dist/garmin/skinTemp.d.ts +27 -0
  79. package/dist/garmin/skinTemp.d.ts.map +1 -0
  80. package/dist/garmin/skinTemp.js +35 -0
  81. package/dist/garmin/skinTemp.js.map +1 -0
  82. package/dist/garmin/sleep.d.ts +4 -4
  83. package/dist/garmin/sleep.d.ts.map +1 -1
  84. package/dist/garmin/sleep.js +15 -9
  85. package/dist/garmin/sleep.js.map +1 -1
  86. package/dist/garmin/stressDetails.d.ts +30 -0
  87. package/dist/garmin/stressDetails.d.ts.map +1 -0
  88. package/dist/garmin/stressDetails.js +49 -0
  89. package/dist/garmin/stressDetails.js.map +1 -0
  90. package/dist/garmin/sync.d.ts +14 -0
  91. package/dist/garmin/sync.d.ts.map +1 -1
  92. package/dist/garmin/sync.js +287 -5
  93. package/dist/garmin/sync.js.map +1 -1
  94. package/dist/garmin/types.d.ts +77 -186
  95. package/dist/garmin/types.d.ts.map +1 -1
  96. package/dist/garmin/types.js +4 -2
  97. package/dist/garmin/types.js.map +1 -1
  98. package/dist/garmin/userMetrics.d.ts +23 -0
  99. package/dist/garmin/userMetrics.d.ts.map +1 -0
  100. package/dist/garmin/userMetrics.js +41 -0
  101. package/dist/garmin/userMetrics.js.map +1 -0
  102. package/dist/validators.d.ts +107 -28
  103. package/dist/validators.d.ts.map +1 -1
  104. package/package.json +133 -124
  105. package/src/client/index.ts +199 -0
  106. package/src/component/_generated/component.ts +161 -2
  107. package/src/component/garmin.ts +898 -26
  108. package/src/component/private.ts +21 -0
  109. package/src/component/public.ts +11 -2
  110. package/src/component/schema.ts +2 -1
  111. package/src/component/validators/connection.ts +2 -0
  112. package/src/component/validators/daily.ts +15 -0
  113. package/src/component/validators/plannedWorkout.ts +4 -0
  114. package/src/garmin/activity.test.ts +13 -21
  115. package/src/garmin/activity.ts +38 -45
  116. package/src/garmin/bloodPressure.ts +41 -0
  117. package/src/garmin/body.ts +1 -1
  118. package/src/garmin/client.ts +550 -71
  119. package/src/garmin/daily.ts +8 -4
  120. package/src/garmin/hrv.ts +57 -0
  121. package/src/garmin/index.ts +77 -7
  122. package/src/garmin/maps/activity-type.ts +2 -2
  123. package/src/garmin/menstruation.ts +14 -12
  124. package/src/garmin/pulseOx.ts +45 -0
  125. package/src/garmin/respiration.ts +55 -0
  126. package/src/garmin/skinTemp.ts +42 -0
  127. package/src/garmin/sleep.test.ts +5 -6
  128. package/src/garmin/sleep.ts +22 -16
  129. package/src/garmin/spec/wellness-api.json +1 -0
  130. package/src/garmin/stressDetails.ts +71 -0
  131. package/src/garmin/sync.ts +348 -5
  132. package/src/garmin/types.ts +88 -300
  133. package/src/garmin/userMetrics.ts +50 -0
  134. package/src/garmin/wellness-api.d.ts +5637 -0
@@ -1,318 +1,100 @@
1
1
  // ─── Garmin Health API Response Types ────────────────────────────────────────
2
- // TypeScript interfaces representing Garmin Health API data shapes.
3
- // Derived from the Garmin Connect Developer Program documentation.
2
+ // TypeScript types representing Garmin Health API data shapes.
3
+ //
4
+ // Wellness API types are derived from the OpenAPI spec (wellness-api.d.ts).
5
+ // Training API types are manually defined from the Garmin Connect docs.
4
6
  //
5
7
  // These types define the CONTRACT for data coming from the Garmin API.
6
8
  // They are used by transformers, the API client, and OAuth helpers.
7
9
 
8
- // ─── Activity ─────────────────────────────────────────────────────────────────
9
-
10
- export interface GarminActivity {
11
- userId: string;
12
- userAccessToken: string;
13
- summaryId: string;
14
- activityId: number;
15
- activityName?: string;
16
- activityType: string;
17
- durationInSeconds: number;
18
- startTimeInSeconds: number;
19
- startTimeOffsetInSeconds: number;
20
- deviceName?: string;
21
- manual?: boolean;
22
-
23
- // Distance
24
- distanceInMeters?: number;
25
- elevationGainInMeters?: number;
26
- elevationLossInMeters?: number;
27
-
28
- // Calories
29
- activeKilocalories?: number;
30
- bmrKilocalories?: number;
31
-
32
- // Heart rate
33
- averageHeartRateInBeatsPerMinute?: number;
34
- maxHeartRateInBeatsPerMinute?: number;
35
-
36
- // Speed / pace
37
- averageSpeedInMetersPerSecond?: number;
38
- maxSpeedInMetersPerSecond?: number;
39
- averagePaceInMinutesPerKilometer?: number;
40
- maxPaceInMinutesPerKilometer?: number;
41
-
42
- // Cadence
43
- averageRunCadenceInStepsPerMinute?: number;
44
- maxRunCadenceInStepsPerMinute?: number;
45
- averageBikeCadenceInRoundsPerMinute?: number;
46
- maxBikeCadenceInRoundsPerMinute?: number;
47
-
48
- // Power
49
- averagePowerInWatts?: number;
50
- maxPowerInWatts?: number;
51
- normalizedPowerInWatts?: number;
52
-
53
- // Swimming
54
- numberOfActiveLengths?: number;
55
- poolLengthInMeters?: number;
56
- averageSwimCadenceInStrokesPerMinute?: number;
57
-
58
- // Position
59
- startingLatitudeInDegree?: number;
60
- startingLongitudeInDegree?: number;
61
-
62
- // Steps
63
- steps?: number;
64
-
65
- // Laps
66
- laps?: GarminActivityLap[];
67
-
68
- // Samples (time-series)
69
- samples?: GarminActivitySample[];
70
- }
71
-
72
- export interface GarminActivityLap {
73
- startTimeInSeconds: number;
74
- airTemperatureCelsius?: number;
75
- heartRate?: number;
76
- totalDistanceInMeters?: number;
77
- timerDurationInSeconds: number;
78
- maxSpeed?: number;
79
- }
80
-
81
- export interface GarminActivitySample {
82
- startTimeInSeconds?: number;
83
- latitudeInDegree?: number;
84
- longitudeInDegree?: number;
85
- elevationInMeters?: number;
86
- heartRate?: number;
87
- speedMetersPerSecond?: number;
88
- bikeCadenceInRPM?: number;
89
- runCadenceInStepsPerMinute?: number;
90
- powerInWatts?: number;
91
- timerDurationInSeconds?: number;
92
- }
93
-
94
- // ─── Daily Summary ──────────────────────────────────────────────────────────
95
-
96
- export interface GarminDailySummary {
97
- userId: string;
98
- userAccessToken: string;
99
- summaryId: string;
100
- calendarDate: string; // "YYYY-MM-DD"
101
- startTimeInSeconds: number;
102
- startTimeOffsetInSeconds: number;
103
- durationInSeconds: number;
104
-
105
- // Activity
106
- steps?: number;
107
- distanceInMeters?: number;
108
- floorsClimbed?: number;
109
- activeTimeInSeconds?: number;
110
- activeKilocalories?: number;
111
- bmrKilocalories?: number;
112
- moderateIntensityDurationInSeconds?: number;
113
- vigorousIntensityDurationInSeconds?: number;
114
- intensityDurationGoalInSeconds?: number;
115
-
116
- // Heart rate
117
- minHeartRateInBeatsPerMinute?: number;
118
- maxHeartRateInBeatsPerMinute?: number;
119
- averageHeartRateInBeatsPerMinute?: number;
120
- restingHeartRateInBeatsPerMinute?: number;
121
- timeOffsetHeartRateSamples?: Record<string, number>;
122
-
123
- // Stress
124
- averageStressLevel?: number;
125
- maxStressLevel?: number;
126
- stressDurationInSeconds?: number;
127
- restStressDurationInSeconds?: number;
128
- activityStressDurationInSeconds?: number;
129
- lowStressDurationInSeconds?: number;
130
- mediumStressDurationInSeconds?: number;
131
- highStressDurationInSeconds?: number;
10
+ import type { components } from "./wellness-api.js";
11
+
12
+ // ─── Wellness API Type Aliases ──────────────────────────────────────────────
13
+ // Convenience aliases for the generated OpenAPI schema types.
14
+
15
+ export type GarminDaily = components["schemas"]["ClientDaily"];
16
+ export type GarminActivity = components["schemas"]["ClientActivity"];
17
+ export type GarminActivityDetail = components["schemas"]["ClientActivityDetail"];
18
+ export type GarminSleep = components["schemas"]["ClientSleep"];
19
+ export type GarminBodyComposition = components["schemas"]["ClientBodyComp"];
20
+ export type GarminMenstrualCycle = components["schemas"]["ClientSummarizedMenstrualCycle"];
21
+ export type GarminUserMetrics = components["schemas"]["ClientUserMetrics"];
22
+ export type GarminStressDetail = components["schemas"]["ClientStress"];
23
+ export type GarminSkinTemperature = components["schemas"]["ClientSkinTemp"];
24
+ export type GarminRespiration = components["schemas"]["ClientRespiration"];
25
+ export type GarminPulseOx = components["schemas"]["ClientDailySpo2Acclimation"];
26
+ export type GarminMoveIQEvent = components["schemas"]["ClientAutoActivityMoveIq"];
27
+ export type GarminHRVSummary = components["schemas"]["ClientHRVSummary"];
28
+ export type GarminHealthSnapshot = components["schemas"]["ClientHealthSnapshot"];
29
+ export type GarminEpoch = components["schemas"]["ClientEpoch"];
30
+ export type GarminBloodPressure = components["schemas"]["ClientBloodPressure"];
31
+ export type GarminSolar = components["schemas"]["ClientSolar"];
32
+ export type GarminSample = components["schemas"]["Sample"];
33
+ export type GarminLap = components["schemas"]["ClientLap"];
34
+ export type GarminTimeRange = components["schemas"]["TimeRange"];
35
+ export type GarminSleepScoreItem = components["schemas"]["ClientSleepScoreItem"];
36
+ export type GarminNap = components["schemas"]["ClientNaps"];
37
+
38
+ // ─── Extended Types ─────────────────────────────────────────────────────────
39
+ // The Garmin API returns additional fields beyond what the OpenAPI spec
40
+ // documents. These extended types add those fields so transformers can use them.
41
+
42
+ // Extended daily type with fields the API returns beyond what the spec documents
43
+ export type GarminDailyExtended = GarminDaily & {
44
+ // Time-series stress data (returned by API, not in spec)
132
45
  timeOffsetStressLevelValues?: Record<string, number>;
133
-
134
- // Body battery
135
46
  timeOffsetBodyBatteryValues?: Record<string, number>;
136
-
137
- // Pulse Ox
47
+ // SpO2 data (returned by API, not in spec)
138
48
  averageSpo2Value?: number;
139
49
  lowestSpo2Value?: number;
140
50
  latestSpo2Value?: number;
141
51
  timeOffsetSpo2Values?: Record<string, number>;
142
-
143
- // Respiration
52
+ // Respiration data (returned by API, not in spec)
144
53
  averageRespirationInBreathsPerMinute?: number;
145
54
  lowestRespirationInBreathsPerMinute?: number;
146
55
  highestRespirationInBreathsPerMinute?: number;
147
56
  timeOffsetRespirationSamples?: Record<string, number>;
148
- }
149
-
150
- // ─── Sleep ──────────────────────────────────────────────────────────────────
151
-
152
- export interface GarminSleep {
153
- userId: string;
154
- userAccessToken: string;
155
- summaryId: string;
156
- calendarDate: string;
157
- startTimeInSeconds: number;
158
- startTimeOffsetInSeconds: number;
159
- durationInSeconds: number;
160
- unmeasurableSleepInSeconds?: number;
161
-
162
- // Sleep stages
163
- deepSleepDurationInSeconds?: number;
164
- lightSleepDurationInSeconds?: number;
165
- remSleepInSeconds?: number;
166
- awakeDurationInSeconds?: number;
57
+ };
167
58
 
168
- // Nap
169
- totalNapDurationInSeconds?: number;
170
-
171
- // Validation
172
- validation: "ENHANCED_TENTATIVE" | "ENHANCED_FINAL" | "AUTO_TENTATIVE" | "AUTO_FINAL" | "MANUAL" | string;
173
-
174
- // Respiration
59
+ // Extended sleep type with fields the API returns beyond what the spec documents
60
+ export type GarminSleepExtended = GarminSleep & {
61
+ // Respiration aggregates (returned by API, not in spec)
175
62
  averageRespirationInBreathsPerMinute?: number;
176
63
  lowestRespirationInBreathsPerMinute?: number;
177
64
  highestRespirationInBreathsPerMinute?: number;
178
- timeOffsetSleepRespiration?: Record<string, number>;
179
-
180
- // SpO2
65
+ // SpO2 aggregates (returned by API, not in spec)
181
66
  averageSpo2Value?: number;
182
67
  lowestSpo2Value?: number;
183
68
  highestSpo2Value?: number;
184
- timeOffsetSpo2Values?: Record<string, number>;
185
-
186
- // Sleep levels (time-series)
187
- sleepLevelsMap?: {
188
- deep?: GarminSleepLevel[];
189
- light?: GarminSleepLevel[];
190
- rem?: GarminSleepLevel[];
191
- awake?: GarminSleepLevel[];
192
- };
193
-
194
- // Heart rate
69
+ // Heart rate samples (returned by API, not in spec)
195
70
  timeOffsetHeartRateSamples?: Record<string, number>;
196
- }
197
-
198
- export interface GarminSleepLevel {
199
- startTimeInSeconds: number;
200
- endTimeInSeconds: number;
201
- }
202
-
203
- // ─── Body Composition ───────────────────────────────────────────────────────
204
-
205
- export interface GarminBodyComposition {
206
- userId: string;
207
- userAccessToken: string;
208
- summaryId?: string;
209
- measurementTimeInSeconds: number;
210
- measurementTimeOffsetInSeconds?: number;
211
-
212
- muscleMassInGrams?: number;
213
- boneMassInGrams?: number;
214
- bodyWaterInPercent?: number;
215
- bodyFatInPercent?: number;
216
- bodyMassIndex?: number;
217
- weightInGrams?: number;
218
- }
219
-
220
- // ─── Menstrual Cycle ────────────────────────────────────────────────────────
221
-
222
- export interface GarminMenstrualCycleData {
223
- userId: string;
224
- userAccessToken: string;
225
- summaryId?: string;
226
- calendarDate: string;
227
- startTimeInSeconds?: number;
228
- startTimeOffsetInSeconds?: number;
229
-
230
- dayInCycle?: number;
231
- currentPhase?: string;
232
- lengthOfCurrentPhase?: number;
233
- periodLength?: number;
234
- predictedCycleLength?: number;
235
- isPredictedCycle?: boolean;
236
- }
237
-
238
- // ─── User Profile ───────────────────────────────────────────────────────────
239
- // Limited profile info available from the Garmin Health API.
240
-
241
- export interface GarminUserProfile {
242
- userId: string;
243
- displayName?: string;
244
- weight?: number;
245
- height?: number;
246
- birthDate?: string;
247
- gender?: string;
248
- }
71
+ // Alternate SpO2 field name (some responses use this instead of timeOffsetSleepSpo2)
72
+ timeOffsetSpo2Values?: Record<string, number>;
73
+ };
74
+
75
+ // Extended activity type for webhook payloads which include detail-level fields
76
+ // (laps, samples, power) that the summary endpoint does not return.
77
+ export type GarminActivityExtended = GarminActivity & {
78
+ // Fields from /rest/activityDetails and webhook payloads
79
+ laps?: GarminLap[];
80
+ samples?: GarminSample[];
81
+ bmrKilocalories?: number;
82
+ averagePowerInWatts?: number;
83
+ maxPowerInWatts?: number;
84
+ normalizedPowerInWatts?: number;
85
+ poolLengthInMeters?: number;
86
+ // Webhook payloads may use these alternate field names
87
+ elevationGainInMeters?: number;
88
+ elevationLossInMeters?: number;
89
+ };
249
90
 
250
- // ─── Garmin Activity Type ───────────────────────────────────────────────────
251
- // Known Garmin activity type strings from the API.
91
+ // ─── Backward Compatibility Aliases ─────────────────────────────────────────
252
92
 
253
- export type GarminActivityType =
254
- | "RUNNING"
255
- | "INDOOR_RUNNING"
256
- | "TRAIL_RUNNING"
257
- | "TREADMILL_RUNNING"
258
- | "CYCLING"
259
- | "INDOOR_CYCLING"
260
- | "MOUNTAIN_BIKING"
261
- | "GRAVEL_CYCLING"
262
- | "VIRTUAL_RIDE"
263
- | "WALKING"
264
- | "HIKING"
265
- | "SWIMMING"
266
- | "OPEN_WATER_SWIMMING"
267
- | "LAP_SWIMMING"
268
- | "POOL_SWIMMING"
269
- | "STRENGTH_TRAINING"
270
- | "YOGA"
271
- | "PILATES"
272
- | "CARDIO"
273
- | "ELLIPTICAL"
274
- | "STAIR_CLIMBING"
275
- | "ROWING"
276
- | "INDOOR_ROWING"
277
- | "CROSS_COUNTRY_SKIING"
278
- | "ALPINE_SKIING"
279
- | "SNOWBOARDING"
280
- | "SNOWSHOEING"
281
- | "SKATING"
282
- | "INLINE_SKATING"
283
- | "GOLF"
284
- | "TENNIS"
285
- | "TABLE_TENNIS"
286
- | "BADMINTON"
287
- | "RACQUETBALL"
288
- | "SQUASH"
289
- | "PADDLEBOARDING"
290
- | "KAYAKING"
291
- | "CANOEING"
292
- | "SAILING"
293
- | "SURFING"
294
- | "KITESURFING"
295
- | "WINDSURFING"
296
- | "ROCK_CLIMBING"
297
- | "BOULDERING"
298
- | "SOCCER"
299
- | "BASKETBALL"
300
- | "VOLLEYBALL"
301
- | "CRICKET"
302
- | "RUGBY"
303
- | "BOXING"
304
- | "MARTIAL_ARTS"
305
- | "CROSSFIT"
306
- | "HIIT"
307
- | "BREATHWORK"
308
- | "FLOOR_CLIMBING"
309
- | "HANDCYCLING"
310
- | "WHEELCHAIR_PUSH_WALKING"
311
- | "WHEELCHAIR_PUSH_RUNNING"
312
- | "OTHER"
313
- | "MULTI_SPORT"
314
- | "FITNESS_EQUIPMENT"
315
- | (string & {});
93
+ export type GarminDailySummary = GarminDailyExtended;
94
+ export type GarminMenstrualCycleData = GarminMenstrualCycle;
95
+ export type GarminSleepLevel = GarminTimeRange;
96
+ export type GarminActivityLap = GarminLap;
97
+ export type GarminActivitySample = GarminSample;
316
98
 
317
99
  // ─── OAuth 2.0 ──────────────────────────────────────────────────────────────
318
100
 
@@ -468,13 +250,19 @@ export interface GarminWorkoutSchedule {
468
250
  date: string;
469
251
  }
470
252
 
471
- // ─── API Response Wrappers ──────────────────────────────────────────────────
472
-
473
- export interface GarminWebhookPayload {
474
- activities?: Array<{ userId: string; summaryId: string; callbackURL: string }>;
475
- activityDetails?: Array<{ userId: string; summaryId: string; callbackURL: string }>;
476
- dailies?: Array<{ userId: string; summaryId: string; callbackURL: string }>;
477
- sleeps?: Array<{ userId: string; summaryId: string; callbackURL: string }>;
478
- bodyComps?: Array<{ userId: string; summaryId: string; callbackURL: string }>;
479
- stressDetails?: Array<{ userId: string; summaryId: string; callbackURL: string }>;
480
- }
253
+ // ─── Push-Mode Webhook Payloads ─────────────────────────────────────────────
254
+ // In push mode, Garmin sends full data objects directly in the POST body.
255
+ // Each data type has its own webhook URL configured in the Garmin developer
256
+ // portal, so each payload is a typed array of the corresponding summary type.
257
+
258
+ export type GarminWebhookActivityPayload = (GarminActivity & { userAccessToken?: string })[];
259
+ export type GarminWebhookDailyPayload = (GarminDailyExtended & { userAccessToken?: string })[];
260
+ export type GarminWebhookSleepPayload = (GarminSleepExtended & { userAccessToken?: string })[];
261
+ export type GarminWebhookBodyPayload = (GarminBodyComposition & { userAccessToken?: string })[];
262
+ export type GarminWebhookBloodPressurePayload = (GarminBloodPressure & { userAccessToken?: string })[];
263
+ export type GarminWebhookSkinTempPayload = (GarminSkinTemperature & { userAccessToken?: string })[];
264
+ export type GarminWebhookUserMetricsPayload = (GarminUserMetrics & { userAccessToken?: string })[];
265
+ export type GarminWebhookHRVPayload = (GarminHRVSummary & { userAccessToken?: string })[];
266
+ export type GarminWebhookStressDetailPayload = (GarminStressDetail & { userAccessToken?: string })[];
267
+ export type GarminWebhookPulseOxPayload = (GarminPulseOx & { userAccessToken?: string })[];
268
+ export type GarminWebhookRespirationPayload = (GarminRespiration & { userAccessToken?: string })[];
@@ -0,0 +1,50 @@
1
+ // ─── User Metrics Transformer ────────────────────────────────────────────────
2
+ // Transforms Garmin user metrics into the Soma Body schema shape.
3
+
4
+ import type { GarminUserMetrics } from "./types.js";
5
+
6
+ export type UserMetricsData = ReturnType<typeof transformUserMetrics>;
7
+
8
+ /**
9
+ * Transform Garmin user metrics into a Soma Body document shape.
10
+ *
11
+ * Maps VO2 Max and fitness age into oxygen_data and scores fields.
12
+ *
13
+ * @param metrics - The Garmin user metrics from the Health API
14
+ * @returns Soma Body fields (without connectionId/userId)
15
+ */
16
+ export function transformUserMetrics(metrics: GarminUserMetrics) {
17
+ const dateStr = metrics.calendarDate;
18
+ const startTime = dateStr
19
+ ? `${dateStr}T00:00:00.000Z`
20
+ : new Date(0).toISOString();
21
+ const endTime = dateStr
22
+ ? `${dateStr}T23:59:59.999Z`
23
+ : new Date(0).toISOString();
24
+
25
+ const hasVo2 = metrics.vo2Max != null || metrics.vo2MaxCycling != null;
26
+ const hasFitnessAge = metrics.fitnessAge != null;
27
+
28
+ if (!hasVo2 && !hasFitnessAge) {
29
+ return {
30
+ metadata: { start_time: startTime, end_time: endTime },
31
+ oxygen_data: undefined,
32
+ scores: undefined,
33
+ };
34
+ }
35
+
36
+ return {
37
+ metadata: {
38
+ start_time: startTime,
39
+ end_time: endTime,
40
+ },
41
+
42
+ oxygen_data: hasVo2
43
+ ? { vo2max_ml_per_min_per_kg: metrics.vo2Max ?? metrics.vo2MaxCycling }
44
+ : undefined,
45
+
46
+ scores: hasFitnessAge
47
+ ? { biological_age: metrics.fitnessAge }
48
+ : undefined,
49
+ };
50
+ }