@nativesquare/soma 0.13.0 → 0.14.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 (96) hide show
  1. package/dist/client/garmin.d.ts +4 -1
  2. package/dist/client/garmin.d.ts.map +1 -1
  3. package/dist/client/garmin.js +4 -1
  4. package/dist/client/garmin.js.map +1 -1
  5. package/dist/client/index.d.ts +2 -2
  6. package/dist/client/index.d.ts.map +1 -1
  7. package/dist/client/index.js +1 -1
  8. package/dist/client/index.js.map +1 -1
  9. package/dist/client/strava.d.ts +48 -34
  10. package/dist/client/strava.d.ts.map +1 -1
  11. package/dist/client/strava.js +141 -23
  12. package/dist/client/strava.js.map +1 -1
  13. package/dist/client/types.d.ts +108 -0
  14. package/dist/client/types.d.ts.map +1 -1
  15. package/dist/component/_generated/api.d.ts +2 -2
  16. package/dist/component/_generated/api.d.ts.map +1 -1
  17. package/dist/component/_generated/component.d.ts +19 -17
  18. package/dist/component/_generated/component.d.ts.map +1 -1
  19. package/dist/component/garmin/auth.d.ts +2 -1
  20. package/dist/component/garmin/auth.d.ts.map +1 -1
  21. package/dist/component/garmin/auth.js +6 -1
  22. package/dist/component/garmin/auth.js.map +1 -1
  23. package/dist/component/garmin/private.d.ts +17 -75
  24. package/dist/component/garmin/private.d.ts.map +1 -1
  25. package/dist/component/garmin/private.js +4 -167
  26. package/dist/component/garmin/private.js.map +1 -1
  27. package/dist/component/garmin/public.d.ts +18 -33
  28. package/dist/component/garmin/public.d.ts.map +1 -1
  29. package/dist/component/garmin/public.js +23 -22
  30. package/dist/component/garmin/public.js.map +1 -1
  31. package/dist/component/garmin/webhooks.d.ts +3 -6
  32. package/dist/component/garmin/webhooks.d.ts.map +1 -1
  33. package/dist/component/garmin/webhooks.js +17 -28
  34. package/dist/component/garmin/webhooks.js.map +1 -1
  35. package/dist/component/private.d.ts +59 -0
  36. package/dist/component/private.d.ts.map +1 -1
  37. package/dist/component/private.js +182 -1
  38. package/dist/component/private.js.map +1 -1
  39. package/dist/component/strava/auth.d.ts +2 -1
  40. package/dist/component/strava/auth.d.ts.map +1 -1
  41. package/dist/component/strava/auth.js +6 -1
  42. package/dist/component/strava/auth.js.map +1 -1
  43. package/dist/component/strava/public.d.ts +26 -50
  44. package/dist/component/strava/public.d.ts.map +1 -1
  45. package/dist/component/strava/public.js +88 -132
  46. package/dist/component/strava/public.js.map +1 -1
  47. package/dist/component/strava/webhooks.d.ts +17 -0
  48. package/dist/component/strava/webhooks.d.ts.map +1 -0
  49. package/dist/component/strava/webhooks.js +231 -0
  50. package/dist/component/strava/webhooks.js.map +1 -0
  51. package/dist/component/utils.d.ts +10 -0
  52. package/dist/component/utils.d.ts.map +1 -1
  53. package/dist/component/utils.js.map +1 -1
  54. package/dist/component/validators/athlete.d.ts +6 -0
  55. package/dist/component/validators/athlete.d.ts.map +1 -1
  56. package/dist/component/validators/athlete.js.map +1 -1
  57. package/dist/component/validators/nutrition.d.ts +6 -0
  58. package/dist/component/validators/nutrition.d.ts.map +1 -1
  59. package/dist/component/validators/nutrition.js.map +1 -1
  60. package/dist/component/validators/shared.d.ts +3 -0
  61. package/dist/component/validators/shared.d.ts.map +1 -1
  62. package/dist/component/validators/shared.js +1 -1
  63. package/dist/component/validators/shared.js.map +1 -1
  64. package/dist/component/validators/sleep.d.ts +6 -0
  65. package/dist/component/validators/sleep.d.ts.map +1 -1
  66. package/dist/component/validators/sleep.js.map +1 -1
  67. package/dist/validators.d.ts +7 -1
  68. package/dist/validators.d.ts.map +1 -1
  69. package/dist/validators.js +6 -6
  70. package/dist/validators.js.map +1 -1
  71. package/package.json +1 -1
  72. package/src/client/garmin.ts +4 -1
  73. package/src/client/index.ts +8 -1
  74. package/src/client/strava.ts +193 -27
  75. package/src/client/types.ts +125 -0
  76. package/src/component/_generated/api.ts +2 -2
  77. package/src/component/_generated/component.ts +25 -6
  78. package/src/component/garmin/auth.ts +9 -2
  79. package/src/component/garmin/private.ts +22 -243
  80. package/src/component/garmin/public.ts +56 -54
  81. package/src/component/garmin/webhooks.ts +38 -55
  82. package/src/component/private.ts +245 -1
  83. package/src/component/strava/auth.ts +9 -2
  84. package/src/component/strava/public.ts +105 -171
  85. package/src/component/strava/webhooks.ts +312 -0
  86. package/src/component/utils.ts +11 -0
  87. package/src/component/validators/athlete.ts +6 -0
  88. package/src/component/validators/nutrition.ts +6 -0
  89. package/src/component/validators/shared.ts +5 -2
  90. package/src/component/validators/sleep.ts +6 -0
  91. package/src/validators.ts +34 -7
  92. package/dist/component/strava/private.d.ts +0 -49
  93. package/dist/component/strava/private.d.ts.map +0 -1
  94. package/dist/component/strava/private.js +0 -121
  95. package/dist/component/strava/private.js.map +0 -1
  96. package/src/component/strava/private.ts +0 -147
@@ -57,6 +57,7 @@ import { transformPulseOx } from "./transform/pulseOx.js";
57
57
  import { transformRespiration } from "./transform/respiration.js";
58
58
  import { transformPlannedWorkoutToGarmin } from "./transform/plannedWorkout.js";
59
59
  import { api, internal } from "../_generated/api";
60
+ import type { SomaError } from "../validators/shared.js";
60
61
 
61
62
  // ─── OAuth ──────────────────────────────────────────────────────────────────
62
63
 
@@ -78,7 +79,7 @@ export const getGarminAuthUrl = action({
78
79
  state,
79
80
  });
80
81
 
81
- await ctx.runMutation(internal.garmin.private.storePendingOAuth, {
82
+ await ctx.runMutation(internal.private.storePendingOAuth, {
82
83
  provider: "GARMIN",
83
84
  state,
84
85
  codeVerifier,
@@ -101,7 +102,7 @@ export const completeGarminOAuth = action({
101
102
  connectionId: Id<"connections">;
102
103
  userId: string;
103
104
  }> => {
104
- const pending: Doc<"pendingOAuth"> | null = await ctx.runQuery(internal.garmin.private.getPendingOAuth, {
105
+ const pending: Doc<"pendingOAuth"> | null = await ctx.runQuery(internal.private.getPendingOAuth, {
105
106
  state: args.state,
106
107
  });
107
108
  if (!pending) {
@@ -126,7 +127,7 @@ export const completeGarminOAuth = action({
126
127
  redirectUri: args.redirectUri,
127
128
  });
128
129
 
129
- const _deleted: null = await ctx.runMutation(internal.garmin.private.deletePendingOAuth, {
130
+ const _deleted: null = await ctx.runMutation(internal.private.deletePendingOAuth, {
130
131
  state: args.state,
131
132
  });
132
133
 
@@ -136,7 +137,7 @@ export const completeGarminOAuth = action({
136
137
  });
137
138
 
138
139
  const expiresAt = Math.floor(Date.now() / 1000) + tokenResult.expires_in;
139
- const _stored: null = await ctx.runMutation(internal.garmin.private.storeTokens, {
140
+ const _stored: null = await ctx.runMutation(internal.private.storeTokens, {
140
141
  connectionId,
141
142
  accessToken: tokenResult.access_token,
142
143
  refreshToken: tokenResult.refresh_token,
@@ -179,7 +180,7 @@ export const disconnectGarmin = action({
179
180
  const connectionId = connection._id;
180
181
 
181
182
  // Best-effort: deregister user at Garmin
182
- const tokenDoc: Doc<"providerTokens"> | null = await ctx.runQuery(internal.garmin.private.getTokens, {
183
+ const tokenDoc: Doc<"providerTokens"> | null = await ctx.runQuery(internal.private.getTokens, {
183
184
  connectionId,
184
185
  });
185
186
  if (tokenDoc) {
@@ -191,7 +192,7 @@ export const disconnectGarmin = action({
191
192
  }
192
193
  }
193
194
 
194
- const _deleted: null = await ctx.runMutation(internal.garmin.private.deleteTokens, { connectionId });
195
+ const _deleted: null = await ctx.runMutation(internal.private.deleteTokens, { connectionId });
195
196
 
196
197
  const _disconnected: null = await ctx.runMutation(api.public.disconnect, {
197
198
  userId: args.userId,
@@ -215,9 +216,10 @@ export const pullActivities = action({
215
216
  handler: async (ctx, args) => {
216
217
 
217
218
  const { connectionId, accessToken } = await ctx.runAction(
218
- internal.garmin.private.resolveConnectionAndAccessToken,
219
+ internal.private.resolveConnectionAndAccessToken,
219
220
  {
220
221
  userId: args.userId,
222
+ provider: "GARMIN",
221
223
  clientId: args.clientId,
222
224
  clientSecret: args.clientSecret,
223
225
  },
@@ -227,7 +229,7 @@ export const pullActivities = action({
227
229
 
228
230
  const wellnessClient = createWellnessClient(accessToken);
229
231
  const synced = { activities: 0 };
230
- const errors: Array<{ type: string; id: string; message: string }> = [];
232
+ const errors: SomaError[] = [];
231
233
 
232
234
  try {
233
235
  const { data: activities, error } = await getActivities({
@@ -283,14 +285,14 @@ export const pullDailies = action({
283
285
  },
284
286
  handler: async (ctx, args) => {
285
287
  const { connectionId, accessToken } = await ctx.runAction(
286
- internal.garmin.private.resolveConnectionAndAccessToken,
287
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
288
+ internal.private.resolveConnectionAndAccessToken,
289
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
288
290
  );
289
291
 
290
292
  const query = buildTimeRangeQuery(args, accessToken);
291
293
  const wellnessClient = createWellnessClient(accessToken);
292
294
  const synced = { dailies: 0 };
293
- const errors: Array<{ type: string; id: string; message: string }> = [];
295
+ const errors: SomaError[] = [];
294
296
 
295
297
  try {
296
298
  const { data: dailies, error } = await getDailies({ client: wellnessClient, query });
@@ -324,14 +326,14 @@ export const pullSleep = action({
324
326
  },
325
327
  handler: async (ctx, args) => {
326
328
  const { connectionId, accessToken } = await ctx.runAction(
327
- internal.garmin.private.resolveConnectionAndAccessToken,
328
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
329
+ internal.private.resolveConnectionAndAccessToken,
330
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
329
331
  );
330
332
 
331
333
  const query = buildTimeRangeQuery(args, accessToken);
332
334
  const wellnessClient = createWellnessClient(accessToken);
333
335
  const synced = { sleep: 0 };
334
- const errors: Array<{ type: string; id: string; message: string }> = [];
336
+ const errors: SomaError[] = [];
335
337
 
336
338
  try {
337
339
  const { data: sleeps, error } = await getSleeps({ client: wellnessClient, query });
@@ -364,14 +366,14 @@ export const pullBody = action({
364
366
  },
365
367
  handler: async (ctx, args) => {
366
368
  const { connectionId, accessToken } = await ctx.runAction(
367
- internal.garmin.private.resolveConnectionAndAccessToken,
368
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
369
+ internal.private.resolveConnectionAndAccessToken,
370
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
369
371
  );
370
372
 
371
373
  const query = buildTimeRangeQuery(args, accessToken);
372
374
  const wellnessClient = createWellnessClient(accessToken);
373
375
  const synced = { body: 0 };
374
- const errors: Array<{ type: string; id: string; message: string }> = [];
376
+ const errors: SomaError[] = [];
375
377
 
376
378
  try {
377
379
  const { data: bodyComps, error } = await getBodyComps({ client: wellnessClient, query });
@@ -405,14 +407,14 @@ export const pullMenstruation = action({
405
407
  },
406
408
  handler: async (ctx, args) => {
407
409
  const { connectionId, accessToken } = await ctx.runAction(
408
- internal.garmin.private.resolveConnectionAndAccessToken,
409
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
410
+ internal.private.resolveConnectionAndAccessToken,
411
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
410
412
  );
411
413
 
412
414
  const query = buildTimeRangeQuery(args, accessToken);
413
415
  const wellnessClient = createWellnessClient(accessToken);
414
416
  const synced = { menstruation: 0 };
415
- const errors: Array<{ type: string; id: string; message: string }> = [];
417
+ const errors: SomaError[] = [];
416
418
 
417
419
  try {
418
420
  const { data: records, error } = await getMct({ client: wellnessClient, query });
@@ -445,14 +447,14 @@ export const pullBloodPressures = action({
445
447
  },
446
448
  handler: async (ctx, args) => {
447
449
  const { connectionId, accessToken } = await ctx.runAction(
448
- internal.garmin.private.resolveConnectionAndAccessToken,
449
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
450
+ internal.private.resolveConnectionAndAccessToken,
451
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
450
452
  );
451
453
 
452
454
  const query = buildTimeRangeQuery(args, accessToken);
453
455
  const wellnessClient = createWellnessClient(accessToken);
454
456
  const synced = { bloodPressures: 0 };
455
- const errors: Array<{ type: string; id: string; message: string }> = [];
457
+ const errors: SomaError[] = [];
456
458
 
457
459
  try {
458
460
  const { data: bpRecords, error } = await getBloodPressures({ client: wellnessClient, query });
@@ -486,14 +488,14 @@ export const pullSkinTemperature = action({
486
488
  },
487
489
  handler: async (ctx, args) => {
488
490
  const { connectionId, accessToken } = await ctx.runAction(
489
- internal.garmin.private.resolveConnectionAndAccessToken,
490
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
491
+ internal.private.resolveConnectionAndAccessToken,
492
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
491
493
  );
492
494
 
493
495
  const query = buildTimeRangeQuery(args, accessToken);
494
496
  const wellnessClient = createWellnessClient(accessToken);
495
497
  const synced = { skinTemp: 0 };
496
- const errors: Array<{ type: string; id: string; message: string }> = [];
498
+ const errors: SomaError[] = [];
497
499
 
498
500
  try {
499
501
  const { data: skinRecords, error } = await getSkinTemp({ client: wellnessClient, query });
@@ -527,14 +529,14 @@ export const pullUserMetrics = action({
527
529
  },
528
530
  handler: async (ctx, args) => {
529
531
  const { connectionId, accessToken } = await ctx.runAction(
530
- internal.garmin.private.resolveConnectionAndAccessToken,
531
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
532
+ internal.private.resolveConnectionAndAccessToken,
533
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
532
534
  );
533
535
 
534
536
  const query = buildTimeRangeQuery(args, accessToken);
535
537
  const wellnessClient = createWellnessClient(accessToken);
536
538
  const synced = { userMetrics: 0 };
537
- const errors: Array<{ type: string; id: string; message: string }> = [];
539
+ const errors: SomaError[] = [];
538
540
 
539
541
  try {
540
542
  const { data: metricsRecords, error } = await getUserMetrics({ client: wellnessClient, query });
@@ -568,14 +570,14 @@ export const pullHRV = action({
568
570
  },
569
571
  handler: async (ctx, args) => {
570
572
  const { connectionId, accessToken } = await ctx.runAction(
571
- internal.garmin.private.resolveConnectionAndAccessToken,
572
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
573
+ internal.private.resolveConnectionAndAccessToken,
574
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
573
575
  );
574
576
 
575
577
  const query = buildTimeRangeQuery(args, accessToken);
576
578
  const wellnessClient = createWellnessClient(accessToken);
577
579
  const synced = { hrv: 0 };
578
- const errors: Array<{ type: string; id: string; message: string }> = [];
580
+ const errors: SomaError[] = [];
579
581
 
580
582
  try {
581
583
  const { data: hrvRecords, error } = await getHrv({ client: wellnessClient, query });
@@ -609,14 +611,14 @@ export const pullStressDetails = action({
609
611
  },
610
612
  handler: async (ctx, args) => {
611
613
  const { connectionId, accessToken } = await ctx.runAction(
612
- internal.garmin.private.resolveConnectionAndAccessToken,
613
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
614
+ internal.private.resolveConnectionAndAccessToken,
615
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
614
616
  );
615
617
 
616
618
  const query = buildTimeRangeQuery(args, accessToken);
617
619
  const wellnessClient = createWellnessClient(accessToken);
618
620
  const synced = { stressDetails: 0 };
619
- const errors: Array<{ type: string; id: string; message: string }> = [];
621
+ const errors: SomaError[] = [];
620
622
 
621
623
  try {
622
624
  const { data: stressRecords, error } = await getStressDetails({ client: wellnessClient, query });
@@ -650,14 +652,14 @@ export const pullPulseOx = action({
650
652
  },
651
653
  handler: async (ctx, args) => {
652
654
  const { connectionId, accessToken } = await ctx.runAction(
653
- internal.garmin.private.resolveConnectionAndAccessToken,
654
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
655
+ internal.private.resolveConnectionAndAccessToken,
656
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
655
657
  );
656
658
 
657
659
  const query = buildTimeRangeQuery(args, accessToken);
658
660
  const wellnessClient = createWellnessClient(accessToken);
659
661
  const synced = { pulseOx: 0 };
660
- const errors: Array<{ type: string; id: string; message: string }> = [];
662
+ const errors: SomaError[] = [];
661
663
 
662
664
  try {
663
665
  const { data: pulseOxRecords, error } = await getPulseox({ client: wellnessClient, query });
@@ -691,14 +693,14 @@ export const pullRespiration = action({
691
693
  },
692
694
  handler: async (ctx, args) => {
693
695
  const { connectionId, accessToken } = await ctx.runAction(
694
- internal.garmin.private.resolveConnectionAndAccessToken,
695
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
696
+ internal.private.resolveConnectionAndAccessToken,
697
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
696
698
  );
697
699
 
698
700
  const query = buildTimeRangeQuery(args, accessToken);
699
701
  const wellnessClient = createWellnessClient(accessToken);
700
702
  const synced = { respiration: 0 };
701
- const errors: Array<{ type: string; id: string; message: string }> = [];
703
+ const errors: SomaError[] = [];
702
704
 
703
705
  try {
704
706
  const { data: respRecords, error } = await getRespiration({ client: wellnessClient, query });
@@ -753,7 +755,7 @@ export const pullAll = action({
753
755
  { ref: api.garmin.public.pullRespiration, name: "respiration" },
754
756
  ];
755
757
  const synced: Record<string, number> = {};
756
- const errors: Array<{ type: string; id: string; message: string }> = [];
758
+ const errors: SomaError[] = [];
757
759
  for (const { ref, name } of pullFns) {
758
760
  try {
759
761
  const result = await ctx.runAction(ref, sharedArgs);
@@ -784,11 +786,11 @@ export const pushWorkout = action({
784
786
  },
785
787
  handler: async (ctx, args): Promise<{
786
788
  data: { garminWorkoutId: number } | null;
787
- errors: Array<{ type: string; id: string; message: string }>;
789
+ errors: SomaError[];
788
790
  }> => {
789
791
  const { connectionId, accessToken } = await ctx.runAction(
790
- internal.garmin.private.resolveConnectionAndAccessToken,
791
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
792
+ internal.private.resolveConnectionAndAccessToken,
793
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
792
794
  );
793
795
 
794
796
  const plannedWorkout: Doc<"plannedWorkouts"> | null = await ctx.runQuery(
@@ -869,11 +871,11 @@ export const pushSchedule = action({
869
871
  },
870
872
  handler: async (ctx, args): Promise<{
871
873
  data: { garminScheduleId: number } | null;
872
- errors: Array<{ type: string; id: string; message: string }>;
874
+ errors: SomaError[];
873
875
  }> => {
874
876
  const { connectionId, accessToken } = await ctx.runAction(
875
- internal.garmin.private.resolveConnectionAndAccessToken,
876
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
877
+ internal.private.resolveConnectionAndAccessToken,
878
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
877
879
  );
878
880
 
879
881
  const plannedWorkout: Doc<"plannedWorkouts"> | null = await ctx.runQuery(
@@ -965,11 +967,11 @@ export const deleteWorkout = action({
965
967
  },
966
968
  handler: async (ctx, args): Promise<{
967
969
  data: null;
968
- errors: Array<{ type: string; id: string; message: string }>;
970
+ errors: SomaError[];
969
971
  }> => {
970
972
  const { connectionId, accessToken } = await ctx.runAction(
971
- internal.garmin.private.resolveConnectionAndAccessToken,
972
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
973
+ internal.private.resolveConnectionAndAccessToken,
974
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
973
975
  );
974
976
 
975
977
  const plannedWorkout: Doc<"plannedWorkouts"> | null = await ctx.runQuery(
@@ -1023,11 +1025,11 @@ export const deleteSchedule = action({
1023
1025
  },
1024
1026
  handler: async (ctx, args): Promise<{
1025
1027
  data: null;
1026
- errors: Array<{ type: string; id: string; message: string }>;
1028
+ errors: SomaError[];
1027
1029
  }> => {
1028
1030
  const { connectionId, accessToken } = await ctx.runAction(
1029
- internal.garmin.private.resolveConnectionAndAccessToken,
1030
- { userId: args.userId, clientId: args.clientId, clientSecret: args.clientSecret },
1031
+ internal.private.resolveConnectionAndAccessToken,
1032
+ { userId: args.userId, provider: "GARMIN", clientId: args.clientId, clientSecret: args.clientSecret },
1031
1033
  );
1032
1034
 
1033
1035
  const plannedWorkout: Doc<"plannedWorkouts"> | null = await ctx.runQuery(
@@ -6,6 +6,7 @@
6
6
  import { v } from "convex/values";
7
7
  import { action, type ActionCtx } from "../_generated/server";
8
8
  import { api, internal } from "../_generated/api";
9
+ import type { SomaError } from "../validators/shared.js";
9
10
  import {
10
11
  garminSkinTemperaturePingPayloadSchema,
11
12
  garminSkinTemperaturePushPayloadSchema,
@@ -103,37 +104,19 @@ function isWebhookPushMode(payload: unknown): boolean {
103
104
  return !("callbackURL" in firstItem);
104
105
  }
105
106
 
106
- /** Shape returned by every public webhook handler action to the HTTP layer. */
107
+ /** Shape returned by webhook handler actions (both internal processors and public handlers). */
107
108
  type WebhookResult = {
108
- errors: Array<{ type: string; id: string; message: string }>;
109
+ errors: SomaError[];
109
110
  items: Array<{ connectionId: string; userId: string; data: Record<string, unknown> }>;
110
111
  };
111
112
 
112
- /** Shape returned by internal push-processing actions (transform only, no DB writes). */
113
- type ProcessResult = {
114
- items: Array<{ connectionId: string; userId: string; data: Record<string, unknown> }>;
115
- errors: Array<{ type: string; id: string; message: string }>;
116
- };
117
-
118
- /**
119
- * Build a WebhookResult from a ProcessResult without writing to the database.
120
- * Used when `autoIngest` is disabled — the host app still gets the full set of
121
- * transformed items in its callbacks, but no data is persisted.
122
- */
123
- function toWebhookResult(result: ProcessResult): WebhookResult {
124
- return {
125
- errors: result.errors,
126
- items: result.items,
127
- };
128
- }
129
-
130
113
  /**
131
114
  * Ingest transformed items from a private handler and update connections.
132
115
  * Shared orchestration logic for all push-mode webhook handlers.
133
116
  */
134
117
  async function ingestAndUpdate(
135
118
  ctx: ActionCtx,
136
- result: ProcessResult,
119
+ result: WebhookResult,
137
120
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
138
121
  ingestMutation: any,
139
122
  ): Promise<WebhookResult> {
@@ -179,13 +162,13 @@ export const handleGarminWebhookActivities = action({
179
162
  if (isWebhookPushMode(args.payload)) {
180
163
  const pushResult = garminActivityPushPayloadSchema.safeParse(args.payload);
181
164
  if (pushResult.success && pushResult.data.activities.length > 0) {
182
- const result: ProcessResult = await ctx.runAction(
165
+ const result: WebhookResult = await ctx.runAction(
183
166
  internal.garmin.private.processActivityPushPayload,
184
167
  { payload: args.payload },
185
168
  );
186
169
  return shouldIngest
187
170
  ? await ingestAndUpdate(ctx, result, api.public.ingestActivity)
188
- : toWebhookResult(result);
171
+ : result;
189
172
  }
190
173
  } else {
191
174
  const pingResult = garminActivityPingPayloadSchema.safeParse(args.payload);
@@ -218,13 +201,13 @@ export const handleGarminWebhookActivityDetails = action({
218
201
  pushResult.success &&
219
202
  pushResult.data.activityDetails.length > 0
220
203
  ) {
221
- const result: ProcessResult = await ctx.runAction(
204
+ const result: WebhookResult = await ctx.runAction(
222
205
  internal.garmin.private.processActivityDetailsPushPayload,
223
206
  { payload: args.payload },
224
207
  );
225
208
  return shouldIngest
226
209
  ? await ingestAndUpdate(ctx, result, api.public.ingestActivity)
227
- : toWebhookResult(result);
210
+ : result;
228
211
  }
229
212
  } else {
230
213
  const pingResult =
@@ -263,14 +246,14 @@ export const handleGarminWebhookManuallyUpdatedActivities = action({
263
246
  pushResult.success &&
264
247
  pushResult.data.manuallyUpdatedActivities.length > 0
265
248
  ) {
266
- const result: ProcessResult = await ctx.runAction(
249
+ const result: WebhookResult = await ctx.runAction(
267
250
  internal.garmin.private
268
251
  .processManuallyUpdatedActivitiesPushPayload,
269
252
  { payload: args.payload },
270
253
  );
271
254
  return shouldIngest
272
255
  ? await ingestAndUpdate(ctx, result, api.public.ingestActivity)
273
- : toWebhookResult(result);
256
+ : result;
274
257
  }
275
258
  } else {
276
259
  const pingResult =
@@ -310,13 +293,13 @@ export const handleGarminWebhookMoveIQ = action({
310
293
  pushResult.success &&
311
294
  pushResult.data.moveIQActivities.length > 0
312
295
  ) {
313
- const result: ProcessResult = await ctx.runAction(
296
+ const result: WebhookResult = await ctx.runAction(
314
297
  internal.garmin.private.processMoveIQPushPayload,
315
298
  { payload: args.payload },
316
299
  );
317
300
  return shouldIngest
318
301
  ? await ingestAndUpdate(ctx, result, api.public.ingestActivity)
319
- : toWebhookResult(result);
302
+ : result;
320
303
  }
321
304
  } else {
322
305
  const pingResult =
@@ -353,13 +336,13 @@ export const handleGarminWebhookBloodPressures = action({
353
336
  pushResult.success &&
354
337
  pushResult.data.bloodPressures.length > 0
355
338
  ) {
356
- const result: ProcessResult = await ctx.runAction(
339
+ const result: WebhookResult = await ctx.runAction(
357
340
  internal.garmin.private.processBloodPressurePushPayload,
358
341
  { payload: args.payload },
359
342
  );
360
343
  return shouldIngest
361
344
  ? await ingestAndUpdate(ctx, result, api.public.ingestBody)
362
- : toWebhookResult(result);
345
+ : result;
363
346
  }
364
347
  } else {
365
348
  const pingResult =
@@ -396,13 +379,13 @@ export const handleGarminWebhookBodyCompositions = action({
396
379
  pushResult.success &&
397
380
  pushResult.data.bodyComps.length > 0
398
381
  ) {
399
- const result: ProcessResult = await ctx.runAction(
382
+ const result: WebhookResult = await ctx.runAction(
400
383
  internal.garmin.private.processBodyCompositionsPushPayload,
401
384
  { payload: args.payload },
402
385
  );
403
386
  return shouldIngest
404
387
  ? await ingestAndUpdate(ctx, result, api.public.ingestBody)
405
- : toWebhookResult(result);
388
+ : result;
406
389
  }
407
390
  } else {
408
391
  const pingResult =
@@ -439,13 +422,13 @@ export const handleGarminWebhookDailies = action({
439
422
  pushResult.success &&
440
423
  pushResult.data.dailies.length > 0
441
424
  ) {
442
- const result: ProcessResult = await ctx.runAction(
425
+ const result: WebhookResult = await ctx.runAction(
443
426
  internal.garmin.private.processDailiesPushPayload,
444
427
  { payload: args.payload },
445
428
  );
446
429
  return shouldIngest
447
430
  ? await ingestAndUpdate(ctx, result, api.public.ingestDaily)
448
- : toWebhookResult(result);
431
+ : result;
449
432
  }
450
433
  } else {
451
434
  const pingResult =
@@ -482,13 +465,13 @@ export const handleGarminWebhookEpochs = action({
482
465
  pushResult.success &&
483
466
  pushResult.data.epochs.length > 0
484
467
  ) {
485
- const result: ProcessResult = await ctx.runAction(
468
+ const result: WebhookResult = await ctx.runAction(
486
469
  internal.garmin.private.processEpochPushPayload,
487
470
  { payload: args.payload },
488
471
  );
489
472
  return shouldIngest
490
473
  ? await ingestAndUpdate(ctx, result, api.public.ingestDaily)
491
- : toWebhookResult(result);
474
+ : result;
492
475
  }
493
476
  } else {
494
477
  const pingResult =
@@ -525,13 +508,13 @@ export const handleGarminWebhookHealthSnapshot = action({
525
508
  pushResult.success &&
526
509
  pushResult.data.healthSnapshot.length > 0
527
510
  ) {
528
- const result: ProcessResult = await ctx.runAction(
511
+ const result: WebhookResult = await ctx.runAction(
529
512
  internal.garmin.private.processHealthSnapshotPushPayload,
530
513
  { payload: args.payload },
531
514
  );
532
515
  return shouldIngest
533
516
  ? await ingestAndUpdate(ctx, result, api.public.ingestDaily)
534
- : toWebhookResult(result);
517
+ : result;
535
518
  }
536
519
  } else {
537
520
  const pingResult =
@@ -568,13 +551,13 @@ export const handleGarminWebhookSleeps = action({
568
551
  pushResult.success &&
569
552
  pushResult.data.sleeps.length > 0
570
553
  ) {
571
- const result: ProcessResult = await ctx.runAction(
554
+ const result: WebhookResult = await ctx.runAction(
572
555
  internal.garmin.private.processSleepsPushPayload,
573
556
  { payload: args.payload },
574
557
  );
575
558
  return shouldIngest
576
559
  ? await ingestAndUpdate(ctx, result, api.public.ingestSleep)
577
- : toWebhookResult(result);
560
+ : result;
578
561
  }
579
562
  } else {
580
563
  const pingResult =
@@ -611,13 +594,13 @@ export const handleGarminWebhookSkinTemp = action({
611
594
  pushResult.success &&
612
595
  pushResult.data.skinTemp.length > 0
613
596
  ) {
614
- const result: ProcessResult = await ctx.runAction(
597
+ const result: WebhookResult = await ctx.runAction(
615
598
  internal.garmin.private.processSkinTemperaturePushPayload,
616
599
  { payload: args.payload },
617
600
  );
618
601
  return shouldIngest
619
602
  ? await ingestAndUpdate(ctx, result, api.public.ingestBody)
620
- : toWebhookResult(result);
603
+ : result;
621
604
  }
622
605
  } else {
623
606
  const pingResult =
@@ -654,13 +637,13 @@ export const handleGarminWebhookUserMetrics = action({
654
637
  pushResult.success &&
655
638
  pushResult.data.userMetrics.length > 0
656
639
  ) {
657
- const result: ProcessResult = await ctx.runAction(
640
+ const result: WebhookResult = await ctx.runAction(
658
641
  internal.garmin.private.processUserMetricsPushPayload,
659
642
  { payload: args.payload },
660
643
  );
661
644
  return shouldIngest
662
645
  ? await ingestAndUpdate(ctx, result, api.public.ingestBody)
663
- : toWebhookResult(result);
646
+ : result;
664
647
  }
665
648
  } else {
666
649
  const pingResult =
@@ -697,13 +680,13 @@ export const handleGarminWebhookMenstrualCycleTracking = action({
697
680
  pushResult.success &&
698
681
  pushResult.data.mct.length > 0
699
682
  ) {
700
- const result: ProcessResult = await ctx.runAction(
683
+ const result: WebhookResult = await ctx.runAction(
701
684
  internal.garmin.private.processMenstrualCycleTrackingPushPayload,
702
685
  { payload: args.payload },
703
686
  );
704
687
  return shouldIngest
705
688
  ? await ingestAndUpdate(ctx, result, api.public.ingestMenstruation)
706
- : toWebhookResult(result);
689
+ : result;
707
690
  }
708
691
  } else {
709
692
  const pingResult =
@@ -740,13 +723,13 @@ export const handleGarminWebhookHRVSummary = action({
740
723
  pushResult.success &&
741
724
  pushResult.data.hrv.length > 0
742
725
  ) {
743
- const result: ProcessResult = await ctx.runAction(
726
+ const result: WebhookResult = await ctx.runAction(
744
727
  internal.garmin.private.processHRVSummaryPushPayload,
745
728
  { payload: args.payload },
746
729
  );
747
730
  return shouldIngest
748
731
  ? await ingestAndUpdate(ctx, result, api.public.ingestDaily)
749
- : toWebhookResult(result);
732
+ : result;
750
733
  }
751
734
  } else {
752
735
  const pingResult =
@@ -783,13 +766,13 @@ export const handleGarminWebhookStress = action({
783
766
  pushResult.success &&
784
767
  pushResult.data.stressDetails.length > 0
785
768
  ) {
786
- const result: ProcessResult = await ctx.runAction(
769
+ const result: WebhookResult = await ctx.runAction(
787
770
  internal.garmin.private.processStressPushPayload,
788
771
  { payload: args.payload },
789
772
  );
790
773
  return shouldIngest
791
774
  ? await ingestAndUpdate(ctx, result, api.public.ingestDaily)
792
- : toWebhookResult(result);
775
+ : result;
793
776
  }
794
777
  } else {
795
778
  const pingResult =
@@ -826,13 +809,13 @@ export const handleGarminWebhookPulseOx = action({
826
809
  pushResult.success &&
827
810
  pushResult.data.pulseox.length > 0
828
811
  ) {
829
- const result: ProcessResult = await ctx.runAction(
812
+ const result: WebhookResult = await ctx.runAction(
830
813
  internal.garmin.private.processPulseOxPushPayload,
831
814
  { payload: args.payload },
832
815
  );
833
816
  return shouldIngest
834
817
  ? await ingestAndUpdate(ctx, result, api.public.ingestDaily)
835
- : toWebhookResult(result);
818
+ : result;
836
819
  }
837
820
  } else {
838
821
  const pingResult =
@@ -869,13 +852,13 @@ export const handleGarminWebhookRespiration = action({
869
852
  pushResult.success &&
870
853
  pushResult.data.allDayRespiration.length > 0
871
854
  ) {
872
- const result: ProcessResult = await ctx.runAction(
855
+ const result: WebhookResult = await ctx.runAction(
873
856
  internal.garmin.private.processRespirationPushPayload,
874
857
  { payload: args.payload },
875
858
  );
876
859
  return shouldIngest
877
860
  ? await ingestAndUpdate(ctx, result, api.public.ingestDaily)
878
- : toWebhookResult(result);
861
+ : result;
879
862
  }
880
863
  } else {
881
864
  const pingResult =