@nativesquare/soma 0.7.3 → 0.9.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.
- package/dist/client/index.d.ts +17 -55
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +63 -13
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +80 -4
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +153 -120
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/garmin/private.d.ts +475 -0
- package/dist/component/garmin/private.d.ts.map +1 -0
- package/dist/component/garmin/private.js +1614 -0
- package/dist/component/garmin/private.js.map +1 -0
- package/dist/component/{garmin.d.ts → garmin/public.d.ts} +25 -52
- package/dist/component/garmin/public.d.ts.map +1 -0
- package/dist/component/{garmin.js → garmin/public.js} +301 -215
- package/dist/component/garmin/public.js.map +1 -0
- package/dist/component/garmin/schemas/activity.d.ts +94 -0
- package/dist/component/garmin/schemas/activity.d.ts.map +1 -0
- package/dist/component/garmin/schemas/activity.js +27 -0
- package/dist/component/garmin/schemas/activity.js.map +1 -0
- package/dist/component/garmin/schemas/activityDetails.d.ts +146 -0
- package/dist/component/garmin/schemas/activityDetails.d.ts.map +1 -0
- package/dist/component/garmin/schemas/activityDetails.js +27 -0
- package/dist/component/garmin/schemas/activityDetails.js.map +1 -0
- package/dist/component/garmin/schemas/bloodPressure.d.ts +38 -0
- package/dist/component/garmin/schemas/bloodPressure.d.ts.map +1 -0
- package/dist/component/garmin/schemas/bloodPressure.js +27 -0
- package/dist/component/garmin/schemas/bloodPressure.js.map +1 -0
- package/dist/component/garmin/schemas/bodyCompositions.d.ts +42 -0
- package/dist/component/garmin/schemas/bodyCompositions.d.ts.map +1 -0
- package/dist/component/garmin/schemas/bodyCompositions.js +27 -0
- package/dist/component/garmin/schemas/bodyCompositions.js.map +1 -0
- package/dist/component/garmin/schemas/dailies.d.ts +98 -0
- package/dist/component/garmin/schemas/dailies.d.ts.map +1 -0
- package/dist/component/garmin/schemas/dailies.js +27 -0
- package/dist/component/garmin/schemas/dailies.js.map +1 -0
- package/dist/component/garmin/schemas/epochs.d.ts +54 -0
- package/dist/component/garmin/schemas/epochs.d.ts.map +1 -0
- package/dist/component/garmin/schemas/epochs.js +27 -0
- package/dist/component/garmin/schemas/epochs.js.map +1 -0
- package/dist/component/garmin/schemas/healthSnapshot.d.ts +48 -0
- package/dist/component/garmin/schemas/healthSnapshot.d.ts.map +1 -0
- package/dist/component/garmin/schemas/healthSnapshot.js +27 -0
- package/dist/component/garmin/schemas/healthSnapshot.js.map +1 -0
- package/dist/component/garmin/schemas/hrvSummary.d.ts +40 -0
- package/dist/component/garmin/schemas/hrvSummary.d.ts.map +1 -0
- package/dist/component/garmin/schemas/hrvSummary.js +27 -0
- package/dist/component/garmin/schemas/hrvSummary.js.map +1 -0
- package/dist/component/garmin/schemas/manuallyUpdatedActivities.d.ts +94 -0
- package/dist/component/garmin/schemas/manuallyUpdatedActivities.d.ts.map +1 -0
- package/dist/component/garmin/schemas/manuallyUpdatedActivities.js +27 -0
- package/dist/component/garmin/schemas/manuallyUpdatedActivities.js.map +1 -0
- package/dist/component/garmin/schemas/menstrualCycleTracking.d.ts +100 -0
- package/dist/component/garmin/schemas/menstrualCycleTracking.d.ts.map +1 -0
- package/dist/component/garmin/schemas/menstrualCycleTracking.js +28 -0
- package/dist/component/garmin/schemas/menstrualCycleTracking.js.map +1 -0
- package/dist/component/garmin/schemas/moveIQ.d.ts +40 -0
- package/dist/component/garmin/schemas/moveIQ.d.ts.map +1 -0
- package/dist/component/garmin/schemas/moveIQ.js +27 -0
- package/dist/component/garmin/schemas/moveIQ.js.map +1 -0
- package/dist/component/garmin/schemas/pulseOx.d.ts +38 -0
- package/dist/component/garmin/schemas/pulseOx.d.ts.map +1 -0
- package/dist/component/garmin/schemas/pulseOx.js +28 -0
- package/dist/component/garmin/schemas/pulseOx.js.map +1 -0
- package/dist/component/garmin/schemas/respiration.d.ts +34 -0
- package/dist/component/garmin/schemas/respiration.d.ts.map +1 -0
- package/dist/component/garmin/schemas/respiration.js +28 -0
- package/dist/component/garmin/schemas/respiration.js.map +1 -0
- package/dist/component/garmin/schemas/skinTemperature.d.ts +36 -0
- package/dist/component/garmin/schemas/skinTemperature.d.ts.map +1 -0
- package/dist/component/garmin/schemas/skinTemperature.js +28 -0
- package/dist/component/garmin/schemas/skinTemperature.js.map +1 -0
- package/dist/component/garmin/schemas/sleeps.d.ts +88 -0
- package/dist/component/garmin/schemas/sleeps.d.ts.map +1 -0
- package/dist/component/garmin/schemas/sleeps.js +27 -0
- package/dist/component/garmin/schemas/sleeps.js.map +1 -0
- package/dist/component/garmin/schemas/stress.d.ts +70 -0
- package/dist/component/garmin/schemas/stress.d.ts.map +1 -0
- package/dist/component/garmin/schemas/stress.js +28 -0
- package/dist/component/garmin/schemas/stress.js.map +1 -0
- package/dist/component/garmin/schemas/userMetrics.d.ts +36 -0
- package/dist/component/garmin/schemas/userMetrics.d.ts.map +1 -0
- package/dist/component/garmin/schemas/userMetrics.js +28 -0
- package/dist/component/garmin/schemas/userMetrics.js.map +1 -0
- package/dist/component/garmin/transform/activity.d.ts +13 -0
- package/dist/component/garmin/transform/activity.d.ts.map +1 -0
- package/dist/component/garmin/transform/activity.js +111 -0
- package/dist/component/garmin/transform/activity.js.map +1 -0
- package/dist/component/garmin/transform/activityDetails.d.ts +13 -0
- package/dist/component/garmin/transform/activityDetails.d.ts.map +1 -0
- package/dist/component/garmin/transform/activityDetails.js +173 -0
- package/dist/component/garmin/transform/activityDetails.js.map +1 -0
- package/dist/component/garmin/transform/bloodPressure.d.ts +12 -0
- package/dist/component/garmin/transform/bloodPressure.d.ts.map +1 -0
- package/dist/component/garmin/transform/bloodPressure.js +33 -0
- package/dist/component/garmin/transform/bloodPressure.js.map +1 -0
- package/dist/component/garmin/transform/bodyCompositions.d.ts +12 -0
- package/dist/component/garmin/transform/bodyCompositions.d.ts.map +1 -0
- package/dist/component/garmin/transform/bodyCompositions.js +42 -0
- package/dist/component/garmin/transform/bodyCompositions.js.map +1 -0
- package/dist/component/garmin/transform/dailies.d.ts +12 -0
- package/dist/component/garmin/transform/dailies.d.ts.map +1 -0
- package/dist/component/garmin/transform/dailies.js +132 -0
- package/dist/component/garmin/transform/dailies.js.map +1 -0
- package/dist/component/garmin/transform/epochs.d.ts +13 -0
- package/dist/component/garmin/transform/epochs.d.ts.map +1 -0
- package/dist/component/garmin/transform/epochs.js +76 -0
- package/dist/component/garmin/transform/epochs.js.map +1 -0
- package/dist/component/garmin/transform/healthSnapshot.d.ts +12 -0
- package/dist/component/garmin/transform/healthSnapshot.d.ts.map +1 -0
- package/dist/component/garmin/transform/healthSnapshot.js +111 -0
- package/dist/component/garmin/transform/healthSnapshot.js.map +1 -0
- package/dist/component/garmin/transform/hrvSummary.d.ts +12 -0
- package/dist/component/garmin/transform/hrvSummary.d.ts.map +1 -0
- package/dist/component/garmin/transform/hrvSummary.js +45 -0
- package/dist/component/garmin/transform/hrvSummary.js.map +1 -0
- package/dist/component/garmin/transform/manuallyUpdatedActivities.d.ts +11 -0
- package/dist/component/garmin/transform/manuallyUpdatedActivities.d.ts.map +1 -0
- package/dist/component/garmin/transform/manuallyUpdatedActivities.js +20 -0
- package/dist/component/garmin/transform/manuallyUpdatedActivities.js.map +1 -0
- package/dist/component/garmin/transform/menstrualCycleTracking.d.ts +10 -0
- package/dist/component/garmin/transform/menstrualCycleTracking.d.ts.map +1 -0
- package/dist/component/garmin/transform/menstrualCycleTracking.js +43 -0
- package/dist/component/garmin/transform/menstrualCycleTracking.js.map +1 -0
- package/dist/component/garmin/transform/moveIQ.d.ts +17 -0
- package/dist/component/garmin/transform/moveIQ.d.ts.map +1 -0
- package/dist/component/garmin/transform/moveIQ.js +41 -0
- package/dist/component/garmin/transform/moveIQ.js.map +1 -0
- package/dist/component/garmin/transform/pulseOx.d.ts +12 -0
- package/dist/component/garmin/transform/pulseOx.d.ts.map +1 -0
- package/dist/component/garmin/transform/pulseOx.js +46 -0
- package/dist/component/garmin/transform/pulseOx.js.map +1 -0
- package/dist/component/garmin/transform/respiration.d.ts +12 -0
- package/dist/component/garmin/transform/respiration.d.ts.map +1 -0
- package/dist/component/garmin/transform/respiration.js +54 -0
- package/dist/component/garmin/transform/respiration.js.map +1 -0
- package/dist/component/garmin/transform/skinTemperature.d.ts +12 -0
- package/dist/component/garmin/transform/skinTemperature.d.ts.map +1 -0
- package/dist/component/garmin/transform/skinTemperature.js +38 -0
- package/dist/component/garmin/transform/skinTemperature.js.map +1 -0
- package/dist/component/garmin/transform/sleeps.d.ts +55 -0
- package/dist/component/garmin/transform/sleeps.d.ts.map +1 -0
- package/dist/component/garmin/transform/sleeps.js +120 -0
- package/dist/component/garmin/transform/sleeps.js.map +1 -0
- package/dist/component/garmin/transform/stress.d.ts +12 -0
- package/dist/component/garmin/transform/stress.d.ts.map +1 -0
- package/dist/component/garmin/transform/stress.js +56 -0
- package/dist/component/garmin/transform/stress.js.map +1 -0
- package/dist/component/garmin/transform/userMetrics.d.ts +12 -0
- package/dist/component/garmin/transform/userMetrics.d.ts.map +1 -0
- package/dist/component/garmin/transform/userMetrics.js +48 -0
- package/dist/component/garmin/transform/userMetrics.js.map +1 -0
- package/dist/component/garmin/types/garmin.d.ts +21 -0
- package/dist/component/garmin/types/garmin.d.ts.map +1 -0
- package/dist/component/garmin/types/garmin.js +6 -0
- package/dist/component/garmin/types/garmin.js.map +1 -0
- package/dist/component/garmin/types/zod/zod.gen.d.ts +1319 -0
- package/dist/component/garmin/types/zod/zod.gen.d.ts.map +1 -0
- package/dist/component/garmin/types/zod/zod.gen.js +784 -0
- package/dist/component/garmin/types/zod/zod.gen.js.map +1 -0
- package/dist/component/garmin/webhooks.d.ts +141 -0
- package/dist/component/garmin/webhooks.d.ts.map +1 -0
- package/dist/component/garmin/webhooks.js +766 -0
- package/dist/component/garmin/webhooks.js.map +1 -0
- package/dist/component/private.d.ts +20 -2
- package/dist/component/private.d.ts.map +1 -1
- package/dist/component/private.js +18 -0
- package/dist/component/private.js.map +1 -1
- package/dist/component/public.d.ts +433 -387
- package/dist/component/public.d.ts.map +1 -1
- package/dist/component/public.js +12 -2
- package/dist/component/public.js.map +1 -1
- package/dist/component/schema.d.ts +217 -162
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +2 -1
- package/dist/component/schema.js.map +1 -1
- package/dist/component/strava/private.d.ts +30 -0
- package/dist/component/strava/private.d.ts.map +1 -0
- package/dist/component/strava/private.js +71 -0
- package/dist/component/strava/private.js.map +1 -0
- package/dist/component/{strava.d.ts → strava/public.d.ts} +3 -31
- package/dist/component/strava/public.d.ts.map +1 -0
- package/dist/component/{strava.js → strava/public.js} +22 -101
- package/dist/component/strava/public.js.map +1 -0
- package/dist/component/validators/activity.d.ts +6 -0
- package/dist/component/validators/activity.d.ts.map +1 -1
- package/dist/component/validators/activity.js.map +1 -1
- package/dist/component/validators/body.d.ts +20 -14
- package/dist/component/validators/body.d.ts.map +1 -1
- package/dist/component/validators/body.js.map +1 -1
- package/dist/component/validators/connection.d.ts +1 -0
- package/dist/component/validators/connection.d.ts.map +1 -1
- package/dist/component/validators/connection.js +2 -0
- package/dist/component/validators/connection.js.map +1 -1
- package/dist/component/validators/daily.d.ts +46 -5
- package/dist/component/validators/daily.d.ts.map +1 -1
- package/dist/component/validators/daily.js +10 -1
- package/dist/component/validators/daily.js.map +1 -1
- package/dist/component/validators/enums.d.ts +2 -2
- package/dist/component/validators/menstruation.d.ts +5 -0
- package/dist/component/validators/menstruation.d.ts.map +1 -1
- package/dist/component/validators/menstruation.js.map +1 -1
- package/dist/component/validators/plannedWorkout.d.ts +5 -1
- package/dist/component/validators/plannedWorkout.d.ts.map +1 -1
- package/dist/component/validators/plannedWorkout.js +4 -0
- package/dist/component/validators/plannedWorkout.js.map +1 -1
- package/dist/component/validators/sleep.d.ts +8 -8
- package/dist/garmin/bloodPressure.d.ts +28 -0
- package/dist/garmin/bloodPressure.d.ts.map +1 -0
- package/dist/garmin/bloodPressure.js +34 -0
- package/dist/garmin/bloodPressure.js.map +1 -0
- package/dist/garmin/body.js +1 -1
- package/dist/garmin/body.js.map +1 -1
- package/dist/garmin/client.d.ts +117 -17
- package/dist/garmin/client.d.ts.map +1 -1
- package/dist/garmin/client.js +337 -43
- package/dist/garmin/client.js.map +1 -1
- package/dist/garmin/daily.d.ts.map +1 -1
- package/dist/garmin/daily.js +3 -3
- package/dist/garmin/daily.js.map +1 -1
- package/dist/garmin/hrv.d.ts +30 -0
- package/dist/garmin/hrv.d.ts.map +1 -0
- package/dist/garmin/hrv.js +45 -0
- package/dist/garmin/hrv.js.map +1 -0
- package/dist/garmin/index.d.ts +16 -4
- package/dist/garmin/index.d.ts.map +1 -1
- package/dist/garmin/index.js +8 -2
- package/dist/garmin/index.js.map +1 -1
- package/dist/garmin/maps/activity-type.d.ts +1 -2
- package/dist/garmin/maps/activity-type.d.ts.map +1 -1
- package/dist/garmin/maps/activity-type.js +1 -0
- package/dist/garmin/maps/activity-type.js.map +1 -1
- package/dist/garmin/menstruation.d.ts +6 -4
- package/dist/garmin/menstruation.d.ts.map +1 -1
- package/dist/garmin/menstruation.js +12 -8
- package/dist/garmin/menstruation.js.map +1 -1
- package/dist/garmin/pulseOx.d.ts +24 -0
- package/dist/garmin/pulseOx.d.ts.map +1 -0
- package/dist/garmin/pulseOx.js +33 -0
- package/dist/garmin/pulseOx.js.map +1 -0
- package/dist/garmin/respiration.d.ts +29 -0
- package/dist/garmin/respiration.d.ts.map +1 -0
- package/dist/garmin/respiration.js +42 -0
- package/dist/garmin/respiration.js.map +1 -0
- package/dist/garmin/skinTemp.d.ts +27 -0
- package/dist/garmin/skinTemp.d.ts.map +1 -0
- package/dist/garmin/skinTemp.js +35 -0
- package/dist/garmin/skinTemp.js.map +1 -0
- package/dist/garmin/sleep.d.ts +4 -4
- package/dist/garmin/sleep.d.ts.map +1 -1
- package/dist/garmin/sleep.js +15 -9
- package/dist/garmin/sleep.js.map +1 -1
- package/dist/garmin/stressDetails.d.ts +30 -0
- package/dist/garmin/stressDetails.d.ts.map +1 -0
- package/dist/garmin/stressDetails.js +49 -0
- package/dist/garmin/stressDetails.js.map +1 -0
- package/dist/garmin/sync.d.ts +14 -0
- package/dist/garmin/sync.d.ts.map +1 -1
- package/dist/garmin/sync.js +290 -7
- package/dist/garmin/sync.js.map +1 -1
- package/dist/garmin/types.d.ts +77 -186
- package/dist/garmin/types.d.ts.map +1 -1
- package/dist/garmin/types.js +4 -2
- package/dist/garmin/types.js.map +1 -1
- package/dist/garmin/userMetrics.d.ts +23 -0
- package/dist/garmin/userMetrics.d.ts.map +1 -0
- package/dist/garmin/userMetrics.js +41 -0
- package/dist/garmin/userMetrics.js.map +1 -0
- package/dist/validators.d.ts +138 -56
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +2 -2
- package/dist/validators.js.map +1 -1
- package/package.json +130 -124
- package/src/client/index.ts +86 -18
- package/src/component/_generated/api.ts +96 -4
- package/src/component/_generated/component.ts +271 -144
- package/src/{garmin → component/garmin}/auth.ts +8 -1
- package/src/component/garmin/client.ts +39 -0
- package/src/component/garmin/private.ts +1798 -0
- package/src/component/garmin/public.ts +938 -0
- package/src/component/garmin/schemas/activity.ts +40 -0
- package/src/component/garmin/schemas/activityDetails.ts +45 -0
- package/src/component/garmin/schemas/bloodPressure.ts +38 -0
- package/src/component/garmin/schemas/bodyCompositions.ts +38 -0
- package/src/component/garmin/schemas/dailies.ts +38 -0
- package/src/component/garmin/schemas/epochs.ts +38 -0
- package/src/component/garmin/schemas/healthSnapshot.ts +38 -0
- package/src/component/garmin/schemas/hrvSummary.ts +38 -0
- package/src/component/garmin/schemas/manuallyUpdatedActivities.ts +49 -0
- package/src/component/garmin/schemas/menstrualCycleTracking.ts +39 -0
- package/src/component/garmin/schemas/moveIQ.ts +38 -0
- package/src/component/garmin/schemas/pulseOx.ts +39 -0
- package/src/component/garmin/schemas/respiration.ts +39 -0
- package/src/component/garmin/schemas/skinTemperature.ts +39 -0
- package/src/component/garmin/schemas/sleeps.ts +38 -0
- package/src/component/garmin/schemas/stress.ts +43 -0
- package/src/component/garmin/schemas/userMetrics.ts +39 -0
- package/src/component/garmin/transform/activity.ts +143 -0
- package/src/component/garmin/transform/activityDetails.ts +236 -0
- package/src/component/garmin/transform/bloodPressure.ts +39 -0
- package/src/component/garmin/transform/bodyCompositions.ts +51 -0
- package/src/component/garmin/transform/dailies.ts +179 -0
- package/src/component/garmin/transform/epochs.ts +94 -0
- package/src/component/garmin/transform/healthSnapshot.ts +152 -0
- package/src/component/garmin/transform/hrvSummary.ts +56 -0
- package/src/component/garmin/transform/manuallyUpdatedActivities.ts +27 -0
- package/src/{garmin/maps/activity-type.ts → component/garmin/transform/maps/activityType.ts} +116 -116
- package/src/{garmin/maps/sleep-level.ts → component/garmin/transform/maps/sleepLevel.ts} +22 -22
- package/src/component/garmin/transform/menstrualCycleTracking.ts +48 -0
- package/src/component/garmin/transform/moveIQ.ts +48 -0
- package/src/{garmin → component/garmin/transform}/plannedWorkout.ts +328 -333
- package/src/component/garmin/transform/pulseOx.ts +64 -0
- package/src/component/garmin/transform/respiration.ts +73 -0
- package/src/component/garmin/transform/skinTemperature.ts +44 -0
- package/src/component/garmin/transform/sleeps.ts +159 -0
- package/src/component/garmin/transform/stress.ts +78 -0
- package/src/component/garmin/transform/userMetrics.ts +56 -0
- package/src/component/garmin/types/specs/training-api-workouts.json +699 -0
- package/src/component/garmin/types/specs/wellness-api.json +1 -0
- package/src/component/garmin/types/trainingApiWorkouts/client/client.gen.ts +290 -0
- package/src/component/garmin/types/trainingApiWorkouts/client/index.ts +25 -0
- package/src/component/garmin/types/trainingApiWorkouts/client/types.gen.ts +214 -0
- package/src/component/garmin/types/trainingApiWorkouts/client/utils.gen.ts +316 -0
- package/src/component/garmin/types/trainingApiWorkouts/client.gen.ts +16 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/auth.gen.ts +41 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/bodySerializer.gen.ts +82 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/params.gen.ts +169 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/pathSerializer.gen.ts +171 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/queryKeySerializer.gen.ts +117 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/serverSentEvents.gen.ts +243 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/types.gen.ts +104 -0
- package/src/component/garmin/types/trainingApiWorkouts/core/utils.gen.ts +140 -0
- package/src/component/garmin/types/trainingApiWorkouts/index.ts +4 -0
- package/src/component/garmin/types/trainingApiWorkouts/sdk.gen.ts +126 -0
- package/src/component/garmin/types/trainingApiWorkouts/types.gen.ts +387 -0
- package/src/component/garmin/types/trainingApiWorkouts/zod.gen.ts +423 -0
- package/src/component/garmin/types/wellnessApi/client/client.gen.ts +290 -0
- package/src/component/garmin/types/wellnessApi/client/index.ts +25 -0
- package/src/component/garmin/types/wellnessApi/client/types.gen.ts +214 -0
- package/src/component/garmin/types/wellnessApi/client/utils.gen.ts +316 -0
- package/src/component/garmin/types/wellnessApi/client.gen.ts +16 -0
- package/src/component/garmin/types/wellnessApi/core/auth.gen.ts +41 -0
- package/src/component/garmin/types/wellnessApi/core/bodySerializer.gen.ts +82 -0
- package/src/component/garmin/types/wellnessApi/core/params.gen.ts +169 -0
- package/src/component/garmin/types/wellnessApi/core/pathSerializer.gen.ts +171 -0
- package/src/component/garmin/types/wellnessApi/core/queryKeySerializer.gen.ts +117 -0
- package/src/component/garmin/types/wellnessApi/core/serverSentEvents.gen.ts +243 -0
- package/src/component/garmin/types/wellnessApi/core/types.gen.ts +104 -0
- package/src/component/garmin/types/wellnessApi/core/utils.gen.ts +140 -0
- package/src/component/garmin/types/wellnessApi/index.ts +4 -0
- package/src/component/garmin/types/wellnessApi/sdk.gen.ts +207 -0
- package/src/component/garmin/types/wellnessApi/types.gen.ts +2942 -0
- package/src/component/garmin/types/wellnessApi/zod.gen.ts +878 -0
- package/src/component/garmin/utils.ts +25 -0
- package/src/component/garmin/webhooks.ts +852 -0
- package/src/component/private.ts +21 -0
- package/src/component/public.ts +11 -2
- package/src/component/schema.ts +2 -1
- package/src/component/strava/private.ts +89 -0
- package/src/component/{strava.ts → strava/public.ts} +341 -404
- package/src/component/validators/activity.ts +5 -0
- package/src/component/validators/body.ts +5 -0
- package/src/component/validators/connection.ts +2 -0
- package/src/component/validators/daily.ts +20 -0
- package/src/component/validators/menstruation.ts +5 -1
- package/src/component/validators/plannedWorkout.ts +9 -0
- package/src/validators.ts +12 -2
- package/dist/component/garmin.d.ts.map +0 -1
- package/dist/component/garmin.js.map +0 -1
- package/dist/component/strava.d.ts.map +0 -1
- package/dist/component/strava.js.map +0 -1
- package/dist/garmin/activity.d.ts +0 -101
- package/dist/garmin/activity.d.ts.map +0 -1
- package/dist/garmin/activity.js +0 -207
- package/dist/garmin/activity.js.map +0 -1
- package/src/component/garmin.ts +0 -850
- package/src/garmin/activity.test.ts +0 -178
- package/src/garmin/activity.ts +0 -272
- package/src/garmin/auth.test.ts +0 -103
- package/src/garmin/body.ts +0 -59
- package/src/garmin/client.ts +0 -407
- package/src/garmin/daily.ts +0 -211
- package/src/garmin/index.ts +0 -75
- package/src/garmin/maps/activity-type.test.ts +0 -78
- package/src/garmin/menstruation.ts +0 -42
- package/src/garmin/sleep.test.ts +0 -110
- package/src/garmin/sleep.ts +0 -170
- package/src/garmin/sync.ts +0 -223
- package/src/garmin/types.ts +0 -480
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
// ─── Garmin Webhook Handlers (Push Mode) ─────────────────────────────────────
|
|
2
|
+
// Each handler receives full Garmin data objects from push-mode webhooks.
|
|
3
|
+
// Separate actions per data type because the Garmin developer portal
|
|
4
|
+
// configures separate URLs per type.
|
|
5
|
+
import { v } from "convex/values";
|
|
6
|
+
import { action } from "../_generated/server";
|
|
7
|
+
import { GarminClient } from "../../garmin/client";
|
|
8
|
+
import { refreshToken } from "../../garmin/auth";
|
|
9
|
+
import { transformMenstruation } from "../../garmin/menstruation";
|
|
10
|
+
import { garminSkinTemperaturePingPayloadSchema, garminSkinTemperaturePushPayloadSchema, } from "./schemas/skinTemperature.js";
|
|
11
|
+
import { garminSleepsPingPayloadSchema, garminSleepsPushPayloadSchema, } from "./schemas/sleeps.js";
|
|
12
|
+
import { transformHRV } from "../../garmin/hrv";
|
|
13
|
+
import { transformStressDetails } from "../../garmin/stressDetails";
|
|
14
|
+
import { garminStressPingPayloadSchema, garminStressPushPayloadSchema, } from "./schemas/stress.js";
|
|
15
|
+
import { api, internal } from "../_generated/api";
|
|
16
|
+
import { garminActivityPingPayloadSchema, garminActivityPushPayloadSchema, } from "./schemas/activity.js";
|
|
17
|
+
import { garminActivityDetailsPingPayloadSchema, garminActivityDetailsPushPayloadSchema, } from "./schemas/activityDetails.js";
|
|
18
|
+
import { garminManuallyUpdatedActivitiesPingPayloadSchema, garminManuallyUpdatedActivitiesPushPayloadSchema, } from "./schemas/manuallyUpdatedActivities.js";
|
|
19
|
+
import { garminMoveIQPingPayloadSchema, garminMoveIQPushPayloadSchema, } from "./schemas/moveIQ.js";
|
|
20
|
+
import { garminBloodPressurePingPayloadSchema, garminBloodPressurePushPayloadSchema, } from "./schemas/bloodPressure.js";
|
|
21
|
+
import { garminBodyCompositionsPingPayloadSchema, garminBodyCompositionsPushPayloadSchema, } from "./schemas/bodyCompositions.js";
|
|
22
|
+
import { garminDailiesPingPayloadSchema, garminDailiesPushPayloadSchema, } from "./schemas/dailies.js";
|
|
23
|
+
import { garminEpochPingPayloadSchema, garminEpochPushPayloadSchema, } from "./schemas/epochs.js";
|
|
24
|
+
import { garminHealthSnapshotPingPayloadSchema, garminHealthSnapshotPushPayloadSchema, } from "./schemas/healthSnapshot.js";
|
|
25
|
+
import { garminHRVSummaryPingPayloadSchema, garminHRVSummaryPushPayloadSchema, } from "./schemas/hrvSummary.js";
|
|
26
|
+
import { garminPulseOxPingPayloadSchema, garminPulseOxPushPayloadSchema, } from "./schemas/pulseOx.js";
|
|
27
|
+
import { garminRespirationPingPayloadSchema, garminRespirationPushPayloadSchema, } from "./schemas/respiration.js";
|
|
28
|
+
import { garminUserMetricsPingPayloadSchema, garminUserMetricsPushPayloadSchema, } from "./schemas/userMetrics.js";
|
|
29
|
+
import { garminMenstrualCycleTrackingPingPayloadSchema, garminMenstrualCycleTrackingPushPayloadSchema, } from "./schemas/menstrualCycleTracking.js";
|
|
30
|
+
// Refresh buffer: refresh tokens 10 minutes before expiry
|
|
31
|
+
const REFRESH_BUFFER_SECONDS = 600;
|
|
32
|
+
/**
|
|
33
|
+
* Discriminate push vs ping webhook payloads.
|
|
34
|
+
*
|
|
35
|
+
* Per the Garmin Activity API docs:
|
|
36
|
+
* - Ping items always carry `callbackURL` (absent only for deregistrations)
|
|
37
|
+
* - Push items never carry `callbackURL`, they contain the full summary data
|
|
38
|
+
*
|
|
39
|
+
* Zod schema validation alone can't distinguish the two because both schemas
|
|
40
|
+
* have all-optional fields and would accept either payload shape.
|
|
41
|
+
*/
|
|
42
|
+
function isWebhookPushMode(payload) {
|
|
43
|
+
if (payload == null || typeof payload !== "object")
|
|
44
|
+
return false;
|
|
45
|
+
// The payload is either a keyed object like { activities: [...] }
|
|
46
|
+
// or a flat array of items. Extract the first item either way.
|
|
47
|
+
let firstItem;
|
|
48
|
+
if (Array.isArray(payload)) {
|
|
49
|
+
firstItem = payload[0];
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const key = Object.keys(payload)[0];
|
|
53
|
+
const list = payload[key];
|
|
54
|
+
firstItem = Array.isArray(list) ? list[0] : undefined;
|
|
55
|
+
}
|
|
56
|
+
if (firstItem == null || typeof firstItem !== "object")
|
|
57
|
+
return false;
|
|
58
|
+
return !("callbackURL" in firstItem);
|
|
59
|
+
}
|
|
60
|
+
// ─── Webhook Handlers ────────────────────────────────────────────────────────
|
|
61
|
+
/**
|
|
62
|
+
* Handle a webhook for Garmin activities (push or ping mode).
|
|
63
|
+
*/
|
|
64
|
+
export const handleGarminWebhookActivities = action({
|
|
65
|
+
args: { payload: v.any() },
|
|
66
|
+
handler: async (ctx, args) => {
|
|
67
|
+
if (isWebhookPushMode(args.payload)) {
|
|
68
|
+
const pushResult = garminActivityPushPayloadSchema.safeParse(args.payload);
|
|
69
|
+
if (pushResult.success && pushResult.data.activities.length > 0) {
|
|
70
|
+
return await ctx.runAction(internal.garmin.private.processActivityPushPayload, { payload: args.payload });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const pingResult = garminActivityPingPayloadSchema.safeParse(args.payload);
|
|
75
|
+
if (pingResult.success && pingResult.data.activities.length > 0) {
|
|
76
|
+
return await ctx.runAction(internal.garmin.private.processActivityPingPayload, { payload: args.payload });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
console.warn(`[garmin:webhook:activities] Payload matched neither ping nor push schema`);
|
|
80
|
+
return { processed: 0, errors: [] };
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
/**
|
|
84
|
+
* Handle a webhook for Garmin activity details (push or ping mode).
|
|
85
|
+
*/
|
|
86
|
+
export const handleGarminWebhookActivityDetails = action({
|
|
87
|
+
args: { payload: v.any() },
|
|
88
|
+
handler: async (ctx, args) => {
|
|
89
|
+
if (isWebhookPushMode(args.payload)) {
|
|
90
|
+
const pushResult = garminActivityDetailsPushPayloadSchema.safeParse(args.payload);
|
|
91
|
+
if (pushResult.success &&
|
|
92
|
+
pushResult.data.activityDetails.length > 0) {
|
|
93
|
+
return await ctx.runAction(internal.garmin.private.processActivityDetailsPushPayload, { payload: args.payload });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const pingResult = garminActivityDetailsPingPayloadSchema.safeParse(args.payload);
|
|
98
|
+
if (pingResult.success &&
|
|
99
|
+
pingResult.data.activityDetails.length > 0) {
|
|
100
|
+
return await ctx.runAction(internal.garmin.private.processActivityDetailsPingPayload, { payload: args.payload });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
console.warn(`[garmin:webhook:activityDetails] Payload matched neither ping nor push schema`);
|
|
104
|
+
return { processed: 0, errors: [] };
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
/**
|
|
108
|
+
* Handle a webhook for Garmin manually updated activities (push or ping mode).
|
|
109
|
+
*/
|
|
110
|
+
export const handleGarminWebhookManuallyUpdatedActivities = action({
|
|
111
|
+
args: { payload: v.any() },
|
|
112
|
+
handler: async (ctx, args) => {
|
|
113
|
+
if (isWebhookPushMode(args.payload)) {
|
|
114
|
+
const pushResult = garminManuallyUpdatedActivitiesPushPayloadSchema.safeParse(args.payload);
|
|
115
|
+
if (pushResult.success &&
|
|
116
|
+
pushResult.data.manuallyUpdatedActivities.length > 0) {
|
|
117
|
+
return await ctx.runAction(internal.garmin.private
|
|
118
|
+
.processManuallyUpdatedActivitiesPushPayload, { payload: args.payload });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const pingResult = garminManuallyUpdatedActivitiesPingPayloadSchema.safeParse(args.payload);
|
|
123
|
+
if (pingResult.success &&
|
|
124
|
+
pingResult.data.manuallyUpdatedActivities.length > 0) {
|
|
125
|
+
return await ctx.runAction(internal.garmin.private
|
|
126
|
+
.processManuallyUpdatedActivitiesPingPayload, { payload: args.payload });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
console.warn(`[garmin:webhook:manuallyUpdatedActivities] Payload matched neither ping nor push schema`);
|
|
130
|
+
return { processed: 0, errors: [] };
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
/**
|
|
134
|
+
* Handle a webhook for Garmin Move IQ auto-detected activities (push or ping mode).
|
|
135
|
+
*/
|
|
136
|
+
export const handleGarminWebhookMoveIQ = action({
|
|
137
|
+
args: { payload: v.any() },
|
|
138
|
+
handler: async (ctx, args) => {
|
|
139
|
+
if (isWebhookPushMode(args.payload)) {
|
|
140
|
+
const pushResult = garminMoveIQPushPayloadSchema.safeParse(args.payload);
|
|
141
|
+
if (pushResult.success &&
|
|
142
|
+
pushResult.data.moveIQActivities.length > 0) {
|
|
143
|
+
return await ctx.runAction(internal.garmin.private.processMoveIQPushPayload, { payload: args.payload });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const pingResult = garminMoveIQPingPayloadSchema.safeParse(args.payload);
|
|
148
|
+
if (pingResult.success &&
|
|
149
|
+
pingResult.data.moveIQActivities.length > 0) {
|
|
150
|
+
return await ctx.runAction(internal.garmin.private.processMoveIQPingPayload, { payload: args.payload });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
console.warn(`[garmin:webhook:moveIQ] Payload matched neither ping nor push schema`);
|
|
154
|
+
return { processed: 0, errors: [] };
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
/**
|
|
158
|
+
* Handle a webhook for Garmin blood pressure summaries (push or ping mode).
|
|
159
|
+
*/
|
|
160
|
+
export const handleGarminWebhookBloodPressures = action({
|
|
161
|
+
args: { payload: v.any() },
|
|
162
|
+
handler: async (ctx, args) => {
|
|
163
|
+
if (isWebhookPushMode(args.payload)) {
|
|
164
|
+
const pushResult = garminBloodPressurePushPayloadSchema.safeParse(args.payload);
|
|
165
|
+
if (pushResult.success &&
|
|
166
|
+
pushResult.data.bloodPressures.length > 0) {
|
|
167
|
+
return await ctx.runAction(internal.garmin.private.processBloodPressurePushPayload, { payload: args.payload });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
const pingResult = garminBloodPressurePingPayloadSchema.safeParse(args.payload);
|
|
172
|
+
if (pingResult.success &&
|
|
173
|
+
pingResult.data.bloodPressures.length > 0) {
|
|
174
|
+
return await ctx.runAction(internal.garmin.private.processBloodPressurePingPayload, { payload: args.payload });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
console.warn(`[garmin:webhook:bloodPressures] Payload matched neither ping nor push schema`);
|
|
178
|
+
return { processed: 0, errors: [] };
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
/**
|
|
182
|
+
* Handle a webhook for Garmin body composition summaries (push or ping mode).
|
|
183
|
+
*/
|
|
184
|
+
export const handleGarminWebhookBodyCompositions = action({
|
|
185
|
+
args: { payload: v.any() },
|
|
186
|
+
handler: async (ctx, args) => {
|
|
187
|
+
if (isWebhookPushMode(args.payload)) {
|
|
188
|
+
const pushResult = garminBodyCompositionsPushPayloadSchema.safeParse(args.payload);
|
|
189
|
+
if (pushResult.success &&
|
|
190
|
+
pushResult.data.bodyComps.length > 0) {
|
|
191
|
+
return await ctx.runAction(internal.garmin.private.processBodyCompositionsPushPayload, { payload: args.payload });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const pingResult = garminBodyCompositionsPingPayloadSchema.safeParse(args.payload);
|
|
196
|
+
if (pingResult.success &&
|
|
197
|
+
pingResult.data.bodyComps.length > 0) {
|
|
198
|
+
return await ctx.runAction(internal.garmin.private.processBodyCompositionsPingPayload, { payload: args.payload });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
console.warn(`[garmin:webhook:bodyCompositions] Payload matched neither ping nor push schema`);
|
|
202
|
+
return { processed: 0, errors: [] };
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
/**
|
|
206
|
+
* Handle a webhook for Garmin daily summaries (push or ping mode).
|
|
207
|
+
*/
|
|
208
|
+
export const handleGarminWebhookDailies = action({
|
|
209
|
+
args: { payload: v.any() },
|
|
210
|
+
handler: async (ctx, args) => {
|
|
211
|
+
if (isWebhookPushMode(args.payload)) {
|
|
212
|
+
const pushResult = garminDailiesPushPayloadSchema.safeParse(args.payload);
|
|
213
|
+
if (pushResult.success &&
|
|
214
|
+
pushResult.data.dailies.length > 0) {
|
|
215
|
+
return await ctx.runAction(internal.garmin.private.processDailiesPushPayload, { payload: args.payload });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const pingResult = garminDailiesPingPayloadSchema.safeParse(args.payload);
|
|
220
|
+
if (pingResult.success &&
|
|
221
|
+
pingResult.data.dailies.length > 0) {
|
|
222
|
+
return await ctx.runAction(internal.garmin.private.processDailiesPingPayload, { payload: args.payload });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
console.warn(`[garmin:webhook:dailies] Payload matched neither ping nor push schema`);
|
|
226
|
+
return { processed: 0, errors: [] };
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
/**
|
|
230
|
+
* Handle a webhook for Garmin epoch summaries (push or ping mode).
|
|
231
|
+
*/
|
|
232
|
+
export const handleGarminWebhookEpochs = action({
|
|
233
|
+
args: { payload: v.any() },
|
|
234
|
+
handler: async (ctx, args) => {
|
|
235
|
+
if (isWebhookPushMode(args.payload)) {
|
|
236
|
+
const pushResult = garminEpochPushPayloadSchema.safeParse(args.payload);
|
|
237
|
+
if (pushResult.success &&
|
|
238
|
+
pushResult.data.epochs.length > 0) {
|
|
239
|
+
return await ctx.runAction(internal.garmin.private.processEpochPushPayload, { payload: args.payload });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
const pingResult = garminEpochPingPayloadSchema.safeParse(args.payload);
|
|
244
|
+
if (pingResult.success &&
|
|
245
|
+
pingResult.data.epochs.length > 0) {
|
|
246
|
+
return await ctx.runAction(internal.garmin.private.processEpochPingPayload, { payload: args.payload });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
console.warn(`[garmin:webhook:epochs] Payload matched neither ping nor push schema`);
|
|
250
|
+
return { processed: 0, errors: [] };
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
/**
|
|
254
|
+
* Handle a webhook for Garmin health snapshot summaries (push or ping mode).
|
|
255
|
+
*/
|
|
256
|
+
export const handleGarminWebhookHealthSnapshot = action({
|
|
257
|
+
args: { payload: v.any() },
|
|
258
|
+
handler: async (ctx, args) => {
|
|
259
|
+
if (isWebhookPushMode(args.payload)) {
|
|
260
|
+
const pushResult = garminHealthSnapshotPushPayloadSchema.safeParse(args.payload);
|
|
261
|
+
if (pushResult.success &&
|
|
262
|
+
pushResult.data.healthSnapshot.length > 0) {
|
|
263
|
+
return await ctx.runAction(internal.garmin.private.processHealthSnapshotPushPayload, { payload: args.payload });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
const pingResult = garminHealthSnapshotPingPayloadSchema.safeParse(args.payload);
|
|
268
|
+
if (pingResult.success &&
|
|
269
|
+
pingResult.data.healthSnapshot.length > 0) {
|
|
270
|
+
return await ctx.runAction(internal.garmin.private.processHealthSnapshotPingPayload, { payload: args.payload });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
console.warn(`[garmin:webhook:healthSnapshot] Payload matched neither ping nor push schema`);
|
|
274
|
+
return { processed: 0, errors: [] };
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
/**
|
|
278
|
+
* Handle a webhook for Garmin sleep summaries (push or ping mode).
|
|
279
|
+
* Follows the structured dispatch pattern: validates with Zod, then delegates
|
|
280
|
+
* to internal actions for processing.
|
|
281
|
+
*/
|
|
282
|
+
export const handleGarminWebhookSleeps = action({
|
|
283
|
+
args: { payload: v.any() },
|
|
284
|
+
handler: async (ctx, args) => {
|
|
285
|
+
if (isWebhookPushMode(args.payload)) {
|
|
286
|
+
const pushResult = garminSleepsPushPayloadSchema.safeParse(args.payload);
|
|
287
|
+
if (pushResult.success &&
|
|
288
|
+
pushResult.data.sleeps.length > 0) {
|
|
289
|
+
return await ctx.runAction(internal.garmin.private.processSleepsPushPayload, { payload: args.payload });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
const pingResult = garminSleepsPingPayloadSchema.safeParse(args.payload);
|
|
294
|
+
if (pingResult.success &&
|
|
295
|
+
pingResult.data.sleeps.length > 0) {
|
|
296
|
+
return await ctx.runAction(internal.garmin.private.processSleepsPingPayload, { payload: args.payload });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
console.warn(`[garmin:webhook:sleeps] Payload matched neither ping nor push schema`);
|
|
300
|
+
return { processed: 0, errors: [] };
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
/**
|
|
304
|
+
* Handle a webhook for Garmin menstrual cycle tracking (push or ping mode).
|
|
305
|
+
*/
|
|
306
|
+
export const handleGarminWebhookMenstruation = action({
|
|
307
|
+
args: { payload: v.any() },
|
|
308
|
+
handler: async (ctx, args) => {
|
|
309
|
+
return await processWebhookDualMode(ctx, {
|
|
310
|
+
items: args.payload,
|
|
311
|
+
type: "menstruation",
|
|
312
|
+
transform: (item) => transformMenstruation(item),
|
|
313
|
+
ingest: api.public.ingestMenstruation,
|
|
314
|
+
getId: (item) => item.summaryId ??
|
|
315
|
+
item.calendarDate ??
|
|
316
|
+
"unknown",
|
|
317
|
+
fetchData: (client, timeRange) => client.getMenstrualCycleData(timeRange),
|
|
318
|
+
});
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
/**
|
|
322
|
+
* Handle a webhook for Garmin skin temperature summaries (push or ping mode).
|
|
323
|
+
* Follows the structured dispatch pattern: validates with Zod, then delegates
|
|
324
|
+
* to internal actions for processing.
|
|
325
|
+
*/
|
|
326
|
+
export const handleGarminWebhookSkinTemp = action({
|
|
327
|
+
args: { payload: v.any() },
|
|
328
|
+
handler: async (ctx, args) => {
|
|
329
|
+
if (isWebhookPushMode(args.payload)) {
|
|
330
|
+
const pushResult = garminSkinTemperaturePushPayloadSchema.safeParse(args.payload);
|
|
331
|
+
if (pushResult.success &&
|
|
332
|
+
pushResult.data.skinTemp.length > 0) {
|
|
333
|
+
return await ctx.runAction(internal.garmin.private.processSkinTemperaturePushPayload, { payload: args.payload });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
const pingResult = garminSkinTemperaturePingPayloadSchema.safeParse(args.payload);
|
|
338
|
+
if (pingResult.success &&
|
|
339
|
+
pingResult.data.skinTemp.length > 0) {
|
|
340
|
+
return await ctx.runAction(internal.garmin.private.processSkinTemperaturePingPayload, { payload: args.payload });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
console.warn(`[garmin:webhook:skinTemperature] Payload matched neither ping nor push schema`);
|
|
344
|
+
return { processed: 0, errors: [] };
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
/**
|
|
348
|
+
* Handle a webhook for Garmin user metrics (push or ping mode).
|
|
349
|
+
* Follows the structured dispatch pattern: validates with Zod, then delegates
|
|
350
|
+
* to internal actions for processing.
|
|
351
|
+
*/
|
|
352
|
+
export const handleGarminWebhookUserMetrics = action({
|
|
353
|
+
args: { payload: v.any() },
|
|
354
|
+
handler: async (ctx, args) => {
|
|
355
|
+
if (isWebhookPushMode(args.payload)) {
|
|
356
|
+
const pushResult = garminUserMetricsPushPayloadSchema.safeParse(args.payload);
|
|
357
|
+
if (pushResult.success &&
|
|
358
|
+
pushResult.data.userMetrics.length > 0) {
|
|
359
|
+
return await ctx.runAction(internal.garmin.private.processUserMetricsPushPayload, { payload: args.payload });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
const pingResult = garminUserMetricsPingPayloadSchema.safeParse(args.payload);
|
|
364
|
+
if (pingResult.success &&
|
|
365
|
+
pingResult.data.userMetrics.length > 0) {
|
|
366
|
+
return await ctx.runAction(internal.garmin.private.processUserMetricsPingPayload, { payload: args.payload });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
console.warn(`[garmin:webhook:userMetrics] Payload matched neither ping nor push schema`);
|
|
370
|
+
return { processed: 0, errors: [] };
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
/**
|
|
374
|
+
* Handle a webhook for Garmin menstrual cycle tracking (push or ping mode).
|
|
375
|
+
* Follows the structured dispatch pattern: validates with Zod, then delegates
|
|
376
|
+
* to internal actions for processing.
|
|
377
|
+
*/
|
|
378
|
+
export const handleGarminWebhookMenstrualCycleTracking = action({
|
|
379
|
+
args: { payload: v.any() },
|
|
380
|
+
handler: async (ctx, args) => {
|
|
381
|
+
if (isWebhookPushMode(args.payload)) {
|
|
382
|
+
const pushResult = garminMenstrualCycleTrackingPushPayloadSchema.safeParse(args.payload);
|
|
383
|
+
if (pushResult.success &&
|
|
384
|
+
pushResult.data.mct.length > 0) {
|
|
385
|
+
return await ctx.runAction(internal.garmin.private.processMenstrualCycleTrackingPushPayload, { payload: args.payload });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
const pingResult = garminMenstrualCycleTrackingPingPayloadSchema.safeParse(args.payload);
|
|
390
|
+
if (pingResult.success &&
|
|
391
|
+
pingResult.data.mct.length > 0) {
|
|
392
|
+
return await ctx.runAction(internal.garmin.private.processMenstrualCycleTrackingPingPayload, { payload: args.payload });
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
console.warn(`[garmin:webhook:menstrualCycleTracking] Payload matched neither ping nor push schema`);
|
|
396
|
+
return { processed: 0, errors: [] };
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
/**
|
|
400
|
+
* Handle a webhook for Garmin HRV summaries (push or ping mode).
|
|
401
|
+
* Enriches daily records with heart_rate_data.
|
|
402
|
+
*/
|
|
403
|
+
export const handleGarminWebhookHRV = action({
|
|
404
|
+
args: { payload: v.any() },
|
|
405
|
+
handler: async (ctx, args) => {
|
|
406
|
+
return await processWebhookDualMode(ctx, {
|
|
407
|
+
items: args.payload,
|
|
408
|
+
type: "hrv",
|
|
409
|
+
transform: (item) => {
|
|
410
|
+
const record = item;
|
|
411
|
+
const data = transformHRV(item);
|
|
412
|
+
if (!data.heart_rate_data)
|
|
413
|
+
return null;
|
|
414
|
+
return {
|
|
415
|
+
metadata: {
|
|
416
|
+
start_time: new Date((record.startTimeInSeconds ?? 0) * 1000).toISOString(),
|
|
417
|
+
end_time: new Date(((record.startTimeInSeconds ?? 0) + (record.durationInSeconds ?? 86400)) * 1000).toISOString(),
|
|
418
|
+
upload_type: 1,
|
|
419
|
+
},
|
|
420
|
+
heart_rate_data: data.heart_rate_data,
|
|
421
|
+
};
|
|
422
|
+
},
|
|
423
|
+
ingest: api.public.ingestDaily,
|
|
424
|
+
getId: (item) => item.summaryId ??
|
|
425
|
+
item.calendarDate ??
|
|
426
|
+
"unknown",
|
|
427
|
+
fetchData: (client, timeRange) => client.getHRV(timeRange),
|
|
428
|
+
});
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
/**
|
|
432
|
+
* Handle a webhook for Garmin HRV summaries (push or ping mode).
|
|
433
|
+
* Follows the structured dispatch pattern: validates with Zod, then delegates
|
|
434
|
+
* to internal actions for processing.
|
|
435
|
+
*/
|
|
436
|
+
export const handleGarminWebhookHRVSummary = action({
|
|
437
|
+
args: { payload: v.any() },
|
|
438
|
+
handler: async (ctx, args) => {
|
|
439
|
+
if (isWebhookPushMode(args.payload)) {
|
|
440
|
+
const pushResult = garminHRVSummaryPushPayloadSchema.safeParse(args.payload);
|
|
441
|
+
if (pushResult.success &&
|
|
442
|
+
pushResult.data.hrv.length > 0) {
|
|
443
|
+
return await ctx.runAction(internal.garmin.private.processHRVSummaryPushPayload, { payload: args.payload });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
const pingResult = garminHRVSummaryPingPayloadSchema.safeParse(args.payload);
|
|
448
|
+
if (pingResult.success &&
|
|
449
|
+
pingResult.data.hrv.length > 0) {
|
|
450
|
+
return await ctx.runAction(internal.garmin.private.processHRVSummaryPingPayload, { payload: args.payload });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
console.warn(`[garmin:webhook:hrvSummary] Payload matched neither ping nor push schema`);
|
|
454
|
+
return { processed: 0, errors: [] };
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
/**
|
|
458
|
+
* Handle a webhook for Garmin stress detail summaries (push or ping mode).
|
|
459
|
+
* Follows the structured dispatch pattern: validates with Zod, then delegates
|
|
460
|
+
* to internal actions for processing.
|
|
461
|
+
*/
|
|
462
|
+
export const handleGarminWebhookStress = action({
|
|
463
|
+
args: { payload: v.any() },
|
|
464
|
+
handler: async (ctx, args) => {
|
|
465
|
+
if (isWebhookPushMode(args.payload)) {
|
|
466
|
+
const pushResult = garminStressPushPayloadSchema.safeParse(args.payload);
|
|
467
|
+
if (pushResult.success &&
|
|
468
|
+
pushResult.data.stressDetails.length > 0) {
|
|
469
|
+
return await ctx.runAction(internal.garmin.private.processStressPushPayload, { payload: args.payload });
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
const pingResult = garminStressPingPayloadSchema.safeParse(args.payload);
|
|
474
|
+
if (pingResult.success &&
|
|
475
|
+
pingResult.data.stressDetails.length > 0) {
|
|
476
|
+
return await ctx.runAction(internal.garmin.private.processStressPingPayload, { payload: args.payload });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
console.warn(`[garmin:webhook:stressDetails] Payload matched neither ping nor push schema`);
|
|
480
|
+
return { processed: 0, errors: [] };
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
/**
|
|
484
|
+
* Handle a webhook for Garmin pulse oximetry (SpO2) summaries (push or ping mode).
|
|
485
|
+
* Follows the structured dispatch pattern: validates with Zod, then delegates
|
|
486
|
+
* to internal actions for processing.
|
|
487
|
+
*/
|
|
488
|
+
export const handleGarminWebhookPulseOx = action({
|
|
489
|
+
args: { payload: v.any() },
|
|
490
|
+
handler: async (ctx, args) => {
|
|
491
|
+
if (isWebhookPushMode(args.payload)) {
|
|
492
|
+
const pushResult = garminPulseOxPushPayloadSchema.safeParse(args.payload);
|
|
493
|
+
if (pushResult.success &&
|
|
494
|
+
pushResult.data.pulseox.length > 0) {
|
|
495
|
+
return await ctx.runAction(internal.garmin.private.processPulseOxPushPayload, { payload: args.payload });
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
const pingResult = garminPulseOxPingPayloadSchema.safeParse(args.payload);
|
|
500
|
+
if (pingResult.success &&
|
|
501
|
+
pingResult.data.pulseox.length > 0) {
|
|
502
|
+
return await ctx.runAction(internal.garmin.private.processPulseOxPingPayload, { payload: args.payload });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
console.warn(`[garmin:webhook:pulseOx] Payload matched neither ping nor push schema`);
|
|
506
|
+
return { processed: 0, errors: [] };
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
/**
|
|
510
|
+
* Handle a webhook for Garmin respiration summaries (push or ping mode).
|
|
511
|
+
* Follows the structured dispatch pattern: validates with Zod, then delegates
|
|
512
|
+
* to internal actions for processing.
|
|
513
|
+
*/
|
|
514
|
+
export const handleGarminWebhookRespiration = action({
|
|
515
|
+
args: { payload: v.any() },
|
|
516
|
+
handler: async (ctx, args) => {
|
|
517
|
+
if (isWebhookPushMode(args.payload)) {
|
|
518
|
+
const pushResult = garminRespirationPushPayloadSchema.safeParse(args.payload);
|
|
519
|
+
if (pushResult.success &&
|
|
520
|
+
pushResult.data.allDayRespiration.length > 0) {
|
|
521
|
+
return await ctx.runAction(internal.garmin.private.processRespirationPushPayload, { payload: args.payload });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
const pingResult = garminRespirationPingPayloadSchema.safeParse(args.payload);
|
|
526
|
+
if (pingResult.success &&
|
|
527
|
+
pingResult.data.allDayRespiration.length > 0) {
|
|
528
|
+
return await ctx.runAction(internal.garmin.private.processRespirationPingPayload, { payload: args.payload });
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
console.warn(`[garmin:webhook:respiration] Payload matched neither ping nor push schema`);
|
|
532
|
+
return { processed: 0, errors: [] };
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
async function processWebhookPayload(ctx, config) {
|
|
536
|
+
const { items, type, transform, ingest, getId } = config;
|
|
537
|
+
let processed = 0;
|
|
538
|
+
const errors = [];
|
|
539
|
+
if (!Array.isArray(items)) {
|
|
540
|
+
errors.push({ type, id: "payload", error: "Expected an array payload" });
|
|
541
|
+
return { processed, errors };
|
|
542
|
+
}
|
|
543
|
+
// Group items by Garmin userId
|
|
544
|
+
const byUser = new Map();
|
|
545
|
+
for (const item of items) {
|
|
546
|
+
const garminUserId = item.userId;
|
|
547
|
+
if (!garminUserId) {
|
|
548
|
+
errors.push({ type, id: getId(item), error: "Missing userId in payload item" });
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (!byUser.has(garminUserId))
|
|
552
|
+
byUser.set(garminUserId, []);
|
|
553
|
+
byUser.get(garminUserId).push(item);
|
|
554
|
+
}
|
|
555
|
+
// Process each Garmin user's items
|
|
556
|
+
for (const [garminUserId, userItems] of byUser) {
|
|
557
|
+
const connection = await ctx.runQuery(internal.private.getConnectionByProviderUserId, { providerUserId: garminUserId, provider: "GARMIN" });
|
|
558
|
+
if (!connection) {
|
|
559
|
+
for (const item of userItems) {
|
|
560
|
+
errors.push({
|
|
561
|
+
type,
|
|
562
|
+
id: getId(item),
|
|
563
|
+
error: `No Soma connection found for Garmin userId "${garminUserId}"`,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (!connection.active) {
|
|
569
|
+
for (const item of userItems) {
|
|
570
|
+
errors.push({
|
|
571
|
+
type,
|
|
572
|
+
id: getId(item),
|
|
573
|
+
error: `Garmin connection for userId "${garminUserId}" is inactive`,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
const connectionId = connection._id;
|
|
579
|
+
const userId = connection.userId;
|
|
580
|
+
for (const item of userItems) {
|
|
581
|
+
try {
|
|
582
|
+
const data = transform(item);
|
|
583
|
+
if (data == null)
|
|
584
|
+
continue; // Skip items with no transformable data
|
|
585
|
+
await ctx.runMutation(ingest, {
|
|
586
|
+
connectionId,
|
|
587
|
+
userId,
|
|
588
|
+
...data,
|
|
589
|
+
});
|
|
590
|
+
processed++;
|
|
591
|
+
}
|
|
592
|
+
catch (err) {
|
|
593
|
+
errors.push({
|
|
594
|
+
type,
|
|
595
|
+
id: getId(item),
|
|
596
|
+
error: err instanceof Error ? err.message : String(err),
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
// Update last data timestamp
|
|
601
|
+
await ctx.runMutation(api.public.updateConnection, {
|
|
602
|
+
connectionId,
|
|
603
|
+
lastDataUpdate: new Date().toISOString(),
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
return { processed, errors };
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Process a webhook payload in either push or ping mode.
|
|
610
|
+
*
|
|
611
|
+
* - Push mode: items contain full data → transform and ingest directly.
|
|
612
|
+
* - Ping mode: items are notifications → fetch data from the API, then
|
|
613
|
+
* transform and ingest.
|
|
614
|
+
*/
|
|
615
|
+
async function processWebhookDualMode(ctx, config) {
|
|
616
|
+
const mode = isWebhookPushMode(config.items) ? "push" : "ping";
|
|
617
|
+
console.log(`[garmin:webhook:${config.type}] mode=${mode} items=${config.items.length} payload:`, JSON.stringify(config.items, null, 2));
|
|
618
|
+
if (mode === "ping") {
|
|
619
|
+
return await processWebhookPingPayload(ctx, config);
|
|
620
|
+
}
|
|
621
|
+
return await processWebhookPayload(ctx, config);
|
|
622
|
+
}
|
|
623
|
+
async function processWebhookPingPayload(ctx, config) {
|
|
624
|
+
const { items, type, fetchData, transform, ingest, getId } = config;
|
|
625
|
+
let processed = 0;
|
|
626
|
+
const errors = [];
|
|
627
|
+
if (!Array.isArray(items)) {
|
|
628
|
+
errors.push({ type, id: "payload", error: "Expected an array payload" });
|
|
629
|
+
return { processed, errors };
|
|
630
|
+
}
|
|
631
|
+
// Group by Garmin userId and merge time ranges
|
|
632
|
+
const byUser = new Map();
|
|
633
|
+
for (const item of items) {
|
|
634
|
+
const garminUserId = item.userId;
|
|
635
|
+
if (!garminUserId) {
|
|
636
|
+
errors.push({ type, id: "unknown", error: "Missing userId in ping notification" });
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
const existing = byUser.get(garminUserId);
|
|
640
|
+
const start = item.uploadStartTimeInSeconds ?? 0;
|
|
641
|
+
const end = item.uploadEndTimeInSeconds ?? 0;
|
|
642
|
+
const token = item.userAccessToken;
|
|
643
|
+
if (existing) {
|
|
644
|
+
existing.minStart = Math.min(existing.minStart, start);
|
|
645
|
+
existing.maxEnd = Math.max(existing.maxEnd, end);
|
|
646
|
+
if (token && !existing.userAccessToken)
|
|
647
|
+
existing.userAccessToken = token;
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
byUser.set(garminUserId, { userAccessToken: token, minStart: start, maxEnd: end });
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
for (const [garminUserId, notification] of byUser) {
|
|
654
|
+
const connection = await ctx.runQuery(internal.private.getConnectionByProviderUserId, { providerUserId: garminUserId, provider: "GARMIN" });
|
|
655
|
+
if (!connection) {
|
|
656
|
+
errors.push({
|
|
657
|
+
type,
|
|
658
|
+
id: "ping",
|
|
659
|
+
error: `No Soma connection found for Garmin userId "${garminUserId}"`,
|
|
660
|
+
});
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (!connection.active) {
|
|
664
|
+
errors.push({
|
|
665
|
+
type,
|
|
666
|
+
id: "ping",
|
|
667
|
+
error: `Garmin connection for userId "${garminUserId}" is inactive`,
|
|
668
|
+
});
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
const connectionId = connection._id;
|
|
672
|
+
const userId = connection.userId;
|
|
673
|
+
// Resolve a valid access token: prefer stored (with refresh), fall back to notification
|
|
674
|
+
let accessToken = null;
|
|
675
|
+
const tokenDoc = await ctx.runQuery(internal.garmin.private.getTokens, {
|
|
676
|
+
connectionId,
|
|
677
|
+
});
|
|
678
|
+
if (tokenDoc) {
|
|
679
|
+
accessToken = tokenDoc.accessToken;
|
|
680
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
681
|
+
if (tokenDoc.expiresAt &&
|
|
682
|
+
tokenDoc.refreshToken &&
|
|
683
|
+
nowSeconds >= tokenDoc.expiresAt - REFRESH_BUFFER_SECONDS) {
|
|
684
|
+
const clientId = process.env.GARMIN_CLIENT_ID;
|
|
685
|
+
const clientSecret = process.env.GARMIN_CLIENT_SECRET;
|
|
686
|
+
if (clientId && clientSecret) {
|
|
687
|
+
try {
|
|
688
|
+
const refreshed = await refreshToken({
|
|
689
|
+
clientId,
|
|
690
|
+
clientSecret,
|
|
691
|
+
refreshToken: tokenDoc.refreshToken,
|
|
692
|
+
});
|
|
693
|
+
accessToken = refreshed.access_token;
|
|
694
|
+
await ctx.runMutation(internal.garmin.private.storeTokens, {
|
|
695
|
+
connectionId,
|
|
696
|
+
accessToken: refreshed.access_token,
|
|
697
|
+
refreshToken: refreshed.refresh_token,
|
|
698
|
+
expiresAt: nowSeconds + refreshed.expires_in,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
catch {
|
|
702
|
+
// Refresh failed — fall through to notification token
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (!accessToken) {
|
|
708
|
+
accessToken = notification.userAccessToken ?? null;
|
|
709
|
+
}
|
|
710
|
+
if (!accessToken) {
|
|
711
|
+
errors.push({
|
|
712
|
+
type,
|
|
713
|
+
id: "ping",
|
|
714
|
+
error: `No access token available for Garmin userId "${garminUserId}"`,
|
|
715
|
+
});
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
const client = new GarminClient({ accessToken });
|
|
719
|
+
// Use the merged time range from all notifications for this user
|
|
720
|
+
let { minStart, maxEnd } = notification;
|
|
721
|
+
if (minStart === 0 && maxEnd === 0) {
|
|
722
|
+
const now = Math.floor(Date.now() / 1000);
|
|
723
|
+
minStart = now - 86400;
|
|
724
|
+
maxEnd = now;
|
|
725
|
+
}
|
|
726
|
+
try {
|
|
727
|
+
const records = await fetchData(client, {
|
|
728
|
+
uploadStartTimeInSeconds: minStart,
|
|
729
|
+
uploadEndTimeInSeconds: maxEnd,
|
|
730
|
+
});
|
|
731
|
+
for (const record of records) {
|
|
732
|
+
try {
|
|
733
|
+
const data = transform(record);
|
|
734
|
+
if (data == null)
|
|
735
|
+
continue;
|
|
736
|
+
await ctx.runMutation(ingest, {
|
|
737
|
+
connectionId,
|
|
738
|
+
userId,
|
|
739
|
+
...data,
|
|
740
|
+
});
|
|
741
|
+
processed++;
|
|
742
|
+
}
|
|
743
|
+
catch (err) {
|
|
744
|
+
errors.push({
|
|
745
|
+
type,
|
|
746
|
+
id: getId(record),
|
|
747
|
+
error: err instanceof Error ? err.message : String(err),
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
catch (err) {
|
|
753
|
+
errors.push({
|
|
754
|
+
type,
|
|
755
|
+
id: "fetch",
|
|
756
|
+
error: err instanceof Error ? err.message : String(err),
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
await ctx.runMutation(api.public.updateConnection, {
|
|
760
|
+
connectionId,
|
|
761
|
+
lastDataUpdate: new Date().toISOString(),
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
return { processed, errors };
|
|
765
|
+
}
|
|
766
|
+
//# sourceMappingURL=webhooks.js.map
|