@nativesquare/soma 0.9.3 → 0.10.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 (202) hide show
  1. package/dist/client/index.d.ts +96 -33
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +80 -35
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/component/_generated/api.d.ts +18 -0
  6. package/dist/component/_generated/api.d.ts.map +1 -1
  7. package/dist/component/_generated/api.js.map +1 -1
  8. package/dist/component/_generated/component.d.ts +43 -9
  9. package/dist/component/_generated/component.d.ts.map +1 -1
  10. package/dist/component/garmin/auth.d.ts +0 -4
  11. package/dist/component/garmin/auth.d.ts.map +1 -1
  12. package/dist/component/garmin/auth.js +0 -8
  13. package/dist/component/garmin/auth.js.map +1 -1
  14. package/dist/component/garmin/private.d.ts +20 -3
  15. package/dist/component/garmin/private.d.ts.map +1 -1
  16. package/dist/component/garmin/private.js +17 -26
  17. package/dist/component/garmin/private.js.map +1 -1
  18. package/dist/component/garmin/public.d.ts +4 -4
  19. package/dist/component/garmin/public.d.ts.map +1 -1
  20. package/dist/component/garmin/public.js +6 -1
  21. package/dist/component/garmin/public.js.map +1 -1
  22. package/dist/component/garmin/webhooks.d.ts +4 -0
  23. package/dist/component/garmin/webhooks.d.ts.map +1 -1
  24. package/dist/component/garmin/webhooks.js +23 -18
  25. package/dist/component/garmin/webhooks.js.map +1 -1
  26. package/dist/component/schema.d.ts +2 -2
  27. package/dist/component/schema.d.ts.map +1 -1
  28. package/dist/component/schema.js +5 -3
  29. package/dist/component/schema.js.map +1 -1
  30. package/dist/{strava → component/strava}/auth.d.ts +15 -48
  31. package/dist/component/strava/auth.d.ts.map +1 -0
  32. package/dist/{strava → component/strava}/auth.js +4 -39
  33. package/dist/component/strava/auth.js.map +1 -0
  34. package/dist/component/strava/client.d.ts +8 -0
  35. package/dist/component/strava/client.d.ts.map +1 -0
  36. package/dist/component/strava/client.js +18 -0
  37. package/dist/component/strava/client.js.map +1 -0
  38. package/dist/component/strava/private.d.ts +19 -0
  39. package/dist/component/strava/private.d.ts.map +1 -1
  40. package/dist/component/strava/private.js +52 -2
  41. package/dist/component/strava/private.js.map +1 -1
  42. package/dist/component/strava/public.d.ts +87 -12
  43. package/dist/component/strava/public.d.ts.map +1 -1
  44. package/dist/component/strava/public.js +218 -92
  45. package/dist/component/strava/public.js.map +1 -1
  46. package/dist/component/strava/transform/activity.d.ts +19 -0
  47. package/dist/component/strava/transform/activity.d.ts.map +1 -0
  48. package/dist/{strava → component/strava/transform}/activity.js +21 -41
  49. package/dist/component/strava/transform/activity.js.map +1 -0
  50. package/dist/{strava → component/strava/transform}/athlete.d.ts +4 -10
  51. package/dist/component/strava/transform/athlete.d.ts.map +1 -0
  52. package/dist/{strava → component/strava/transform}/athlete.js +2 -8
  53. package/dist/component/strava/transform/athlete.js.map +1 -0
  54. package/dist/component/strava/transform/maps/sportType.d.ts +7 -0
  55. package/dist/component/strava/transform/maps/sportType.d.ts.map +1 -0
  56. package/dist/{strava/maps/sport-type.js → component/strava/transform/maps/sportType.js} +4 -2
  57. package/dist/component/strava/transform/maps/sportType.js.map +1 -0
  58. package/dist/component/strava/types/stravaApi/client/client.gen.d.ts +3 -0
  59. package/dist/component/strava/types/stravaApi/client/client.gen.d.ts.map +1 -0
  60. package/dist/component/strava/types/stravaApi/client/client.gen.js +236 -0
  61. package/dist/component/strava/types/stravaApi/client/client.gen.js.map +1 -0
  62. package/dist/component/strava/types/stravaApi/client/index.d.ts +9 -0
  63. package/dist/component/strava/types/stravaApi/client/index.d.ts.map +1 -0
  64. package/dist/component/strava/types/stravaApi/client/index.js +7 -0
  65. package/dist/component/strava/types/stravaApi/client/index.js.map +1 -0
  66. package/dist/component/strava/types/stravaApi/client/types.gen.d.ts +118 -0
  67. package/dist/component/strava/types/stravaApi/client/types.gen.d.ts.map +1 -0
  68. package/dist/component/strava/types/stravaApi/client/types.gen.js +3 -0
  69. package/dist/component/strava/types/stravaApi/client/types.gen.js.map +1 -0
  70. package/dist/component/strava/types/stravaApi/client/utils.gen.d.ts +34 -0
  71. package/dist/component/strava/types/stravaApi/client/utils.gen.d.ts.map +1 -0
  72. package/dist/component/strava/types/stravaApi/client/utils.gen.js +229 -0
  73. package/dist/component/strava/types/stravaApi/client/utils.gen.js.map +1 -0
  74. package/dist/component/strava/types/stravaApi/client.gen.d.ts +13 -0
  75. package/dist/component/strava/types/stravaApi/client.gen.d.ts.map +1 -0
  76. package/dist/component/strava/types/stravaApi/client.gen.js +4 -0
  77. package/dist/component/strava/types/stravaApi/client.gen.js.map +1 -0
  78. package/dist/component/strava/types/stravaApi/core/auth.gen.d.ts +19 -0
  79. package/dist/component/strava/types/stravaApi/core/auth.gen.d.ts.map +1 -0
  80. package/dist/component/strava/types/stravaApi/core/auth.gen.js +15 -0
  81. package/dist/component/strava/types/stravaApi/core/auth.gen.js.map +1 -0
  82. package/dist/component/strava/types/stravaApi/core/bodySerializer.gen.d.ts +26 -0
  83. package/dist/component/strava/types/stravaApi/core/bodySerializer.gen.d.ts.map +1 -0
  84. package/dist/component/strava/types/stravaApi/core/bodySerializer.gen.js +58 -0
  85. package/dist/component/strava/types/stravaApi/core/bodySerializer.gen.js.map +1 -0
  86. package/dist/component/strava/types/stravaApi/core/params.gen.d.ts +44 -0
  87. package/dist/component/strava/types/stravaApi/core/params.gen.d.ts.map +1 -0
  88. package/dist/component/strava/types/stravaApi/core/params.gen.js +101 -0
  89. package/dist/component/strava/types/stravaApi/core/params.gen.js.map +1 -0
  90. package/dist/component/strava/types/stravaApi/core/pathSerializer.gen.d.ts +34 -0
  91. package/dist/component/strava/types/stravaApi/core/pathSerializer.gen.d.ts.map +1 -0
  92. package/dist/component/strava/types/stravaApi/core/pathSerializer.gen.js +107 -0
  93. package/dist/component/strava/types/stravaApi/core/pathSerializer.gen.js.map +1 -0
  94. package/dist/component/strava/types/stravaApi/core/queryKeySerializer.gen.d.ts +19 -0
  95. package/dist/component/strava/types/stravaApi/core/queryKeySerializer.gen.d.ts.map +1 -0
  96. package/dist/component/strava/types/stravaApi/core/queryKeySerializer.gen.js +93 -0
  97. package/dist/component/strava/types/stravaApi/core/queryKeySerializer.gen.js.map +1 -0
  98. package/dist/component/strava/types/stravaApi/core/serverSentEvents.gen.d.ts +72 -0
  99. package/dist/component/strava/types/stravaApi/core/serverSentEvents.gen.d.ts.map +1 -0
  100. package/dist/component/strava/types/stravaApi/core/serverSentEvents.gen.js +134 -0
  101. package/dist/component/strava/types/stravaApi/core/serverSentEvents.gen.js.map +1 -0
  102. package/dist/component/strava/types/stravaApi/core/types.gen.d.ts +79 -0
  103. package/dist/component/strava/types/stravaApi/core/types.gen.d.ts.map +1 -0
  104. package/dist/component/strava/types/stravaApi/core/types.gen.js +3 -0
  105. package/dist/component/strava/types/stravaApi/core/types.gen.js.map +1 -0
  106. package/dist/component/strava/types/stravaApi/core/utils.gen.d.ts +20 -0
  107. package/dist/component/strava/types/stravaApi/core/utils.gen.d.ts.map +1 -0
  108. package/dist/component/strava/types/stravaApi/core/utils.gen.js +88 -0
  109. package/dist/component/strava/types/stravaApi/core/utils.gen.js.map +1 -0
  110. package/dist/component/strava/types/stravaApi/index.d.ts +3 -0
  111. package/dist/component/strava/types/stravaApi/index.d.ts.map +1 -0
  112. package/dist/component/strava/types/stravaApi/index.js +3 -0
  113. package/dist/component/strava/types/stravaApi/index.js.map +1 -0
  114. package/dist/component/strava/types/stravaApi/sdk.gen.d.ts +224 -0
  115. package/dist/component/strava/types/stravaApi/sdk.gen.d.ts.map +1 -0
  116. package/dist/component/strava/types/stravaApi/sdk.gen.js +361 -0
  117. package/dist/component/strava/types/stravaApi/sdk.gen.js.map +1 -0
  118. package/dist/component/strava/types/stravaApi/types.gen.d.ts +2209 -0
  119. package/dist/component/strava/types/stravaApi/types.gen.d.ts.map +1 -0
  120. package/dist/component/strava/types/stravaApi/types.gen.js +3 -0
  121. package/dist/component/strava/types/stravaApi/types.gen.js.map +1 -0
  122. package/dist/component/strava/types/stravaApi/zod.gen.d.ts +5332 -0
  123. package/dist/component/strava/types/stravaApi/zod.gen.d.ts.map +1 -0
  124. package/dist/component/strava/types/stravaApi/zod.gen.js +1009 -0
  125. package/dist/component/strava/types/stravaApi/zod.gen.js.map +1 -0
  126. package/dist/component/strava/utils.d.ts +15 -0
  127. package/dist/component/strava/utils.d.ts.map +1 -0
  128. package/dist/component/strava/utils.js +36 -0
  129. package/dist/component/strava/utils.js.map +1 -0
  130. package/dist/component/utils.d.ts +5 -0
  131. package/dist/component/utils.d.ts.map +1 -0
  132. package/dist/component/utils.js +11 -0
  133. package/dist/component/utils.js.map +1 -0
  134. package/package.json +131 -130
  135. package/src/client/index.ts +121 -52
  136. package/src/component/_generated/api.ts +18 -0
  137. package/src/component/_generated/component.ts +44 -11
  138. package/src/component/garmin/auth.ts +0 -9
  139. package/src/component/garmin/private.ts +0 -12
  140. package/src/component/garmin/public.ts +8 -1
  141. package/src/component/schema.ts +5 -3
  142. package/src/{strava → component/strava}/auth.ts +143 -185
  143. package/src/component/strava/client.ts +20 -0
  144. package/src/component/strava/private.ts +147 -89
  145. package/src/component/strava/public.ts +268 -110
  146. package/src/{strava → component/strava/transform}/activity.ts +256 -276
  147. package/src/{strava → component/strava/transform}/athlete.ts +41 -47
  148. package/src/{strava/maps/sport-type.ts → component/strava/transform/maps/sportType.ts} +100 -99
  149. package/src/component/strava/types/specs/strava-api.json +4796 -0
  150. package/src/component/strava/types/stravaApi/client/client.gen.ts +290 -0
  151. package/src/component/strava/types/stravaApi/client/index.ts +25 -0
  152. package/src/component/strava/types/stravaApi/client/types.gen.ts +214 -0
  153. package/src/component/strava/types/stravaApi/client/utils.gen.ts +316 -0
  154. package/src/component/strava/types/stravaApi/client.gen.ts +16 -0
  155. package/src/component/strava/types/stravaApi/core/auth.gen.ts +41 -0
  156. package/src/component/strava/types/stravaApi/core/bodySerializer.gen.ts +82 -0
  157. package/src/component/strava/types/stravaApi/core/params.gen.ts +169 -0
  158. package/src/component/strava/types/stravaApi/core/pathSerializer.gen.ts +171 -0
  159. package/src/component/strava/types/stravaApi/core/queryKeySerializer.gen.ts +117 -0
  160. package/src/component/strava/types/stravaApi/core/serverSentEvents.gen.ts +243 -0
  161. package/src/component/strava/types/stravaApi/core/types.gen.ts +104 -0
  162. package/src/component/strava/types/stravaApi/core/utils.gen.ts +140 -0
  163. package/src/component/strava/types/stravaApi/index.ts +4 -0
  164. package/src/component/strava/types/stravaApi/sdk.gen.ts +410 -0
  165. package/src/component/strava/types/stravaApi/types.gen.ts +2435 -0
  166. package/src/component/strava/types/stravaApi/zod.gen.ts +1132 -0
  167. package/src/component/strava/utils.ts +52 -0
  168. package/src/component/utils.ts +11 -0
  169. package/dist/strava/activity.d.ts +0 -121
  170. package/dist/strava/activity.d.ts.map +0 -1
  171. package/dist/strava/activity.js.map +0 -1
  172. package/dist/strava/athlete.d.ts.map +0 -1
  173. package/dist/strava/athlete.js.map +0 -1
  174. package/dist/strava/auth.d.ts.map +0 -1
  175. package/dist/strava/auth.js.map +0 -1
  176. package/dist/strava/client.d.ts +0 -93
  177. package/dist/strava/client.d.ts.map +0 -1
  178. package/dist/strava/client.js +0 -158
  179. package/dist/strava/client.js.map +0 -1
  180. package/dist/strava/index.d.ts +0 -13
  181. package/dist/strava/index.d.ts.map +0 -1
  182. package/dist/strava/index.js +0 -17
  183. package/dist/strava/index.js.map +0 -1
  184. package/dist/strava/maps/sport-type.d.ts +0 -7
  185. package/dist/strava/maps/sport-type.d.ts.map +0 -1
  186. package/dist/strava/maps/sport-type.js.map +0 -1
  187. package/dist/strava/sync.d.ts +0 -104
  188. package/dist/strava/sync.d.ts.map +0 -1
  189. package/dist/strava/sync.js +0 -87
  190. package/dist/strava/sync.js.map +0 -1
  191. package/dist/strava/types.d.ts +0 -266
  192. package/dist/strava/types.d.ts.map +0 -1
  193. package/dist/strava/types.js +0 -8
  194. package/dist/strava/types.js.map +0 -1
  195. package/src/strava/activity.test.ts +0 -415
  196. package/src/strava/athlete.test.ts +0 -139
  197. package/src/strava/auth.test.ts +0 -78
  198. package/src/strava/client.ts +0 -212
  199. package/src/strava/index.ts +0 -54
  200. package/src/strava/maps/sport-type.test.ts +0 -69
  201. package/src/strava/sync.ts +0 -168
  202. package/src/strava/types.ts +0 -361
@@ -1,7 +1,57 @@
1
- // ─── Internal Token CRUD ─────────────────────────────────────────────────────
2
- // These are only callable from within the component (actions in this file).
1
+ // ─── Internal Token & Pending OAuth CRUD ─────────────────────────────────────
2
+ // Called only from strava/public.ts. Not exposed to the host app.
3
3
  import { v } from "convex/values";
4
4
  import { internalMutation, internalQuery, } from "../_generated/server";
5
+ // ─── Internal Pending OAuth CRUD ─────────────────────────────────────────────
6
+ // Temporary storage for in-progress Strava OAuth flows.
7
+ // Bridges getStravaAuthUrl and completeStravaOAuth.
8
+ export const storePendingOAuth = internalMutation({
9
+ args: {
10
+ provider: v.string(),
11
+ state: v.string(),
12
+ userId: v.string(),
13
+ },
14
+ returns: v.null(),
15
+ handler: async (ctx, args) => {
16
+ await ctx.db.insert("pendingOAuth", {
17
+ ...args,
18
+ createdAt: Date.now(),
19
+ });
20
+ return null;
21
+ },
22
+ });
23
+ export const getPendingOAuth = internalQuery({
24
+ args: { state: v.string() },
25
+ returns: v.union(v.object({
26
+ _id: v.id("pendingOAuth"),
27
+ _creationTime: v.number(),
28
+ provider: v.string(),
29
+ state: v.string(),
30
+ userId: v.string(),
31
+ createdAt: v.number(),
32
+ }), v.null()),
33
+ handler: async (ctx, args) => {
34
+ return await ctx.db
35
+ .query("pendingOAuth")
36
+ .withIndex("by_state", (q) => q.eq("state", args.state))
37
+ .first();
38
+ },
39
+ });
40
+ export const deletePendingOAuth = internalMutation({
41
+ args: { state: v.string() },
42
+ returns: v.null(),
43
+ handler: async (ctx, args) => {
44
+ const pending = await ctx.db
45
+ .query("pendingOAuth")
46
+ .withIndex("by_state", (q) => q.eq("state", args.state))
47
+ .first();
48
+ if (pending) {
49
+ await ctx.db.delete(pending._id);
50
+ }
51
+ return null;
52
+ },
53
+ });
54
+ // ─── Internal Token CRUD ─────────────────────────────────────────────────────
5
55
  /**
6
56
  * Store or update OAuth tokens for a connection.
7
57
  * Upserts by connectionId — one token record per connection.
@@ -1 +1 @@
1
- {"version":3,"file":"private.js","sourceRoot":"","sources":["../../../src/component/strava/private.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"private.js","sourceRoot":"","sources":["../../../src/component/strava/private.ts"],"names":[],"mappings":"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"}
@@ -1,27 +1,78 @@
1
1
  import type { Id } from "../_generated/dataModel";
2
2
  /**
3
- * Full Strava OAuth callback handler.
3
+ * Generate a Strava OAuth authorization URL.
4
+ *
5
+ * If `userId` is provided, the state parameter is stored in the component's
6
+ * `pendingOAuth` table so that `completeStravaOAuth` can look it up
7
+ * automatically when the callback fires. This is the recommended flow
8
+ * when using `registerRoutes`.
9
+ *
10
+ * If `userId` is omitted, the host app must store the returned `state`
11
+ * itself and pass the userId to `connectStrava` manually.
12
+ */
13
+ export declare const getStravaAuthUrl: import("convex/server").RegisteredAction<"public", {
14
+ userId?: string | undefined;
15
+ scope?: string | undefined;
16
+ clientId: string;
17
+ redirectUri: string;
18
+ }, Promise<{
19
+ authUrl: string;
20
+ state: string;
21
+ }>>;
22
+ /**
23
+ * Full Strava OAuth callback handler (manual flow).
4
24
  *
5
25
  * Exchanges the authorization code for tokens, creates/reactivates the
6
26
  * Soma connection, stores tokens securely, syncs the athlete profile,
7
27
  * and syncs all activities.
8
28
  *
29
+ * Used when the host app handles the callback itself and passes the
30
+ * userId directly (analogous to `connectGarmin`).
31
+ *
9
32
  * Returns `{ connectionId, synced, errors }`.
10
33
  */
11
34
  export declare const connectStrava: import("convex/server").RegisteredAction<"public", {
12
- baseUrl?: string | undefined;
13
- includeStreams?: boolean | undefined;
14
35
  userId: string;
15
36
  clientId: string;
16
37
  clientSecret: string;
17
38
  code: string;
18
39
  }, Promise<{
19
40
  connectionId: Id<"connections">;
20
- synced: number;
21
- errors: {
22
- activityId: number;
41
+ synced: {
42
+ athletes: number;
43
+ activities: number;
44
+ };
45
+ errors: Array<{
46
+ type: string;
47
+ id: string;
23
48
  error: string;
24
- }[];
49
+ }>;
50
+ }>>;
51
+ /**
52
+ * Complete a Strava OAuth flow using stored pending state.
53
+ *
54
+ * Used by `registerRoutes` — the callback handler calls this with the
55
+ * `code` and `state` from the redirect. The action looks up the pending
56
+ * state (userId) stored during `getStravaAuthUrl`, exchanges for tokens,
57
+ * creates the connection, syncs data, and cleans up the pending entry.
58
+ */
59
+ export declare const completeStravaOAuth: import("convex/server").RegisteredAction<"public", {
60
+ state: string;
61
+ clientId: string;
62
+ clientSecret: string;
63
+ code: string;
64
+ }, Promise<{
65
+ connectionId: Id<"connections">;
66
+ userId: string;
67
+ synced: {
68
+ athletes: number;
69
+ activities: number;
70
+ };
71
+ errors: Array<{
72
+ type: string;
73
+ id: string;
74
+ error: string;
75
+ }>;
25
76
  }>>;
26
77
  /**
27
78
  * Incremental Strava sync for an already-connected user.
@@ -32,16 +83,41 @@ export declare const connectStrava: import("convex/server").RegisteredAction<"pu
32
83
  * Returns `{ synced, errors }`.
33
84
  */
34
85
  export declare const syncStrava: import("convex/server").RegisteredAction<"public", {
35
- baseUrl?: string | undefined;
36
- includeStreams?: boolean | undefined;
37
86
  after?: number | undefined;
38
87
  userId: string;
39
88
  clientId: string;
40
89
  clientSecret: string;
41
90
  }, Promise<{
42
- synced: number;
91
+ synced: {
92
+ athletes: number;
93
+ activities: number;
94
+ };
95
+ errors: Array<{
96
+ type: string;
97
+ id: string;
98
+ error: string;
99
+ }>;
100
+ }>>;
101
+ /**
102
+ * Fetch and ingest all Strava data types for a connected user.
103
+ *
104
+ * Called by the public actions (connectStrava, completeStravaOAuth, syncStrava)
105
+ * after obtaining a valid access token.
106
+ */
107
+ export declare const syncAllTypes: import("convex/server").RegisteredAction<"public", {
108
+ after?: number | undefined;
109
+ before?: number | undefined;
110
+ connectionId: import("convex/values").GenericId<"connections">;
111
+ userId: string;
112
+ accessToken: string;
113
+ }, Promise<{
114
+ synced: {
115
+ athletes: number;
116
+ activities: number;
117
+ };
43
118
  errors: {
44
- activityId: number;
119
+ type: string;
120
+ id: string;
45
121
  error: string;
46
122
  }[];
47
123
  }>>;
@@ -52,7 +128,6 @@ export declare const syncStrava: import("convex/server").RegisteredAction<"publi
52
128
  * and sets the connection to inactive.
53
129
  */
54
130
  export declare const disconnectStrava: import("convex/server").RegisteredAction<"public", {
55
- baseUrl?: string | undefined;
56
131
  userId: string;
57
132
  clientId: string;
58
133
  clientSecret: string;
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/component/strava/public.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAO,EAAE,EAAE,MAAM,yBAAyB,CAAC;AAWvD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;oBAiEY,MAAM;eAAS,MAAM;;GAqCzD,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;oBAkGe,MAAM;eAAS,MAAM;;GAqCzD,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;;;;;iBAyD3B,CAAC"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/component/strava/public.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAO,EAAE,EAAE,MAAM,yBAAyB,CAAC;AAmBvD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;GA2B3B,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa;;;;;;kBAeR,EAAE,CAAC,aAAa,CAAC;YACvB;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;YACxC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;GA+C5D,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB;;;;;;kBAgBd,EAAE,CAAC,aAAa,CAAC;YACvB,MAAM;YACN;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;YACxC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;GAsE5D,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU;;;;;;YAcX;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;YACxC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;GA+E5D,CAAC;AAIH;;;;;GAKG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;cAaO,MAAM;YAAM,MAAM;eAAS,MAAM;;GAmE/D,CAAC;AAIH;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;;;;iBAoD3B,CAAC"}
@@ -5,33 +5,73 @@
5
5
  import { v } from "convex/values";
6
6
  import { action } from "../_generated/server";
7
7
  import { api, internal } from "../_generated/api";
8
- import { StravaClient } from "../../strava/client";
9
- import { exchangeCode, refreshToken as refreshStravaToken, } from "../../strava/auth";
10
- import { transformActivity } from "../../strava/activity";
11
- import { transformAthlete } from "../../strava/athlete";
8
+ import { createStravaClient } from "./client.js";
9
+ import { listAllActivities } from "./utils.js";
10
+ import { getLoggedInAthlete, getActivityById, getActivityStreams, } from "./types/stravaApi/sdk.gen.js";
11
+ import { generateState } from "../utils.js";
12
+ import { buildAuthUrl, exchangeCode, refreshToken as refreshStravaToken, } from "./auth.js";
13
+ import { transformActivity } from "./transform/activity.js";
14
+ import { transformAthlete } from "./transform/athlete.js";
12
15
  // ─── Public Actions ──────────────────────────────────────────────────────────
13
16
  /**
14
- * Full Strava OAuth callback handler.
17
+ * Generate a Strava OAuth authorization URL.
18
+ *
19
+ * If `userId` is provided, the state parameter is stored in the component's
20
+ * `pendingOAuth` table so that `completeStravaOAuth` can look it up
21
+ * automatically when the callback fires. This is the recommended flow
22
+ * when using `registerRoutes`.
23
+ *
24
+ * If `userId` is omitted, the host app must store the returned `state`
25
+ * itself and pass the userId to `connectStrava` manually.
26
+ */
27
+ export const getStravaAuthUrl = action({
28
+ args: {
29
+ clientId: v.string(),
30
+ redirectUri: v.string(),
31
+ scope: v.optional(v.string()),
32
+ userId: v.optional(v.string()),
33
+ },
34
+ handler: async (ctx, args) => {
35
+ const state = generateState();
36
+ const authUrl = buildAuthUrl({
37
+ clientId: args.clientId,
38
+ redirectUri: args.redirectUri,
39
+ scope: args.scope,
40
+ state,
41
+ });
42
+ if (args.userId) {
43
+ await ctx.runMutation(internal.strava.private.storePendingOAuth, {
44
+ provider: "STRAVA",
45
+ state,
46
+ userId: args.userId,
47
+ });
48
+ }
49
+ return { authUrl, state };
50
+ },
51
+ });
52
+ /**
53
+ * Full Strava OAuth callback handler (manual flow).
15
54
  *
16
55
  * Exchanges the authorization code for tokens, creates/reactivates the
17
56
  * Soma connection, stores tokens securely, syncs the athlete profile,
18
57
  * and syncs all activities.
19
58
  *
59
+ * Used when the host app handles the callback itself and passes the
60
+ * userId directly (analogous to `connectGarmin`).
61
+ *
20
62
  * Returns `{ connectionId, synced, errors }`.
21
63
  */
22
64
  export const connectStrava = action({
23
65
  args: {
24
66
  userId: v.string(),
25
- code: v.string(),
26
67
  clientId: v.string(),
27
68
  clientSecret: v.string(),
28
- baseUrl: v.optional(v.string()),
29
- includeStreams: v.optional(v.boolean()),
69
+ code: v.string(),
30
70
  },
31
71
  returns: v.object({
32
72
  connectionId: v.string(),
33
- synced: v.number(),
34
- errors: v.array(v.object({ activityId: v.number(), error: v.string() })),
73
+ synced: v.object({ athletes: v.number(), activities: v.number() }),
74
+ errors: v.array(v.object({ type: v.string(), id: v.string(), error: v.string() })),
35
75
  }),
36
76
  handler: async (ctx, args) => {
37
77
  // 1. Exchange authorization code for tokens
@@ -39,7 +79,6 @@ export const connectStrava = action({
39
79
  clientId: args.clientId,
40
80
  clientSecret: args.clientSecret,
41
81
  code: args.code,
42
- baseUrl: args.baseUrl,
43
82
  });
44
83
  // 2. Create/reactivate the Soma connection
45
84
  const connectionId = await ctx.runMutation(api.public.connect, {
@@ -47,55 +86,91 @@ export const connectStrava = action({
47
86
  provider: "STRAVA",
48
87
  });
49
88
  // 3. Store OAuth tokens in providerTokens table
50
- const _stored = await ctx.runMutation(internal.strava.private.storeTokens, {
89
+ await ctx.runMutation(internal.strava.private.storeTokens, {
51
90
  connectionId,
52
91
  accessToken: tokens.access_token,
53
92
  refreshToken: tokens.refresh_token,
54
93
  expiresAt: tokens.expires_at,
55
94
  });
56
- // 4. Sync athlete profile
57
- const client = new StravaClient({
95
+ // 4. Sync all data types
96
+ const result = await ctx.runAction(api.strava.public.syncAllTypes, {
58
97
  accessToken: tokens.access_token,
59
- baseUrl: args.baseUrl,
60
- });
61
- const athlete = await client.getAthlete();
62
- const athleteData = transformAthlete(athlete);
63
- const _athleteId = await ctx.runMutation(api.public.ingestAthlete, {
64
98
  connectionId,
65
99
  userId: args.userId,
66
- ...athleteData,
67
100
  });
68
- // 5. Sync all activities
69
- const summaries = await client.listAllActivities();
70
- let synced = 0;
71
- const errors = [];
72
- for (const summary of summaries) {
73
- try {
74
- const detailed = await client.getActivity(summary.id);
75
- const streams = args.includeStreams
76
- ? await client.getActivityStreams(summary.id)
77
- : undefined;
78
- const data = transformActivity(detailed, { streams });
79
- const _activityId = await ctx.runMutation(api.public.ingestActivity, {
80
- connectionId,
81
- userId: args.userId,
82
- ...data,
83
- });
84
- synced++;
85
- }
86
- catch (err) {
87
- errors.push({
88
- activityId: summary.id,
89
- error: err instanceof Error ? err.message : String(err),
90
- });
91
- }
101
+ // 5. Update lastDataUpdate timestamp
102
+ await ctx.runMutation(api.public.updateConnection, {
103
+ connectionId,
104
+ lastDataUpdate: new Date().toISOString(),
105
+ });
106
+ return { connectionId, synced: result.synced, errors: result.errors };
107
+ },
108
+ });
109
+ /**
110
+ * Complete a Strava OAuth flow using stored pending state.
111
+ *
112
+ * Used by `registerRoutes` — the callback handler calls this with the
113
+ * `code` and `state` from the redirect. The action looks up the pending
114
+ * state (userId) stored during `getStravaAuthUrl`, exchanges for tokens,
115
+ * creates the connection, syncs data, and cleans up the pending entry.
116
+ */
117
+ export const completeStravaOAuth = action({
118
+ args: {
119
+ code: v.string(),
120
+ state: v.string(),
121
+ clientId: v.string(),
122
+ clientSecret: v.string(),
123
+ },
124
+ returns: v.object({
125
+ connectionId: v.string(),
126
+ userId: v.string(),
127
+ synced: v.object({ athletes: v.number(), activities: v.number() }),
128
+ errors: v.array(v.object({ type: v.string(), id: v.string(), error: v.string() })),
129
+ }),
130
+ handler: async (ctx, args) => {
131
+ // 1. Look up pending state
132
+ const pending = await ctx.runQuery(internal.strava.private.getPendingOAuth, { state: args.state });
133
+ if (!pending) {
134
+ throw new Error("No pending Strava OAuth state found for this state parameter. " +
135
+ "The authorization may have expired or was already used.");
92
136
  }
93
- // 6. Update lastDataUpdate timestamp
94
- const _updated = await ctx.runMutation(api.public.updateConnection, {
137
+ // 2. Exchange authorization code for tokens
138
+ const tokens = await exchangeCode({
139
+ clientId: args.clientId,
140
+ clientSecret: args.clientSecret,
141
+ code: args.code,
142
+ });
143
+ // 3. Clean up pending entry
144
+ await ctx.runMutation(internal.strava.private.deletePendingOAuth, { state: args.state });
145
+ // 4. Create/reactivate the Soma connection
146
+ const connectionId = await ctx.runMutation(api.public.connect, {
147
+ userId: pending.userId,
148
+ provider: "STRAVA",
149
+ });
150
+ // 5. Store OAuth tokens
151
+ await ctx.runMutation(internal.strava.private.storeTokens, {
152
+ connectionId,
153
+ accessToken: tokens.access_token,
154
+ refreshToken: tokens.refresh_token,
155
+ expiresAt: tokens.expires_at,
156
+ });
157
+ // 6. Sync all data types
158
+ const result = await ctx.runAction(api.strava.public.syncAllTypes, {
159
+ accessToken: tokens.access_token,
160
+ connectionId,
161
+ userId: pending.userId,
162
+ });
163
+ // 7. Update lastDataUpdate timestamp
164
+ await ctx.runMutation(api.public.updateConnection, {
95
165
  connectionId,
96
166
  lastDataUpdate: new Date().toISOString(),
97
167
  });
98
- return { connectionId, synced, errors };
168
+ return {
169
+ connectionId,
170
+ userId: pending.userId,
171
+ synced: result.synced,
172
+ errors: result.errors,
173
+ };
99
174
  },
100
175
  });
101
176
  /**
@@ -111,13 +186,11 @@ export const syncStrava = action({
111
186
  userId: v.string(),
112
187
  clientId: v.string(),
113
188
  clientSecret: v.string(),
114
- baseUrl: v.optional(v.string()),
115
- includeStreams: v.optional(v.boolean()),
116
189
  after: v.optional(v.number()),
117
190
  },
118
191
  returns: v.object({
119
- synced: v.number(),
120
- errors: v.array(v.object({ activityId: v.number(), error: v.string() })),
192
+ synced: v.object({ athletes: v.number(), activities: v.number() }),
193
+ errors: v.array(v.object({ type: v.string(), id: v.string(), error: v.string() })),
121
194
  }),
122
195
  handler: async (ctx, args) => {
123
196
  // 1. Look up connection
@@ -148,64 +221,119 @@ export const syncStrava = action({
148
221
  clientId: args.clientId,
149
222
  clientSecret: args.clientSecret,
150
223
  refreshToken: tokenDoc.refreshToken,
151
- baseUrl: args.baseUrl,
152
224
  });
153
225
  accessToken = refreshed.access_token;
154
- const _refreshed = await ctx.runMutation(internal.strava.private.storeTokens, {
226
+ await ctx.runMutation(internal.strava.private.storeTokens, {
155
227
  connectionId,
156
228
  accessToken: refreshed.access_token,
157
229
  refreshToken: refreshed.refresh_token,
158
230
  expiresAt: refreshed.expires_at,
159
231
  });
160
232
  }
161
- // 4. Create client and sync
162
- const client = new StravaClient({
233
+ // 4. Sync all data types
234
+ const result = await ctx.runAction(api.strava.public.syncAllTypes, {
163
235
  accessToken,
164
- baseUrl: args.baseUrl,
165
- });
166
- // Sync athlete profile
167
- const athlete = await client.getAthlete();
168
- const athleteData = transformAthlete(athlete);
169
- const _athleteId = await ctx.runMutation(api.public.ingestAthlete, {
170
236
  connectionId,
171
237
  userId: args.userId,
172
- ...athleteData,
173
- });
174
- // Sync activities (optionally incremental via `after`)
175
- const summaries = await client.listAllActivities({
176
238
  after: args.after,
177
239
  });
178
- let synced = 0;
179
- const errors = [];
180
- for (const summary of summaries) {
181
- try {
182
- const detailed = await client.getActivity(summary.id);
183
- const streams = args.includeStreams
184
- ? await client.getActivityStreams(summary.id)
185
- : undefined;
186
- const data = transformActivity(detailed, { streams });
187
- const _activityId = await ctx.runMutation(api.public.ingestActivity, {
188
- connectionId,
189
- userId: args.userId,
190
- ...data,
191
- });
192
- synced++;
193
- }
194
- catch (err) {
195
- errors.push({
196
- activityId: summary.id,
197
- error: err instanceof Error ? err.message : String(err),
198
- });
199
- }
200
- }
201
240
  // 5. Update lastDataUpdate timestamp
202
- const _updated = await ctx.runMutation(api.public.updateConnection, {
241
+ await ctx.runMutation(api.public.updateConnection, {
203
242
  connectionId,
204
243
  lastDataUpdate: new Date().toISOString(),
205
244
  });
245
+ return { synced: result.synced, errors: result.errors };
246
+ },
247
+ });
248
+ // ─── Sync Engine ────────────────────────────────────────────────────────────
249
+ /**
250
+ * Fetch and ingest all Strava data types for a connected user.
251
+ *
252
+ * Called by the public actions (connectStrava, completeStravaOAuth, syncStrava)
253
+ * after obtaining a valid access token.
254
+ */
255
+ export const syncAllTypes = action({
256
+ args: {
257
+ accessToken: v.string(),
258
+ connectionId: v.id("connections"),
259
+ userId: v.string(),
260
+ after: v.optional(v.number()),
261
+ before: v.optional(v.number()),
262
+ },
263
+ handler: async (ctx, args) => {
264
+ const { accessToken, connectionId, userId } = args;
265
+ const client = createStravaClient(accessToken);
266
+ const synced = { athletes: 0, activities: 0 };
267
+ const errors = [];
268
+ // ── Athlete ──────────────────────────────────────────────────────────
269
+ try {
270
+ const { data: athlete, error } = await getLoggedInAthlete({ client });
271
+ if (error || !athlete)
272
+ throw new Error(error ? JSON.stringify(error) : "No athlete data");
273
+ const data = transformAthlete(athlete);
274
+ await ctx.runMutation(api.public.ingestAthlete, {
275
+ connectionId,
276
+ userId,
277
+ ...data,
278
+ });
279
+ synced.athletes++;
280
+ }
281
+ catch (err) {
282
+ errors.push({
283
+ type: "athlete",
284
+ id: "fetch",
285
+ error: err instanceof Error ? err.message : String(err),
286
+ });
287
+ }
288
+ // ── Activities ───────────────────────────────────────────────────────
289
+ try {
290
+ const summaries = await listAllActivities(client, {
291
+ after: args.after,
292
+ before: args.before,
293
+ });
294
+ for (const summary of summaries) {
295
+ if (summary.id == null)
296
+ continue;
297
+ try {
298
+ const { data: detailed, error: detailError } = await getActivityById({ client, path: { id: summary.id } });
299
+ if (detailError || !detailed)
300
+ throw new Error(detailError ? JSON.stringify(detailError) : "No activity data");
301
+ const { data: streams } = await getActivityStreams({
302
+ client,
303
+ path: { id: summary.id },
304
+ query: {
305
+ keys: ["time", "heartrate", "watts", "cadence", "latlng", "altitude", "velocity_smooth", "grade_smooth", "distance", "temp"],
306
+ key_by_type: true,
307
+ },
308
+ });
309
+ const data = transformActivity(detailed, { streams: streams ?? undefined });
310
+ await ctx.runMutation(api.public.ingestActivity, {
311
+ connectionId,
312
+ userId,
313
+ ...data,
314
+ });
315
+ synced.activities++;
316
+ }
317
+ catch (err) {
318
+ errors.push({
319
+ type: "activity",
320
+ id: String(summary.id),
321
+ error: err instanceof Error ? err.message : String(err),
322
+ });
323
+ }
324
+ }
325
+ }
326
+ catch (err) {
327
+ errors.push({
328
+ type: "activity",
329
+ id: "fetch",
330
+ error: err instanceof Error ? err.message : String(err),
331
+ });
332
+ }
206
333
  return { synced, errors };
207
334
  },
208
335
  });
336
+ // ─── Disconnect ─────────────────────────────────────────────────────────────
209
337
  /**
210
338
  * Disconnect a user from Strava.
211
339
  *
@@ -217,7 +345,6 @@ export const disconnectStrava = action({
217
345
  userId: v.string(),
218
346
  clientId: v.string(),
219
347
  clientSecret: v.string(),
220
- baseUrl: v.optional(v.string()),
221
348
  },
222
349
  returns: v.null(),
223
350
  handler: async (ctx, args) => {
@@ -231,8 +358,7 @@ export const disconnectStrava = action({
231
358
  const tokenDoc = await ctx.runQuery(internal.strava.private.getTokens, { connectionId });
232
359
  if (tokenDoc) {
233
360
  try {
234
- const base = (args.baseUrl ?? "https://www.strava.com").replace(/\/+$/, "");
235
- await fetch(`${base}/oauth/deauthorize`, {
361
+ await fetch("https://www.strava.com/oauth/deauthorize", {
236
362
  method: "POST",
237
363
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
238
364
  body: `access_token=${tokenDoc.accessToken}`,