@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.
- package/dist/client/garmin.d.ts +4 -1
- package/dist/client/garmin.d.ts.map +1 -1
- package/dist/client/garmin.js +4 -1
- package/dist/client/garmin.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/strava.d.ts +48 -34
- package/dist/client/strava.d.ts.map +1 -1
- package/dist/client/strava.js +141 -23
- package/dist/client/strava.js.map +1 -1
- package/dist/client/types.d.ts +108 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -2
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/component.d.ts +19 -17
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/garmin/auth.d.ts +2 -1
- package/dist/component/garmin/auth.d.ts.map +1 -1
- package/dist/component/garmin/auth.js +6 -1
- package/dist/component/garmin/auth.js.map +1 -1
- package/dist/component/garmin/private.d.ts +17 -75
- package/dist/component/garmin/private.d.ts.map +1 -1
- package/dist/component/garmin/private.js +4 -167
- package/dist/component/garmin/private.js.map +1 -1
- package/dist/component/garmin/public.d.ts +18 -33
- package/dist/component/garmin/public.d.ts.map +1 -1
- package/dist/component/garmin/public.js +23 -22
- package/dist/component/garmin/public.js.map +1 -1
- package/dist/component/garmin/webhooks.d.ts +3 -6
- package/dist/component/garmin/webhooks.d.ts.map +1 -1
- package/dist/component/garmin/webhooks.js +17 -28
- package/dist/component/garmin/webhooks.js.map +1 -1
- package/dist/component/private.d.ts +59 -0
- package/dist/component/private.d.ts.map +1 -1
- package/dist/component/private.js +182 -1
- package/dist/component/private.js.map +1 -1
- package/dist/component/strava/auth.d.ts +2 -1
- package/dist/component/strava/auth.d.ts.map +1 -1
- package/dist/component/strava/auth.js +6 -1
- package/dist/component/strava/auth.js.map +1 -1
- package/dist/component/strava/public.d.ts +26 -50
- package/dist/component/strava/public.d.ts.map +1 -1
- package/dist/component/strava/public.js +88 -132
- package/dist/component/strava/public.js.map +1 -1
- package/dist/component/strava/webhooks.d.ts +17 -0
- package/dist/component/strava/webhooks.d.ts.map +1 -0
- package/dist/component/strava/webhooks.js +231 -0
- package/dist/component/strava/webhooks.js.map +1 -0
- package/dist/component/utils.d.ts +10 -0
- package/dist/component/utils.d.ts.map +1 -1
- package/dist/component/utils.js.map +1 -1
- package/dist/component/validators/athlete.d.ts +6 -0
- package/dist/component/validators/athlete.d.ts.map +1 -1
- package/dist/component/validators/athlete.js.map +1 -1
- package/dist/component/validators/nutrition.d.ts +6 -0
- package/dist/component/validators/nutrition.d.ts.map +1 -1
- package/dist/component/validators/nutrition.js.map +1 -1
- package/dist/component/validators/shared.d.ts +3 -0
- package/dist/component/validators/shared.d.ts.map +1 -1
- package/dist/component/validators/shared.js +1 -1
- package/dist/component/validators/shared.js.map +1 -1
- package/dist/component/validators/sleep.d.ts +6 -0
- package/dist/component/validators/sleep.d.ts.map +1 -1
- package/dist/component/validators/sleep.js.map +1 -1
- package/dist/validators.d.ts +7 -1
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +6 -6
- package/dist/validators.js.map +1 -1
- package/package.json +1 -1
- package/src/client/garmin.ts +4 -1
- package/src/client/index.ts +8 -1
- package/src/client/strava.ts +193 -27
- package/src/client/types.ts +125 -0
- package/src/component/_generated/api.ts +2 -2
- package/src/component/_generated/component.ts +25 -6
- package/src/component/garmin/auth.ts +9 -2
- package/src/component/garmin/private.ts +22 -243
- package/src/component/garmin/public.ts +56 -54
- package/src/component/garmin/webhooks.ts +38 -55
- package/src/component/private.ts +245 -1
- package/src/component/strava/auth.ts +9 -2
- package/src/component/strava/public.ts +105 -171
- package/src/component/strava/webhooks.ts +312 -0
- package/src/component/utils.ts +11 -0
- package/src/component/validators/athlete.ts +6 -0
- package/src/component/validators/nutrition.ts +6 -0
- package/src/component/validators/shared.ts +5 -2
- package/src/component/validators/sleep.ts +6 -0
- package/src/validators.ts +34 -7
- package/dist/component/strava/private.d.ts +0 -49
- package/dist/component/strava/private.d.ts.map +0 -1
- package/dist/component/strava/private.js +0 -121
- package/dist/component/strava/private.js.map +0 -1
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
789
|
+
errors: SomaError[];
|
|
788
790
|
}> => {
|
|
789
791
|
const { connectionId, accessToken } = await ctx.runAction(
|
|
790
|
-
internal.
|
|
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:
|
|
874
|
+
errors: SomaError[];
|
|
873
875
|
}> => {
|
|
874
876
|
const { connectionId, accessToken } = await ctx.runAction(
|
|
875
|
-
internal.
|
|
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:
|
|
970
|
+
errors: SomaError[];
|
|
969
971
|
}> => {
|
|
970
972
|
const { connectionId, accessToken } = await ctx.runAction(
|
|
971
|
-
internal.
|
|
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:
|
|
1028
|
+
errors: SomaError[];
|
|
1027
1029
|
}> => {
|
|
1028
1030
|
const { connectionId, accessToken } = await ctx.runAction(
|
|
1029
|
-
internal.
|
|
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
|
|
107
|
+
/** Shape returned by webhook handler actions (both internal processors and public handlers). */
|
|
107
108
|
type WebhookResult = {
|
|
108
|
-
errors:
|
|
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:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
:
|
|
861
|
+
: result;
|
|
879
862
|
}
|
|
880
863
|
} else {
|
|
881
864
|
const pingResult =
|