@nativesquare/soma 0.2.0 → 0.4.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 (105) hide show
  1. package/dist/client/index.d.ts +167 -0
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +150 -0
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/component/_generated/api.d.ts +2 -0
  6. package/dist/component/_generated/api.d.ts.map +1 -1
  7. package/dist/component/_generated/api.js.map +1 -1
  8. package/dist/component/_generated/component.d.ts +56 -0
  9. package/dist/component/_generated/component.d.ts.map +1 -1
  10. package/dist/component/garmin.d.ts +110 -0
  11. package/dist/component/garmin.d.ts.map +1 -0
  12. package/dist/component/garmin.js +454 -0
  13. package/dist/component/garmin.js.map +1 -0
  14. package/dist/component/public.d.ts +761 -761
  15. package/dist/component/schema.d.ts +390 -388
  16. package/dist/component/schema.d.ts.map +1 -1
  17. package/dist/component/schema.js +3 -2
  18. package/dist/component/schema.js.map +1 -1
  19. package/dist/component/strava.d.ts +5 -4
  20. package/dist/component/strava.d.ts.map +1 -1
  21. package/dist/component/strava.js +18 -1
  22. package/dist/component/strava.js.map +1 -1
  23. package/dist/component/validators/activity.d.ts +42 -42
  24. package/dist/component/validators/body.d.ts +47 -47
  25. package/dist/component/validators/daily.d.ts +17 -17
  26. package/dist/component/validators/plannedWorkout.d.ts +5 -5
  27. package/dist/component/validators/samples.d.ts +2 -2
  28. package/dist/component/validators/shared.d.ts +17 -17
  29. package/dist/component/validators/sleep.d.ts +17 -17
  30. package/dist/garmin/activity.d.ts +101 -0
  31. package/dist/garmin/activity.d.ts.map +1 -0
  32. package/dist/garmin/activity.js +207 -0
  33. package/dist/garmin/activity.js.map +1 -0
  34. package/dist/garmin/auth.d.ts +65 -0
  35. package/dist/garmin/auth.d.ts.map +1 -0
  36. package/dist/garmin/auth.js +155 -0
  37. package/dist/garmin/auth.js.map +1 -0
  38. package/dist/garmin/body.d.ts +26 -0
  39. package/dist/garmin/body.d.ts.map +1 -0
  40. package/dist/garmin/body.js +44 -0
  41. package/dist/garmin/body.js.map +1 -0
  42. package/dist/garmin/client.d.ts +99 -0
  43. package/dist/garmin/client.d.ts.map +1 -0
  44. package/dist/garmin/client.js +153 -0
  45. package/dist/garmin/client.js.map +1 -0
  46. package/dist/garmin/daily.d.ts +74 -0
  47. package/dist/garmin/daily.d.ts.map +1 -0
  48. package/dist/garmin/daily.js +143 -0
  49. package/dist/garmin/daily.js.map +1 -0
  50. package/dist/garmin/index.d.ts +20 -0
  51. package/dist/garmin/index.d.ts.map +1 -0
  52. package/dist/garmin/index.js +21 -0
  53. package/dist/garmin/index.js.map +1 -0
  54. package/dist/garmin/maps/activity-type.d.ts +7 -0
  55. package/dist/garmin/maps/activity-type.d.ts.map +1 -0
  56. package/dist/garmin/maps/activity-type.js +98 -0
  57. package/dist/garmin/maps/activity-type.js.map +1 -0
  58. package/dist/garmin/maps/sleep-level.d.ts +6 -0
  59. package/dist/garmin/maps/sleep-level.d.ts.map +1 -0
  60. package/dist/garmin/maps/sleep-level.js +21 -0
  61. package/dist/garmin/maps/sleep-level.js.map +1 -0
  62. package/dist/garmin/menstruation.d.ts +23 -0
  63. package/dist/garmin/menstruation.d.ts.map +1 -0
  64. package/dist/garmin/menstruation.js +34 -0
  65. package/dist/garmin/menstruation.js.map +1 -0
  66. package/dist/garmin/sleep.d.ts +62 -0
  67. package/dist/garmin/sleep.d.ts.map +1 -0
  68. package/dist/garmin/sleep.js +125 -0
  69. package/dist/garmin/sleep.js.map +1 -0
  70. package/dist/garmin/sync.d.ts +39 -0
  71. package/dist/garmin/sync.d.ts.map +1 -0
  72. package/dist/garmin/sync.js +175 -0
  73. package/dist/garmin/sync.js.map +1 -0
  74. package/dist/garmin/types.d.ts +212 -0
  75. package/dist/garmin/types.d.ts.map +1 -0
  76. package/dist/garmin/types.js +8 -0
  77. package/dist/garmin/types.js.map +1 -0
  78. package/dist/validators.d.ts +6617 -0
  79. package/dist/validators.d.ts.map +1 -0
  80. package/dist/validators.js +78 -0
  81. package/dist/validators.js.map +1 -0
  82. package/package.json +9 -1
  83. package/src/client/index.ts +194 -1
  84. package/src/component/_generated/api.ts +2 -0
  85. package/src/component/_generated/component.ts +62 -0
  86. package/src/component/garmin.ts +534 -0
  87. package/src/component/schema.ts +3 -2
  88. package/src/component/strava.ts +23 -1
  89. package/src/garmin/activity.test.ts +178 -0
  90. package/src/garmin/activity.ts +272 -0
  91. package/src/garmin/auth.test.ts +128 -0
  92. package/src/garmin/auth.ts +249 -0
  93. package/src/garmin/body.ts +59 -0
  94. package/src/garmin/client.ts +254 -0
  95. package/src/garmin/daily.ts +211 -0
  96. package/src/garmin/index.ts +76 -0
  97. package/src/garmin/maps/activity-type.test.ts +78 -0
  98. package/src/garmin/maps/activity-type.ts +116 -0
  99. package/src/garmin/maps/sleep-level.ts +22 -0
  100. package/src/garmin/menstruation.ts +42 -0
  101. package/src/garmin/sleep.test.ts +110 -0
  102. package/src/garmin/sleep.ts +170 -0
  103. package/src/garmin/sync.ts +223 -0
  104. package/src/garmin/types.ts +338 -0
  105. package/src/validators.ts +89 -0
@@ -0,0 +1,338 @@
1
+ // ─── Garmin Health API Response Types ────────────────────────────────────────
2
+ // TypeScript interfaces representing Garmin Health API data shapes.
3
+ // Derived from the Garmin Connect Developer Program documentation.
4
+ //
5
+ // These types define the CONTRACT for data coming from the Garmin API.
6
+ // They are used by transformers, the API client, and OAuth helpers.
7
+
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;
132
+ timeOffsetStressLevelValues?: Record<string, number>;
133
+
134
+ // Body battery
135
+ timeOffsetBodyBatteryValues?: Record<string, number>;
136
+
137
+ // Pulse Ox
138
+ averageSpo2Value?: number;
139
+ lowestSpo2Value?: number;
140
+ latestSpo2Value?: number;
141
+ timeOffsetSpo2Values?: Record<string, number>;
142
+
143
+ // Respiration
144
+ averageRespirationInBreathsPerMinute?: number;
145
+ lowestRespirationInBreathsPerMinute?: number;
146
+ highestRespirationInBreathsPerMinute?: number;
147
+ 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;
167
+
168
+ // Nap
169
+ totalNapDurationInSeconds?: number;
170
+
171
+ // Validation
172
+ validation: "ENHANCED_TENTATIVE" | "ENHANCED_FINAL" | "AUTO_TENTATIVE" | "AUTO_FINAL" | "MANUAL" | string;
173
+
174
+ // Respiration
175
+ averageRespirationInBreathsPerMinute?: number;
176
+ lowestRespirationInBreathsPerMinute?: number;
177
+ highestRespirationInBreathsPerMinute?: number;
178
+ timeOffsetSleepRespiration?: Record<string, number>;
179
+
180
+ // SpO2
181
+ averageSpo2Value?: number;
182
+ lowestSpo2Value?: number;
183
+ 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
195
+ 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
+ }
249
+
250
+ // ─── Garmin Activity Type ───────────────────────────────────────────────────
251
+ // Known Garmin activity type strings from the API.
252
+
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 & {});
316
+
317
+ // ─── OAuth ──────────────────────────────────────────────────────────────────
318
+
319
+ export interface GarminOAuthRequestTokenResponse {
320
+ oauthToken: string;
321
+ oauthTokenSecret: string;
322
+ }
323
+
324
+ export interface GarminOAuthAccessTokenResponse {
325
+ oauthToken: string;
326
+ oauthTokenSecret: string;
327
+ }
328
+
329
+ // ─── API Response Wrappers ──────────────────────────────────────────────────
330
+
331
+ export interface GarminWebhookPayload {
332
+ activities?: Array<{ userId: string; summaryId: string; callbackURL: string }>;
333
+ activityDetails?: Array<{ userId: string; summaryId: string; callbackURL: string }>;
334
+ dailies?: Array<{ userId: string; summaryId: string; callbackURL: string }>;
335
+ sleeps?: Array<{ userId: string; summaryId: string; callbackURL: string }>;
336
+ bodyComps?: Array<{ userId: string; summaryId: string; callbackURL: string }>;
337
+ stressDetails?: Array<{ userId: string; summaryId: string; callbackURL: string }>;
338
+ }
@@ -0,0 +1,89 @@
1
+ // ─── Soma Validators ─────────────────────────────────────────────────────────
2
+ // Re-exports the Convex validators for each Soma table so host apps can use
3
+ // them for type-safe argument validation in their own mutations.
4
+ //
5
+ // Two flavors are exported for each table:
6
+ //
7
+ // *Validator — Full validator including connectionId (v.string()) and userId.
8
+ // Use when the mutation args map 1:1 to a soma.ingestX() call.
9
+ //
10
+ // *Data — Data-only validator (no connectionId/userId).
11
+ // Use when connection is resolved server-side and the client
12
+ // only sends the health data payload.
13
+ //
14
+ // ─── Examples ────────────────────────────────────────────────────────────────
15
+ //
16
+ // Direct ingest (client provides connectionId):
17
+ //
18
+ // import { activityValidator } from "@nativesquare/soma/validators";
19
+ //
20
+ // export const storeActivity = mutation({
21
+ // args: activityValidator,
22
+ // handler: async (ctx, args) => {
23
+ // await soma.ingestActivity(ctx, args);
24
+ // },
25
+ // });
26
+ //
27
+ // Batch sync (connection resolved server-side):
28
+ //
29
+ // import { activityData } from "@nativesquare/soma/validators";
30
+ //
31
+ // export const syncHealthKit = mutation({
32
+ // args: { activities: v.array(v.object(activityData)) },
33
+ // handler: async (ctx, args) => {
34
+ // const connectionId = await soma.connect(ctx, { userId, provider: "HEALTHKIT" });
35
+ // for (const activity of args.activities) {
36
+ // await soma.ingestActivity(ctx, { connectionId, userId, ...activity });
37
+ // }
38
+ // },
39
+ // });
40
+
41
+ import { v } from "convex/values";
42
+ import { connectionValidator as _connectionValidator } from "./component/validators/connection.js";
43
+ import { athleteValidator as _athleteValidator } from "./component/validators/athlete.js";
44
+ import { activityValidator as _activityValidator } from "./component/validators/activity.js";
45
+ import { bodyValidator as _bodyValidator } from "./component/validators/body.js";
46
+ import { dailyValidator as _dailyValidator } from "./component/validators/daily.js";
47
+ import { sleepValidator as _sleepValidator } from "./component/validators/sleep.js";
48
+ import { menstruationValidator as _menstruationValidator } from "./component/validators/menstruation.js";
49
+ import { nutritionValidator as _nutritionValidator } from "./component/validators/nutrition.js";
50
+ import { plannedWorkoutValidator as _plannedWorkoutValidator } from "./component/validators/plannedWorkout.js";
51
+
52
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
53
+
54
+ const asString = { connectionId: v.string() };
55
+
56
+ type WithConnection = { connectionId: unknown; userId: unknown };
57
+
58
+ function stripConnection<T extends WithConnection>(
59
+ validator: T,
60
+ ): Omit<T, "connectionId" | "userId"> {
61
+ const { connectionId, userId, ...rest } = validator;
62
+ return rest as Omit<T, "connectionId" | "userId">;
63
+ }
64
+
65
+ // ─── Full validators (connectionId as v.string() + userId) ───────────────────
66
+
67
+ export const connectionValidator = _connectionValidator;
68
+ export const athleteValidator = { ..._athleteValidator, ...asString };
69
+ export const activityValidator = { ..._activityValidator, ...asString };
70
+ export const bodyValidator = { ..._bodyValidator, ...asString };
71
+ export const dailyValidator = { ..._dailyValidator, ...asString };
72
+ export const sleepValidator = { ..._sleepValidator, ...asString };
73
+ export const menstruationValidator = { ..._menstruationValidator, ...asString };
74
+ export const nutritionValidator = { ..._nutritionValidator, ...asString };
75
+ export const plannedWorkoutValidator = {
76
+ ..._plannedWorkoutValidator,
77
+ ...asString,
78
+ };
79
+
80
+ // ─── Data-only validators (no connectionId / userId) ─────────────────────────
81
+
82
+ export const athleteData = stripConnection(_athleteValidator);
83
+ export const activityData = stripConnection(_activityValidator);
84
+ export const bodyData = stripConnection(_bodyValidator);
85
+ export const dailyData = stripConnection(_dailyValidator);
86
+ export const sleepData = stripConnection(_sleepValidator);
87
+ export const menstruationData = stripConnection(_menstruationValidator);
88
+ export const nutritionData = stripConnection(_nutritionValidator);
89
+ export const plannedWorkoutData = stripConnection(_plannedWorkoutValidator);