@nativesquare/soma 0.10.0 → 0.10.2

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 (31) hide show
  1. package/dist/client/index.d.ts +85 -163
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +109 -130
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/component/_generated/component.d.ts +92 -35
  6. package/dist/component/_generated/component.d.ts.map +1 -1
  7. package/dist/component/garmin/private.d.ts +9 -0
  8. package/dist/component/garmin/private.d.ts.map +1 -1
  9. package/dist/component/garmin/private.js +49 -0
  10. package/dist/component/garmin/private.js.map +1 -1
  11. package/dist/component/garmin/public.d.ts +237 -62
  12. package/dist/component/garmin/public.d.ts.map +1 -1
  13. package/dist/component/garmin/public.js +683 -253
  14. package/dist/component/garmin/public.js.map +1 -1
  15. package/dist/component/garmin/utils.d.ts +8 -0
  16. package/dist/component/garmin/utils.d.ts.map +1 -1
  17. package/dist/component/garmin/utils.js +9 -0
  18. package/dist/component/garmin/utils.js.map +1 -1
  19. package/dist/component/strava/public.d.ts +12 -52
  20. package/dist/component/strava/public.d.ts.map +1 -1
  21. package/dist/component/strava/public.js +16 -92
  22. package/dist/component/strava/public.js.map +1 -1
  23. package/dist/component/strava/transform/activity.js +15 -9
  24. package/dist/component/strava/transform/activity.js.map +1 -1
  25. package/package.json +1 -1
  26. package/src/client/index.ts +210 -158
  27. package/src/component/_generated/component.ts +165 -31
  28. package/src/component/garmin/private.ts +84 -0
  29. package/src/component/garmin/public.ts +804 -347
  30. package/src/component/garmin/utils.ts +17 -0
  31. package/src/component/strava/public.ts +17 -123
@@ -75,8 +75,11 @@ export interface SomaGarminConfig {
75
75
  * provider: "GARMIN",
76
76
  * });
77
77
  *
78
- * // Connect via Strava (handles OAuth, token storage, and initial sync):
79
- * const result = await soma.connectStrava(ctx, { userId: "user_123", code: "..." });
78
+ * // Start Strava OAuth (redirects user, callback handled by registerRoutes):
79
+ * const { authUrl } = await soma.getStravaAuthUrl(ctx, {
80
+ * userId: "user_123",
81
+ * redirectUri: "https://your-app.convex.site/api/strava/callback",
82
+ * });
80
83
  * ```
81
84
  */
82
85
  export class Soma {
@@ -879,18 +882,14 @@ export class Soma {
879
882
  /**
880
883
  * Generate a Strava OAuth authorization URL.
881
884
  *
882
- * If `userId` is provided, the state parameter is stored inside the
883
- * component automatically, and the callback handler registered by
884
- * `registerRoutes` will complete the flow without further host-app
885
- * intervention. This is the recommended approach.
886
- *
887
- * If `userId` is omitted, the host app must store the returned `state`
888
- * itself and handle the callback via `connectStrava`.
885
+ * The state parameter is stored inside the component automatically,
886
+ * and the callback handler registered by `registerRoutes` will
887
+ * complete the flow without further host-app intervention.
889
888
  *
890
889
  * @param ctx - Action context from the host app
890
+ * @param opts.userId - The host app's user identifier
891
891
  * @param opts.redirectUri - The URL Strava will redirect to after authorization
892
892
  * @param opts.scope - Comma-separated Strava OAuth scopes (default: "read,activity:read_all,profile:read_all")
893
- * @param opts.userId - The host app's user identifier (required for `registerRoutes` flow)
894
893
  * @returns `{ authUrl, state }`
895
894
  *
896
895
  * @example
@@ -904,7 +903,7 @@ export class Soma {
904
903
  */
905
904
  async getStravaAuthUrl(
906
905
  ctx: ActionCtx,
907
- opts: { redirectUri: string; scope?: string; userId?: string },
906
+ opts: { userId: string; redirectUri: string; scope?: string },
908
907
  ) {
909
908
  const config = this.requireStravaConfig();
910
909
  return await ctx.runAction(this.component.strava.public.getStravaAuthUrl, {
@@ -915,69 +914,6 @@ export class Soma {
915
914
  });
916
915
  }
917
916
 
918
- /**
919
- * Handle the Strava OAuth callback.
920
- *
921
- * Exchanges the authorization code for tokens, creates/reactivates the
922
- * Soma connection, stores tokens securely in the component, syncs the
923
- * athlete profile, and syncs all activities.
924
- *
925
- * Call this from your OAuth callback endpoint after receiving the `code`
926
- * query parameter from Strava.
927
- *
928
- * @param ctx - Action context from the host app
929
- * @param args.userId - The host app's user identifier
930
- * @param args.code - The authorization code from the OAuth callback
931
- * @returns `{ connectionId, synced, errors }`
932
- *
933
- * @example
934
- * ```ts
935
- * export const handleStravaCallback = action({
936
- * args: { userId: v.string(), code: v.string() },
937
- * handler: async (ctx, { userId, code }) => {
938
- * return await soma.connectStrava(ctx, { userId, code });
939
- * },
940
- * });
941
- * ```
942
- */
943
- async connectStrava(
944
- ctx: ActionCtx,
945
- args: { userId: string; code: string },
946
- ) {
947
- const config = this.requireStravaConfig();
948
- return await ctx.runAction(this.component.strava.public.connectStrava, {
949
- ...args,
950
- clientId: config.clientId,
951
- clientSecret: config.clientSecret,
952
- });
953
- }
954
-
955
- /**
956
- * Complete a Strava OAuth flow using stored pending state.
957
- *
958
- * This is called automatically by the `registerRoutes` callback handler.
959
- * It looks up the pending OAuth state stored during `getStravaAuthUrl`,
960
- * exchanges for tokens, creates the connection, and syncs data.
961
- *
962
- * @param ctx - Action context from the host app
963
- * @param args.code - The authorization code from the callback query params
964
- * @param args.state - The state parameter from the callback query params
965
- * @returns `{ connectionId, userId, synced, errors }`
966
- */
967
- async completeStravaOAuth(
968
- ctx: ActionCtx,
969
- args: { code: string; state: string },
970
- ) {
971
- const config = this.requireStravaConfig();
972
- return await ctx.runAction(
973
- this.component.strava.public.completeStravaOAuth,
974
- {
975
- ...args,
976
- clientId: config.clientId,
977
- clientSecret: config.clientSecret,
978
- },
979
- );
980
- }
981
917
 
982
918
  /**
983
919
  * Sync activities from Strava for an already-connected user.
@@ -1051,20 +987,13 @@ export class Soma {
1051
987
  /**
1052
988
  * Generate a Garmin OAuth 2.0 authorization URL with PKCE.
1053
989
  *
1054
- * Returns the `authUrl` to redirect the user to, along with the `state`
1055
- * and `codeVerifier` used for the PKCE flow.
1056
- *
1057
- * If `userId` is provided, the PKCE state is stored inside the component
1058
- * automatically, and the callback handler registered by `registerRoutes`
1059
- * will complete the flow without further host-app intervention. This is
1060
- * the recommended approach.
1061
- *
1062
- * If `userId` is omitted, the host app must store the returned
1063
- * `codeVerifier` itself and pass it to `connectGarmin` manually.
990
+ * The PKCE state is stored inside the component automatically,
991
+ * and the callback handler registered by `registerRoutes` will
992
+ * complete the flow without further host-app intervention.
1064
993
  *
1065
994
  * @param ctx - Action context from the host app
995
+ * @param opts.userId - The host app's user identifier
1066
996
  * @param opts.redirectUri - The URL Garmin will redirect to after authorization
1067
- * @param opts.userId - The host app's user identifier (required for `registerRoutes` flow)
1068
997
  * @returns `{ authUrl, state, codeVerifier }`
1069
998
  *
1070
999
  * @example
@@ -1078,7 +1007,7 @@ export class Soma {
1078
1007
  */
1079
1008
  async getGarminAuthUrl(
1080
1009
  ctx: ActionCtx,
1081
- opts: { redirectUri?: string; userId?: string },
1010
+ opts: { userId: string; redirectUri?: string },
1082
1011
  ) {
1083
1012
  const config = this.requireGarminConfig();
1084
1013
  return await ctx.runAction(this.component.garmin.public.getGarminAuthUrl, {
@@ -1088,73 +1017,208 @@ export class Soma {
1088
1017
  });
1089
1018
  }
1090
1019
 
1091
- /**
1092
- * Handle the Garmin OAuth 2.0 callback (manual flow).
1093
- *
1094
- * Exchanges the authorization code for tokens, creates/reactivates the
1095
- * Soma connection, stores tokens securely, and syncs the last 30 days
1096
- * of all data types.
1097
- *
1098
- * Call this from your OAuth callback endpoint after receiving the `code`
1099
- * query parameter from Garmin.
1100
- *
1101
- * @param ctx - Action context from the host app
1102
- * @param args.userId - The host app's user identifier
1103
- * @param args.code - The authorization code from the callback
1104
- * @param args.codeVerifier - The PKCE code verifier from Step 1
1105
- * @param args.redirectUri - The redirect URI used in the authorization request
1106
- * @returns `{ connectionId, synced, errors }`
1107
- *
1108
- * @example
1109
- * ```ts
1110
- * export const handleGarminCallback = action({
1111
- * args: {
1112
- * userId: v.string(),
1113
- * code: v.string(),
1114
- * codeVerifier: v.string(),
1115
- * },
1116
- * handler: async (ctx, args) => {
1117
- * return await soma.connectGarmin(ctx, args);
1118
- * },
1119
- * });
1120
- * ```
1121
- */
1122
- async connectGarmin(
1020
+ async pullGarminActivities(
1123
1021
  ctx: ActionCtx,
1124
1022
  args: {
1125
1023
  userId: string;
1126
- code: string;
1127
- codeVerifier: string;
1128
- redirectUri?: string;
1024
+ startTimeInSeconds?: number;
1025
+ endTimeInSeconds?: number;
1129
1026
  },
1130
1027
  ) {
1131
1028
  const config = this.requireGarminConfig();
1132
- return await ctx.runAction(this.component.garmin.public.connectGarmin, {
1029
+ return await ctx.runAction(this.component.garmin.public.pullActivities, {
1133
1030
  ...args,
1134
1031
  clientId: config.clientId,
1135
1032
  clientSecret: config.clientSecret,
1136
1033
  });
1137
1034
  }
1138
1035
 
1139
- /**
1140
- * Complete a Garmin OAuth 2.0 flow using stored pending state.
1141
- *
1142
- * This is called automatically by the `registerRoutes` callback handler.
1143
- * It looks up the pending OAuth state stored during `getGarminAuthUrl`,
1144
- * exchanges for tokens, creates the connection, and syncs data.
1145
- *
1146
- * @param ctx - Action context from the host app
1147
- * @param args.code - The authorization code from the callback query params
1148
- * @param args.state - The state parameter from the callback query params
1149
- * @param args.redirectUri - The redirect URI used in the authorization request
1150
- * @returns `{ connectionId, synced, errors }`
1151
- */
1152
- async completeGarminOAuth(
1036
+ async pullGarminDailies(
1037
+ ctx: ActionCtx,
1038
+ args: {
1039
+ userId: string;
1040
+ startTimeInSeconds?: number;
1041
+ endTimeInSeconds?: number;
1042
+ },
1043
+ ) {
1044
+ const config = this.requireGarminConfig();
1045
+ return await ctx.runAction(this.component.garmin.public.pullDailies, {
1046
+ ...args,
1047
+ clientId: config.clientId,
1048
+ clientSecret: config.clientSecret,
1049
+ });
1050
+ }
1051
+
1052
+ async pullGarminSleep(
1153
1053
  ctx: ActionCtx,
1154
- args: { code: string; state: string; redirectUri?: string },
1054
+ args: {
1055
+ userId: string;
1056
+ startTimeInSeconds?: number;
1057
+ endTimeInSeconds?: number;
1058
+ },
1155
1059
  ) {
1156
1060
  const config = this.requireGarminConfig();
1157
- return await ctx.runAction(this.component.garmin.public.completeGarminOAuth, {
1061
+ return await ctx.runAction(this.component.garmin.public.pullSleep, {
1062
+ ...args,
1063
+ clientId: config.clientId,
1064
+ clientSecret: config.clientSecret,
1065
+ });
1066
+ }
1067
+
1068
+ async pullGarminBody(
1069
+ ctx: ActionCtx,
1070
+ args: {
1071
+ userId: string;
1072
+ startTimeInSeconds?: number;
1073
+ endTimeInSeconds?: number;
1074
+ },
1075
+ ) {
1076
+ const config = this.requireGarminConfig();
1077
+ return await ctx.runAction(this.component.garmin.public.pullBody, {
1078
+ ...args,
1079
+ clientId: config.clientId,
1080
+ clientSecret: config.clientSecret,
1081
+ });
1082
+ }
1083
+
1084
+ async pullGarminMenstruation(
1085
+ ctx: ActionCtx,
1086
+ args: {
1087
+ userId: string;
1088
+ startTimeInSeconds?: number;
1089
+ endTimeInSeconds?: number;
1090
+ },
1091
+ ) {
1092
+ const config = this.requireGarminConfig();
1093
+ return await ctx.runAction(this.component.garmin.public.pullMenstruation, {
1094
+ ...args,
1095
+ clientId: config.clientId,
1096
+ clientSecret: config.clientSecret,
1097
+ });
1098
+ }
1099
+
1100
+ async pullGarminBloodPressures(
1101
+ ctx: ActionCtx,
1102
+ args: {
1103
+ userId: string;
1104
+ startTimeInSeconds?: number;
1105
+ endTimeInSeconds?: number;
1106
+ },
1107
+ ) {
1108
+ const config = this.requireGarminConfig();
1109
+ return await ctx.runAction(this.component.garmin.public.pullBloodPressures, {
1110
+ ...args,
1111
+ clientId: config.clientId,
1112
+ clientSecret: config.clientSecret,
1113
+ });
1114
+ }
1115
+
1116
+ async pullGarminSkinTemperature(
1117
+ ctx: ActionCtx,
1118
+ args: {
1119
+ userId: string;
1120
+ startTimeInSeconds?: number;
1121
+ endTimeInSeconds?: number;
1122
+ },
1123
+ ) {
1124
+ const config = this.requireGarminConfig();
1125
+ return await ctx.runAction(this.component.garmin.public.pullSkinTemperature, {
1126
+ ...args,
1127
+ clientId: config.clientId,
1128
+ clientSecret: config.clientSecret,
1129
+ });
1130
+ }
1131
+
1132
+ async pullGarminUserMetrics(
1133
+ ctx: ActionCtx,
1134
+ args: {
1135
+ userId: string;
1136
+ startTimeInSeconds?: number;
1137
+ endTimeInSeconds?: number;
1138
+ },
1139
+ ) {
1140
+ const config = this.requireGarminConfig();
1141
+ return await ctx.runAction(this.component.garmin.public.pullUserMetrics, {
1142
+ ...args,
1143
+ clientId: config.clientId,
1144
+ clientSecret: config.clientSecret,
1145
+ });
1146
+ }
1147
+
1148
+ async pullGarminHRV(
1149
+ ctx: ActionCtx,
1150
+ args: {
1151
+ userId: string;
1152
+ startTimeInSeconds?: number;
1153
+ endTimeInSeconds?: number;
1154
+ },
1155
+ ) {
1156
+ const config = this.requireGarminConfig();
1157
+ return await ctx.runAction(this.component.garmin.public.pullHRV, {
1158
+ ...args,
1159
+ clientId: config.clientId,
1160
+ clientSecret: config.clientSecret,
1161
+ });
1162
+ }
1163
+
1164
+ async pullGarminStressDetails(
1165
+ ctx: ActionCtx,
1166
+ args: {
1167
+ userId: string;
1168
+ startTimeInSeconds?: number;
1169
+ endTimeInSeconds?: number;
1170
+ },
1171
+ ) {
1172
+ const config = this.requireGarminConfig();
1173
+ return await ctx.runAction(this.component.garmin.public.pullStressDetails, {
1174
+ ...args,
1175
+ clientId: config.clientId,
1176
+ clientSecret: config.clientSecret,
1177
+ });
1178
+ }
1179
+
1180
+ async pullGarminPulseOx(
1181
+ ctx: ActionCtx,
1182
+ args: {
1183
+ userId: string;
1184
+ startTimeInSeconds?: number;
1185
+ endTimeInSeconds?: number;
1186
+ },
1187
+ ) {
1188
+ const config = this.requireGarminConfig();
1189
+ return await ctx.runAction(this.component.garmin.public.pullPulseOx, {
1190
+ ...args,
1191
+ clientId: config.clientId,
1192
+ clientSecret: config.clientSecret,
1193
+ });
1194
+ }
1195
+
1196
+ async pullGarminRespiration(
1197
+ ctx: ActionCtx,
1198
+ args: {
1199
+ userId: string;
1200
+ startTimeInSeconds?: number;
1201
+ endTimeInSeconds?: number;
1202
+ },
1203
+ ) {
1204
+ const config = this.requireGarminConfig();
1205
+ return await ctx.runAction(this.component.garmin.public.pullRespiration, {
1206
+ ...args,
1207
+ clientId: config.clientId,
1208
+ clientSecret: config.clientSecret,
1209
+ });
1210
+ }
1211
+
1212
+ async pullGarminAll(
1213
+ ctx: ActionCtx,
1214
+ args: {
1215
+ userId: string;
1216
+ startTimeInSeconds?: number;
1217
+ endTimeInSeconds?: number;
1218
+ },
1219
+ ) {
1220
+ const config = this.requireGarminConfig();
1221
+ return await ctx.runAction(this.component.garmin.public.pullAll, {
1158
1222
  ...args,
1159
1223
  clientId: config.clientId,
1160
1224
  clientSecret: config.clientSecret,
@@ -1282,7 +1346,7 @@ export interface StravaOAuthOptions {
1282
1346
  clientSecret?: string;
1283
1347
  /** URL to redirect the user to after a successful connection. */
1284
1348
  redirectTo?: string;
1285
- /** Called after Strava OAuth completes and initial data sync finishes. */
1349
+ /** Called after Strava OAuth completes and the connection is established. */
1286
1350
  onComplete?: (
1287
1351
  ctx: GenericActionCtx<GenericDataModel>,
1288
1352
  event: StravaConnectEvent,
@@ -1291,24 +1355,20 @@ export interface StravaOAuthOptions {
1291
1355
 
1292
1356
  // ─── Strava Callback Event Types ────────────────────────────────────────────
1293
1357
 
1294
- /** Data passed to `onComplete` after Strava OAuth + initial sync. */
1358
+ /** Data passed to `onComplete` after Strava OAuth completes. */
1295
1359
  export interface StravaConnectEvent {
1296
1360
  provider: "STRAVA";
1297
1361
  userId: string;
1298
1362
  connectionId: string;
1299
- synced: Record<string, number>;
1300
- errors: Array<{ type: string; id: string; error: string }>;
1301
1363
  }
1302
1364
 
1303
1365
  // ─── Garmin Callback Event Types ─────────────────────────────────────────────
1304
1366
 
1305
- /** Data passed to `oauth.onComplete` after Garmin OAuth + initial sync. */
1367
+ /** Data passed to `oauth.onComplete` after Garmin OAuth completes. */
1306
1368
  export interface GarminConnectEvent {
1307
1369
  provider: "GARMIN";
1308
1370
  userId: string;
1309
1371
  connectionId: string;
1310
- synced: Record<string, number>;
1311
- errors: Array<{ type: string; id: string; error: string }>;
1312
1372
  }
1313
1373
 
1314
1374
  /** Data passed to webhook `events` handlers and `onEvent` after data ingestion. */
@@ -1331,7 +1391,7 @@ export interface GarminOAuthOptions {
1331
1391
  clientSecret?: string;
1332
1392
  /** URL to redirect the user to after a successful connection. */
1333
1393
  redirectTo?: string;
1334
- /** Called after Garmin OAuth completes and initial data sync finishes. */
1394
+ /** Called after Garmin OAuth completes and the connection is established. */
1335
1395
  onComplete?: (
1336
1396
  ctx: GenericActionCtx<GenericDataModel>,
1337
1397
  event: GarminConnectEvent,
@@ -1378,8 +1438,8 @@ export interface RegisterRoutesOptions {
1378
1438
  *
1379
1439
  * Call this from your `convex/http.ts` to set up the callback endpoints
1380
1440
  * that Strava and Garmin redirect to after user authorization. The handlers
1381
- * complete the OAuth exchange, create the connection, and sync data
1382
- * automatically.
1441
+ * complete the OAuth exchange, create the connection, and store tokens.
1442
+ * The host app is responsible for calling sync separately.
1383
1443
  *
1384
1444
  * When called with no `opts`, registers both Strava and Garmin routes with
1385
1445
  * default paths and credentials from environment variables. When `opts` is
@@ -1410,7 +1470,7 @@ export interface RegisterRoutesOptions {
1410
1470
  * path: "/oauth/strava/callback",
1411
1471
  * redirectTo: "https://myapp.com/settings",
1412
1472
  * onComplete: async (ctx, event) => {
1413
- * // Runs after OAuth + initial sync completes
1473
+ * // Runs after OAuth completes and connection is established
1414
1474
  * await ctx.runMutation(internal.users.markConnected, {
1415
1475
  * userId: event.userId,
1416
1476
  * provider: event.provider,
@@ -1422,7 +1482,7 @@ export interface RegisterRoutesOptions {
1422
1482
  * oauth: {
1423
1483
  * redirectTo: "https://myapp.com/settings",
1424
1484
  * onComplete: async (ctx, event) => {
1425
- * // Runs after OAuth + initial sync completes
1485
+ * // Runs after OAuth completes and connection is established
1426
1486
  * await ctx.runMutation(internal.users.markConnected, {
1427
1487
  * userId: event.userId,
1428
1488
  * provider: event.provider,
@@ -1491,8 +1551,6 @@ export function registerRoutes(
1491
1551
  let result: {
1492
1552
  connectionId: string;
1493
1553
  userId: string;
1494
- synced: Record<string, number>;
1495
- errors: Array<{ type: string; id: string; error: string }>;
1496
1554
  };
1497
1555
  try {
1498
1556
  result = await ctx.runAction(component.strava.public.completeStravaOAuth, {
@@ -1515,8 +1573,6 @@ export function registerRoutes(
1515
1573
  provider: "STRAVA",
1516
1574
  userId: result.userId,
1517
1575
  connectionId: result.connectionId,
1518
- synced: result.synced,
1519
- errors: result.errors,
1520
1576
  });
1521
1577
  } catch (callbackError) {
1522
1578
  console.error(
@@ -1582,8 +1638,6 @@ export function registerRoutes(
1582
1638
  let result: {
1583
1639
  connectionId: string;
1584
1640
  userId: string;
1585
- synced: Record<string, number>;
1586
- errors: Array<{ type: string; id: string; error: string }>;
1587
1641
  };
1588
1642
  try {
1589
1643
  result = await ctx.runAction(component.garmin.public.completeGarminOAuth, {
@@ -1606,8 +1660,6 @@ export function registerRoutes(
1606
1660
  provider: "GARMIN",
1607
1661
  userId: result.userId,
1608
1662
  connectionId: result.connectionId,
1609
- synced: result.synced,
1610
- errors: result.errors,
1611
1663
  });
1612
1664
  } catch (callbackError) {
1613
1665
  console.error(