@nativesquare/soma 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/dist/client/index.d.ts +7 -128
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +31 -112
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/component/_generated/api.d.ts +80 -4
  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 +135 -261
  9. package/dist/component/_generated/component.d.ts.map +1 -1
  10. package/dist/component/garmin/private.d.ts +475 -0
  11. package/dist/component/garmin/private.d.ts.map +1 -0
  12. package/dist/component/garmin/private.js +1614 -0
  13. package/dist/component/garmin/private.js.map +1 -0
  14. package/dist/component/garmin/public.d.ts +155 -0
  15. package/dist/component/garmin/public.d.ts.map +1 -0
  16. package/dist/component/garmin/public.js +787 -0
  17. package/dist/component/garmin/public.js.map +1 -0
  18. package/dist/component/garmin/schemas/activity.d.ts +94 -0
  19. package/dist/component/garmin/schemas/activity.d.ts.map +1 -0
  20. package/dist/component/garmin/schemas/activity.js +27 -0
  21. package/dist/component/garmin/schemas/activity.js.map +1 -0
  22. package/dist/component/garmin/schemas/activityDetails.d.ts +146 -0
  23. package/dist/component/garmin/schemas/activityDetails.d.ts.map +1 -0
  24. package/dist/component/garmin/schemas/activityDetails.js +27 -0
  25. package/dist/component/garmin/schemas/activityDetails.js.map +1 -0
  26. package/dist/component/garmin/schemas/bloodPressure.d.ts +38 -0
  27. package/dist/component/garmin/schemas/bloodPressure.d.ts.map +1 -0
  28. package/dist/component/garmin/schemas/bloodPressure.js +27 -0
  29. package/dist/component/garmin/schemas/bloodPressure.js.map +1 -0
  30. package/dist/component/garmin/schemas/bodyCompositions.d.ts +42 -0
  31. package/dist/component/garmin/schemas/bodyCompositions.d.ts.map +1 -0
  32. package/dist/component/garmin/schemas/bodyCompositions.js +27 -0
  33. package/dist/component/garmin/schemas/bodyCompositions.js.map +1 -0
  34. package/dist/component/garmin/schemas/dailies.d.ts +98 -0
  35. package/dist/component/garmin/schemas/dailies.d.ts.map +1 -0
  36. package/dist/component/garmin/schemas/dailies.js +27 -0
  37. package/dist/component/garmin/schemas/dailies.js.map +1 -0
  38. package/dist/component/garmin/schemas/epochs.d.ts +54 -0
  39. package/dist/component/garmin/schemas/epochs.d.ts.map +1 -0
  40. package/dist/component/garmin/schemas/epochs.js +27 -0
  41. package/dist/component/garmin/schemas/epochs.js.map +1 -0
  42. package/dist/component/garmin/schemas/healthSnapshot.d.ts +48 -0
  43. package/dist/component/garmin/schemas/healthSnapshot.d.ts.map +1 -0
  44. package/dist/component/garmin/schemas/healthSnapshot.js +27 -0
  45. package/dist/component/garmin/schemas/healthSnapshot.js.map +1 -0
  46. package/dist/component/garmin/schemas/hrvSummary.d.ts +40 -0
  47. package/dist/component/garmin/schemas/hrvSummary.d.ts.map +1 -0
  48. package/dist/component/garmin/schemas/hrvSummary.js +27 -0
  49. package/dist/component/garmin/schemas/hrvSummary.js.map +1 -0
  50. package/dist/component/garmin/schemas/manuallyUpdatedActivities.d.ts +94 -0
  51. package/dist/component/garmin/schemas/manuallyUpdatedActivities.d.ts.map +1 -0
  52. package/dist/component/garmin/schemas/manuallyUpdatedActivities.js +27 -0
  53. package/dist/component/garmin/schemas/manuallyUpdatedActivities.js.map +1 -0
  54. package/dist/component/garmin/schemas/menstrualCycleTracking.d.ts +100 -0
  55. package/dist/component/garmin/schemas/menstrualCycleTracking.d.ts.map +1 -0
  56. package/dist/component/garmin/schemas/menstrualCycleTracking.js +28 -0
  57. package/dist/component/garmin/schemas/menstrualCycleTracking.js.map +1 -0
  58. package/dist/component/garmin/schemas/moveIQ.d.ts +40 -0
  59. package/dist/component/garmin/schemas/moveIQ.d.ts.map +1 -0
  60. package/dist/component/garmin/schemas/moveIQ.js +27 -0
  61. package/dist/component/garmin/schemas/moveIQ.js.map +1 -0
  62. package/dist/component/garmin/schemas/pulseOx.d.ts +38 -0
  63. package/dist/component/garmin/schemas/pulseOx.d.ts.map +1 -0
  64. package/dist/component/garmin/schemas/pulseOx.js +28 -0
  65. package/dist/component/garmin/schemas/pulseOx.js.map +1 -0
  66. package/dist/component/garmin/schemas/respiration.d.ts +34 -0
  67. package/dist/component/garmin/schemas/respiration.d.ts.map +1 -0
  68. package/dist/component/garmin/schemas/respiration.js +28 -0
  69. package/dist/component/garmin/schemas/respiration.js.map +1 -0
  70. package/dist/component/garmin/schemas/skinTemperature.d.ts +36 -0
  71. package/dist/component/garmin/schemas/skinTemperature.d.ts.map +1 -0
  72. package/dist/component/garmin/schemas/skinTemperature.js +28 -0
  73. package/dist/component/garmin/schemas/skinTemperature.js.map +1 -0
  74. package/dist/component/garmin/schemas/sleeps.d.ts +88 -0
  75. package/dist/component/garmin/schemas/sleeps.d.ts.map +1 -0
  76. package/dist/component/garmin/schemas/sleeps.js +27 -0
  77. package/dist/component/garmin/schemas/sleeps.js.map +1 -0
  78. package/dist/component/garmin/schemas/stress.d.ts +70 -0
  79. package/dist/component/garmin/schemas/stress.d.ts.map +1 -0
  80. package/dist/component/garmin/schemas/stress.js +28 -0
  81. package/dist/component/garmin/schemas/stress.js.map +1 -0
  82. package/dist/component/garmin/schemas/userMetrics.d.ts +36 -0
  83. package/dist/component/garmin/schemas/userMetrics.d.ts.map +1 -0
  84. package/dist/component/garmin/schemas/userMetrics.js +28 -0
  85. package/dist/component/garmin/schemas/userMetrics.js.map +1 -0
  86. package/dist/component/garmin/transform/activity.d.ts +13 -0
  87. package/dist/component/garmin/transform/activity.d.ts.map +1 -0
  88. package/dist/component/garmin/transform/activity.js +111 -0
  89. package/dist/component/garmin/transform/activity.js.map +1 -0
  90. package/dist/component/garmin/transform/activityDetails.d.ts +13 -0
  91. package/dist/component/garmin/transform/activityDetails.d.ts.map +1 -0
  92. package/dist/component/garmin/transform/activityDetails.js +173 -0
  93. package/dist/component/garmin/transform/activityDetails.js.map +1 -0
  94. package/dist/component/garmin/transform/bloodPressure.d.ts +12 -0
  95. package/dist/component/garmin/transform/bloodPressure.d.ts.map +1 -0
  96. package/dist/component/garmin/transform/bloodPressure.js +33 -0
  97. package/dist/component/garmin/transform/bloodPressure.js.map +1 -0
  98. package/dist/component/garmin/transform/bodyCompositions.d.ts +12 -0
  99. package/dist/component/garmin/transform/bodyCompositions.d.ts.map +1 -0
  100. package/dist/component/garmin/transform/bodyCompositions.js +42 -0
  101. package/dist/component/garmin/transform/bodyCompositions.js.map +1 -0
  102. package/dist/component/garmin/transform/dailies.d.ts +12 -0
  103. package/dist/component/garmin/transform/dailies.d.ts.map +1 -0
  104. package/dist/component/garmin/transform/dailies.js +132 -0
  105. package/dist/component/garmin/transform/dailies.js.map +1 -0
  106. package/dist/component/garmin/transform/epochs.d.ts +13 -0
  107. package/dist/component/garmin/transform/epochs.d.ts.map +1 -0
  108. package/dist/component/garmin/transform/epochs.js +76 -0
  109. package/dist/component/garmin/transform/epochs.js.map +1 -0
  110. package/dist/component/garmin/transform/healthSnapshot.d.ts +12 -0
  111. package/dist/component/garmin/transform/healthSnapshot.d.ts.map +1 -0
  112. package/dist/component/garmin/transform/healthSnapshot.js +111 -0
  113. package/dist/component/garmin/transform/healthSnapshot.js.map +1 -0
  114. package/dist/component/garmin/transform/hrvSummary.d.ts +12 -0
  115. package/dist/component/garmin/transform/hrvSummary.d.ts.map +1 -0
  116. package/dist/component/garmin/transform/hrvSummary.js +45 -0
  117. package/dist/component/garmin/transform/hrvSummary.js.map +1 -0
  118. package/dist/component/garmin/transform/manuallyUpdatedActivities.d.ts +11 -0
  119. package/dist/component/garmin/transform/manuallyUpdatedActivities.d.ts.map +1 -0
  120. package/dist/component/garmin/transform/manuallyUpdatedActivities.js +20 -0
  121. package/dist/component/garmin/transform/manuallyUpdatedActivities.js.map +1 -0
  122. package/dist/component/garmin/transform/menstrualCycleTracking.d.ts +10 -0
  123. package/dist/component/garmin/transform/menstrualCycleTracking.d.ts.map +1 -0
  124. package/dist/component/garmin/transform/menstrualCycleTracking.js +43 -0
  125. package/dist/component/garmin/transform/menstrualCycleTracking.js.map +1 -0
  126. package/dist/component/garmin/transform/moveIQ.d.ts +17 -0
  127. package/dist/component/garmin/transform/moveIQ.d.ts.map +1 -0
  128. package/dist/component/garmin/transform/moveIQ.js +41 -0
  129. package/dist/component/garmin/transform/moveIQ.js.map +1 -0
  130. package/dist/component/garmin/transform/pulseOx.d.ts +12 -0
  131. package/dist/component/garmin/transform/pulseOx.d.ts.map +1 -0
  132. package/dist/component/garmin/transform/pulseOx.js +46 -0
  133. package/dist/component/garmin/transform/pulseOx.js.map +1 -0
  134. package/dist/component/garmin/transform/respiration.d.ts +12 -0
  135. package/dist/component/garmin/transform/respiration.d.ts.map +1 -0
  136. package/dist/component/garmin/transform/respiration.js +54 -0
  137. package/dist/component/garmin/transform/respiration.js.map +1 -0
  138. package/dist/component/garmin/transform/skinTemperature.d.ts +12 -0
  139. package/dist/component/garmin/transform/skinTemperature.d.ts.map +1 -0
  140. package/dist/component/garmin/transform/skinTemperature.js +38 -0
  141. package/dist/component/garmin/transform/skinTemperature.js.map +1 -0
  142. package/dist/component/garmin/transform/sleeps.d.ts +55 -0
  143. package/dist/component/garmin/transform/sleeps.d.ts.map +1 -0
  144. package/dist/component/garmin/transform/sleeps.js +120 -0
  145. package/dist/component/garmin/transform/sleeps.js.map +1 -0
  146. package/dist/component/garmin/transform/stress.d.ts +12 -0
  147. package/dist/component/garmin/transform/stress.d.ts.map +1 -0
  148. package/dist/component/garmin/transform/stress.js +56 -0
  149. package/dist/component/garmin/transform/stress.js.map +1 -0
  150. package/dist/component/garmin/transform/userMetrics.d.ts +12 -0
  151. package/dist/component/garmin/transform/userMetrics.d.ts.map +1 -0
  152. package/dist/component/garmin/transform/userMetrics.js +48 -0
  153. package/dist/component/garmin/transform/userMetrics.js.map +1 -0
  154. package/dist/component/garmin/types/garmin.d.ts +21 -0
  155. package/dist/component/garmin/types/garmin.d.ts.map +1 -0
  156. package/dist/component/garmin/types/garmin.js +6 -0
  157. package/dist/component/garmin/types/garmin.js.map +1 -0
  158. package/dist/component/garmin/types/zod/zod.gen.d.ts +1319 -0
  159. package/dist/component/garmin/types/zod/zod.gen.d.ts.map +1 -0
  160. package/dist/component/garmin/types/zod/zod.gen.js +784 -0
  161. package/dist/component/garmin/types/zod/zod.gen.js.map +1 -0
  162. package/dist/component/garmin/webhooks.d.ts +141 -0
  163. package/dist/component/garmin/webhooks.d.ts.map +1 -0
  164. package/dist/component/garmin/webhooks.js +766 -0
  165. package/dist/component/garmin/webhooks.js.map +1 -0
  166. package/dist/component/private.d.ts +4 -4
  167. package/dist/component/public.d.ts +333 -333
  168. package/dist/component/schema.d.ts +133 -133
  169. package/dist/component/strava/private.d.ts +30 -0
  170. package/dist/component/strava/private.d.ts.map +1 -0
  171. package/dist/component/strava/private.js +71 -0
  172. package/dist/component/strava/private.js.map +1 -0
  173. package/dist/component/{strava.d.ts → strava/public.d.ts} +3 -31
  174. package/dist/component/strava/public.d.ts.map +1 -0
  175. package/dist/component/{strava.js → strava/public.js} +22 -101
  176. package/dist/component/strava/public.js.map +1 -0
  177. package/dist/component/validators/activity.d.ts +6 -0
  178. package/dist/component/validators/activity.d.ts.map +1 -1
  179. package/dist/component/validators/activity.js.map +1 -1
  180. package/dist/component/validators/body.d.ts +20 -14
  181. package/dist/component/validators/body.d.ts.map +1 -1
  182. package/dist/component/validators/body.js.map +1 -1
  183. package/dist/component/validators/daily.d.ts +6 -0
  184. package/dist/component/validators/daily.d.ts.map +1 -1
  185. package/dist/component/validators/daily.js.map +1 -1
  186. package/dist/component/validators/enums.d.ts +1 -1
  187. package/dist/component/validators/menstruation.d.ts +5 -0
  188. package/dist/component/validators/menstruation.d.ts.map +1 -1
  189. package/dist/component/validators/menstruation.js.map +1 -1
  190. package/dist/garmin/client.js.map +1 -1
  191. package/dist/garmin/index.d.ts +0 -2
  192. package/dist/garmin/index.d.ts.map +1 -1
  193. package/dist/garmin/index.js +0 -1
  194. package/dist/garmin/index.js.map +1 -1
  195. package/dist/garmin/sync.d.ts.map +1 -1
  196. package/dist/garmin/sync.js +3 -2
  197. package/dist/garmin/sync.js.map +1 -1
  198. package/dist/garmin/types.d.ts +1 -1
  199. package/dist/garmin/types.d.ts.map +1 -1
  200. package/dist/validators.d.ts +31 -28
  201. package/dist/validators.d.ts.map +1 -1
  202. package/dist/validators.js +2 -2
  203. package/dist/validators.js.map +1 -1
  204. package/package.json +4 -7
  205. package/src/client/index.ts +41 -172
  206. package/src/component/_generated/api.ts +96 -4
  207. package/src/component/_generated/component.ts +252 -284
  208. package/src/{garmin → component/garmin}/auth.ts +8 -1
  209. package/src/component/garmin/client.ts +39 -0
  210. package/src/component/garmin/private.ts +1798 -0
  211. package/src/component/garmin/public.ts +938 -0
  212. package/src/component/garmin/schemas/activity.ts +40 -0
  213. package/src/component/garmin/schemas/activityDetails.ts +45 -0
  214. package/src/component/garmin/schemas/bloodPressure.ts +38 -0
  215. package/src/component/garmin/schemas/bodyCompositions.ts +38 -0
  216. package/src/component/garmin/schemas/dailies.ts +38 -0
  217. package/src/component/garmin/schemas/epochs.ts +38 -0
  218. package/src/component/garmin/schemas/healthSnapshot.ts +38 -0
  219. package/src/component/garmin/schemas/hrvSummary.ts +38 -0
  220. package/src/component/garmin/schemas/manuallyUpdatedActivities.ts +49 -0
  221. package/src/component/garmin/schemas/menstrualCycleTracking.ts +39 -0
  222. package/src/component/garmin/schemas/moveIQ.ts +38 -0
  223. package/src/component/garmin/schemas/pulseOx.ts +39 -0
  224. package/src/component/garmin/schemas/respiration.ts +39 -0
  225. package/src/component/garmin/schemas/skinTemperature.ts +39 -0
  226. package/src/component/garmin/schemas/sleeps.ts +38 -0
  227. package/src/component/garmin/schemas/stress.ts +43 -0
  228. package/src/component/garmin/schemas/userMetrics.ts +39 -0
  229. package/src/component/garmin/transform/activity.ts +143 -0
  230. package/src/component/garmin/transform/activityDetails.ts +236 -0
  231. package/src/{garmin → component/garmin/transform}/bloodPressure.ts +39 -41
  232. package/src/component/garmin/transform/bodyCompositions.ts +51 -0
  233. package/src/component/garmin/transform/dailies.ts +179 -0
  234. package/src/component/garmin/transform/epochs.ts +94 -0
  235. package/src/component/garmin/transform/healthSnapshot.ts +152 -0
  236. package/src/component/garmin/transform/hrvSummary.ts +56 -0
  237. package/src/component/garmin/transform/manuallyUpdatedActivities.ts +27 -0
  238. package/src/{garmin/maps/activity-type.ts → component/garmin/transform/maps/activityType.ts} +116 -116
  239. package/src/{garmin/maps/sleep-level.ts → component/garmin/transform/maps/sleepLevel.ts} +22 -22
  240. package/src/component/garmin/transform/menstrualCycleTracking.ts +48 -0
  241. package/src/component/garmin/transform/moveIQ.ts +48 -0
  242. package/src/{garmin → component/garmin/transform}/plannedWorkout.ts +328 -333
  243. package/src/component/garmin/transform/pulseOx.ts +64 -0
  244. package/src/component/garmin/transform/respiration.ts +73 -0
  245. package/src/component/garmin/transform/skinTemperature.ts +44 -0
  246. package/src/component/garmin/transform/sleeps.ts +159 -0
  247. package/src/component/garmin/transform/stress.ts +78 -0
  248. package/src/component/garmin/transform/userMetrics.ts +56 -0
  249. package/src/component/garmin/types/specs/training-api-workouts.json +699 -0
  250. package/src/component/garmin/types/trainingApiWorkouts/client/client.gen.ts +290 -0
  251. package/src/component/garmin/types/trainingApiWorkouts/client/index.ts +25 -0
  252. package/src/component/garmin/types/trainingApiWorkouts/client/types.gen.ts +214 -0
  253. package/src/component/garmin/types/trainingApiWorkouts/client/utils.gen.ts +316 -0
  254. package/src/component/garmin/types/trainingApiWorkouts/client.gen.ts +16 -0
  255. package/src/component/garmin/types/trainingApiWorkouts/core/auth.gen.ts +41 -0
  256. package/src/component/garmin/types/trainingApiWorkouts/core/bodySerializer.gen.ts +82 -0
  257. package/src/component/garmin/types/trainingApiWorkouts/core/params.gen.ts +169 -0
  258. package/src/component/garmin/types/trainingApiWorkouts/core/pathSerializer.gen.ts +171 -0
  259. package/src/component/garmin/types/trainingApiWorkouts/core/queryKeySerializer.gen.ts +117 -0
  260. package/src/component/garmin/types/trainingApiWorkouts/core/serverSentEvents.gen.ts +243 -0
  261. package/src/component/garmin/types/trainingApiWorkouts/core/types.gen.ts +104 -0
  262. package/src/component/garmin/types/trainingApiWorkouts/core/utils.gen.ts +140 -0
  263. package/src/component/garmin/types/trainingApiWorkouts/index.ts +4 -0
  264. package/src/component/garmin/types/trainingApiWorkouts/sdk.gen.ts +126 -0
  265. package/src/component/garmin/types/trainingApiWorkouts/types.gen.ts +387 -0
  266. package/src/component/garmin/types/trainingApiWorkouts/zod.gen.ts +423 -0
  267. package/src/component/garmin/types/wellnessApi/client/client.gen.ts +290 -0
  268. package/src/component/garmin/types/wellnessApi/client/index.ts +25 -0
  269. package/src/component/garmin/types/wellnessApi/client/types.gen.ts +214 -0
  270. package/src/component/garmin/types/wellnessApi/client/utils.gen.ts +316 -0
  271. package/src/component/garmin/types/wellnessApi/client.gen.ts +16 -0
  272. package/src/component/garmin/types/wellnessApi/core/auth.gen.ts +41 -0
  273. package/src/component/garmin/types/wellnessApi/core/bodySerializer.gen.ts +82 -0
  274. package/src/component/garmin/types/wellnessApi/core/params.gen.ts +169 -0
  275. package/src/component/garmin/types/wellnessApi/core/pathSerializer.gen.ts +171 -0
  276. package/src/component/garmin/types/wellnessApi/core/queryKeySerializer.gen.ts +117 -0
  277. package/src/component/garmin/types/wellnessApi/core/serverSentEvents.gen.ts +243 -0
  278. package/src/component/garmin/types/wellnessApi/core/types.gen.ts +104 -0
  279. package/src/component/garmin/types/wellnessApi/core/utils.gen.ts +140 -0
  280. package/src/component/garmin/types/wellnessApi/index.ts +4 -0
  281. package/src/component/garmin/types/wellnessApi/sdk.gen.ts +207 -0
  282. package/src/component/garmin/types/wellnessApi/types.gen.ts +2942 -0
  283. package/src/component/garmin/types/wellnessApi/zod.gen.ts +878 -0
  284. package/src/component/garmin/utils.ts +25 -0
  285. package/src/component/garmin/webhooks.ts +852 -0
  286. package/src/component/strava/private.ts +89 -0
  287. package/src/component/{strava.ts → strava/public.ts} +341 -404
  288. package/src/component/validators/activity.ts +5 -0
  289. package/src/component/validators/body.ts +5 -0
  290. package/src/component/validators/daily.ts +5 -0
  291. package/src/component/validators/menstruation.ts +5 -1
  292. package/src/component/validators/plannedWorkout.ts +5 -0
  293. package/src/validators.ts +12 -2
  294. package/dist/component/garmin.d.ts +0 -366
  295. package/dist/component/garmin.d.ts.map +0 -1
  296. package/dist/component/garmin.js +0 -1481
  297. package/dist/component/garmin.js.map +0 -1
  298. package/dist/component/strava.d.ts.map +0 -1
  299. package/dist/component/strava.js.map +0 -1
  300. package/dist/garmin/activity.d.ts +0 -92
  301. package/dist/garmin/activity.d.ts.map +0 -1
  302. package/dist/garmin/activity.js +0 -201
  303. package/dist/garmin/activity.js.map +0 -1
  304. package/src/component/garmin.ts +0 -1722
  305. package/src/garmin/activity.test.ts +0 -170
  306. package/src/garmin/activity.ts +0 -265
  307. package/src/garmin/auth.test.ts +0 -103
  308. package/src/garmin/body.ts +0 -59
  309. package/src/garmin/client.ts +0 -886
  310. package/src/garmin/daily.ts +0 -215
  311. package/src/garmin/hrv.ts +0 -57
  312. package/src/garmin/index.ts +0 -145
  313. package/src/garmin/maps/activity-type.test.ts +0 -78
  314. package/src/garmin/menstruation.ts +0 -44
  315. package/src/garmin/pulseOx.ts +0 -45
  316. package/src/garmin/respiration.ts +0 -55
  317. package/src/garmin/skinTemp.ts +0 -42
  318. package/src/garmin/sleep.test.ts +0 -109
  319. package/src/garmin/sleep.ts +0 -176
  320. package/src/garmin/stressDetails.ts +0 -71
  321. package/src/garmin/sync.ts +0 -566
  322. package/src/garmin/types.ts +0 -268
  323. package/src/garmin/userMetrics.ts +0 -50
  324. package/src/garmin/wellness-api.d.ts +0 -5637
  325. /package/src/{garmin/spec → component/garmin/types/specs}/wellness-api.json +0 -0
@@ -1,404 +1,341 @@
1
- // ─── Strava Component Actions ────────────────────────────────────────────────
2
- // Public actions that handle the full Strava OAuth + sync lifecycle.
3
- // The host app calls these through the Soma class, which threads the
4
- // credentials automatically from env vars or constructor config.
5
- //
6
- // Internal mutations manage the providerTokens table (token CRUD).
7
-
8
- import { v } from "convex/values";
9
- import { anyApi } from "convex/server";
10
- import {
11
- action,
12
- internalMutation,
13
- internalQuery,
14
- } from "./_generated/server.js";
15
- import { StravaClient } from "../strava/client.js";
16
- import {
17
- exchangeCode,
18
- refreshToken as refreshStravaToken,
19
- } from "../strava/auth.js";
20
- import { transformActivity } from "../strava/activity.js";
21
- import { transformAthlete } from "../strava/athlete.js";
22
-
23
- // Use anyApi to avoid circular type references between this file and _generated/api.ts.
24
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
- const publicApi: any = anyApi;
26
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
- const internalApi: any = anyApi;
28
-
29
- // ─── Internal Token CRUD ─────────────────────────────────────────────────────
30
- // These are only callable from within the component (actions in this file).
31
-
32
- /**
33
- * Store or update OAuth tokens for a connection.
34
- * Upserts by connectionId — one token record per connection.
35
- */
36
- export const storeTokens = internalMutation({
37
- args: {
38
- connectionId: v.id("connections"),
39
- accessToken: v.string(),
40
- refreshToken: v.string(),
41
- expiresAt: v.number(),
42
- },
43
- returns: v.null(),
44
- handler: async (ctx, args) => {
45
- const existing = await ctx.db
46
- .query("providerTokens")
47
- .withIndex("by_connectionId", (q) =>
48
- q.eq("connectionId", args.connectionId),
49
- )
50
- .first();
51
-
52
- if (existing) {
53
- await ctx.db.patch(existing._id, {
54
- accessToken: args.accessToken,
55
- refreshToken: args.refreshToken,
56
- expiresAt: args.expiresAt,
57
- });
58
- return null;
59
- }
60
-
61
- await ctx.db.insert("providerTokens", args);
62
- return null;
63
- },
64
- });
65
-
66
- /**
67
- * Get stored tokens for a connection.
68
- */
69
- export const getTokens = internalQuery({
70
- args: { connectionId: v.id("connections") },
71
- returns: v.union(
72
- v.object({
73
- _id: v.id("providerTokens"),
74
- _creationTime: v.number(),
75
- connectionId: v.id("connections"),
76
- accessToken: v.string(),
77
- refreshToken: v.optional(v.string()),
78
- expiresAt: v.optional(v.number()),
79
- }),
80
- v.null(),
81
- ),
82
- handler: async (ctx, args) => {
83
- return await ctx.db
84
- .query("providerTokens")
85
- .withIndex("by_connectionId", (q) =>
86
- q.eq("connectionId", args.connectionId),
87
- )
88
- .first();
89
- },
90
- });
91
-
92
- /**
93
- * Delete stored tokens for a connection.
94
- */
95
- export const deleteTokens = internalMutation({
96
- args: { connectionId: v.id("connections") },
97
- returns: v.null(),
98
- handler: async (ctx, args) => {
99
- const existing = await ctx.db
100
- .query("providerTokens")
101
- .withIndex("by_connectionId", (q) =>
102
- q.eq("connectionId", args.connectionId),
103
- )
104
- .first();
105
-
106
- if (existing) {
107
- await ctx.db.delete(existing._id);
108
- }
109
- return null;
110
- },
111
- });
112
-
113
- // ─── Public Actions ──────────────────────────────────────────────────────────
114
-
115
- /**
116
- * Full Strava OAuth callback handler.
117
- *
118
- * Exchanges the authorization code for tokens, creates/reactivates the
119
- * Soma connection, stores tokens securely, syncs the athlete profile,
120
- * and syncs all activities.
121
- *
122
- * Returns `{ connectionId, synced, errors }`.
123
- */
124
- export const connectStrava = action({
125
- args: {
126
- userId: v.string(),
127
- code: v.string(),
128
- clientId: v.string(),
129
- clientSecret: v.string(),
130
- baseUrl: v.optional(v.string()),
131
- includeStreams: v.optional(v.boolean()),
132
- },
133
- returns: v.object({
134
- connectionId: v.string(),
135
- synced: v.number(),
136
- errors: v.array(
137
- v.object({ activityId: v.number(), error: v.string() }),
138
- ),
139
- }),
140
- handler: async (ctx, args) => {
141
- // 1. Exchange authorization code for tokens
142
- const tokens = await exchangeCode({
143
- clientId: args.clientId,
144
- clientSecret: args.clientSecret,
145
- code: args.code,
146
- baseUrl: args.baseUrl,
147
- });
148
-
149
- // 2. Create/reactivate the Soma connection
150
- const connectionId = await ctx.runMutation(publicApi.public.connect, {
151
- userId: args.userId,
152
- provider: "STRAVA",
153
- });
154
-
155
- // 3. Store OAuth tokens in providerTokens table
156
- await ctx.runMutation(internalApi.strava.storeTokens, {
157
- connectionId,
158
- accessToken: tokens.access_token,
159
- refreshToken: tokens.refresh_token,
160
- expiresAt: tokens.expires_at,
161
- });
162
-
163
- // 4. Sync athlete profile
164
- const client = new StravaClient({
165
- accessToken: tokens.access_token,
166
- baseUrl: args.baseUrl,
167
- });
168
-
169
- const athlete = await client.getAthlete();
170
- const athleteData = transformAthlete(athlete);
171
- await ctx.runMutation(publicApi.public.ingestAthlete, {
172
- connectionId,
173
- userId: args.userId,
174
- ...athleteData,
175
- } as never);
176
-
177
- // 5. Sync all activities
178
- const summaries = await client.listAllActivities();
179
- let synced = 0;
180
- const errors: Array<{ activityId: number; error: string }> = [];
181
-
182
- for (const summary of summaries) {
183
- try {
184
- const detailed = await client.getActivity(summary.id);
185
- const streams = args.includeStreams
186
- ? await client.getActivityStreams(summary.id)
187
- : undefined;
188
- const data = transformActivity(detailed, { streams });
189
- await ctx.runMutation(publicApi.public.ingestActivity, {
190
- connectionId,
191
- userId: args.userId,
192
- ...data,
193
- } as never);
194
- synced++;
195
- } catch (err) {
196
- errors.push({
197
- activityId: summary.id,
198
- error: err instanceof Error ? err.message : String(err),
199
- });
200
- }
201
- }
202
-
203
- // 6. Update lastDataUpdate timestamp
204
- await ctx.runMutation(publicApi.public.updateConnection, {
205
- connectionId,
206
- lastDataUpdate: new Date().toISOString(),
207
- });
208
-
209
- return { connectionId, synced, errors };
210
- },
211
- });
212
-
213
- /**
214
- * Incremental Strava sync for an already-connected user.
215
- *
216
- * Looks up the stored tokens, auto-refreshes if expired, then syncs
217
- * the athlete profile and activities.
218
- *
219
- * Returns `{ synced, errors }`.
220
- */
221
- export const syncStrava = action({
222
- args: {
223
- userId: v.string(),
224
- clientId: v.string(),
225
- clientSecret: v.string(),
226
- baseUrl: v.optional(v.string()),
227
- includeStreams: v.optional(v.boolean()),
228
- after: v.optional(v.number()),
229
- },
230
- returns: v.object({
231
- synced: v.number(),
232
- errors: v.array(
233
- v.object({ activityId: v.number(), error: v.string() }),
234
- ),
235
- }),
236
- handler: async (ctx, args) => {
237
- // 1. Look up connection
238
- const connection = await ctx.runQuery(
239
- internalApi.private.getConnectionByProvider,
240
- { userId: args.userId, provider: "STRAVA" },
241
- );
242
- if (!connection) {
243
- throw new Error(
244
- `No Strava connection found for user "${args.userId}". ` +
245
- "Call connectStrava first.",
246
- );
247
- }
248
- if (!connection.active) {
249
- throw new Error(
250
- `Strava connection for user "${args.userId}" is inactive. Reconnect first.`,
251
- );
252
- }
253
-
254
- const connectionId = connection._id;
255
-
256
- // 2. Get stored tokens
257
- const tokenDoc = await ctx.runQuery(internalApi.strava.getTokens, {
258
- connectionId,
259
- });
260
- if (!tokenDoc) {
261
- throw new Error(
262
- `No tokens found for Strava connection. ` +
263
- "The connection may have been created before token storage was available.",
264
- );
265
- }
266
-
267
- // 3. Auto-refresh if token is expired or expiring within 5 minutes
268
- let accessToken = tokenDoc.accessToken;
269
- const now = Math.floor(Date.now() / 1000);
270
- if (!tokenDoc.refreshToken || !tokenDoc.expiresAt) {
271
- throw new Error(
272
- "Strava tokens are missing refreshToken or expiresAt. " +
273
- "This connection may have been created with an incompatible version.",
274
- );
275
- }
276
- if (tokenDoc.expiresAt < now + 300) {
277
- const refreshed = await refreshStravaToken({
278
- clientId: args.clientId,
279
- clientSecret: args.clientSecret,
280
- refreshToken: tokenDoc.refreshToken,
281
- baseUrl: args.baseUrl,
282
- });
283
- accessToken = refreshed.access_token;
284
- await ctx.runMutation(internalApi.strava.storeTokens, {
285
- connectionId,
286
- accessToken: refreshed.access_token,
287
- refreshToken: refreshed.refresh_token,
288
- expiresAt: refreshed.expires_at,
289
- });
290
- }
291
-
292
- // 4. Create client and sync
293
- const client = new StravaClient({
294
- accessToken,
295
- baseUrl: args.baseUrl,
296
- });
297
-
298
- // Sync athlete profile
299
- const athlete = await client.getAthlete();
300
- const athleteData = transformAthlete(athlete);
301
- await ctx.runMutation(publicApi.public.ingestAthlete, {
302
- connectionId,
303
- userId: args.userId,
304
- ...athleteData,
305
- } as never);
306
-
307
- // Sync activities (optionally incremental via `after`)
308
- const summaries = await client.listAllActivities({
309
- after: args.after,
310
- });
311
- let synced = 0;
312
- const errors: Array<{ activityId: number; error: string }> = [];
313
-
314
- for (const summary of summaries) {
315
- try {
316
- const detailed = await client.getActivity(summary.id);
317
- const streams = args.includeStreams
318
- ? await client.getActivityStreams(summary.id)
319
- : undefined;
320
- const data = transformActivity(detailed, { streams });
321
- await ctx.runMutation(publicApi.public.ingestActivity, {
322
- connectionId,
323
- userId: args.userId,
324
- ...data,
325
- } as never);
326
- synced++;
327
- } catch (err) {
328
- errors.push({
329
- activityId: summary.id,
330
- error: err instanceof Error ? err.message : String(err),
331
- });
332
- }
333
- }
334
-
335
- // 5. Update lastDataUpdate timestamp
336
- await ctx.runMutation(publicApi.public.updateConnection, {
337
- connectionId,
338
- lastDataUpdate: new Date().toISOString(),
339
- });
340
-
341
- return { synced, errors };
342
- },
343
- });
344
-
345
- /**
346
- * Disconnect a user from Strava.
347
- *
348
- * Revokes the token at Strava (best-effort), deletes stored tokens,
349
- * and sets the connection to inactive.
350
- */
351
- export const disconnectStrava = action({
352
- args: {
353
- userId: v.string(),
354
- clientId: v.string(),
355
- clientSecret: v.string(),
356
- baseUrl: v.optional(v.string()),
357
- },
358
- returns: v.null(),
359
- handler: async (ctx, args) => {
360
- // 1. Look up connection
361
- const connection = await ctx.runQuery(
362
- internalApi.private.getConnectionByProvider,
363
- { userId: args.userId, provider: "STRAVA" },
364
- );
365
- if (!connection) {
366
- throw new Error(
367
- `No Strava connection found for user "${args.userId}".`,
368
- );
369
- }
370
-
371
- const connectionId = connection._id;
372
-
373
- // 2. Revoke token at Strava (best-effort, don't fail if it errors)
374
- const tokenDoc = await ctx.runQuery(internalApi.strava.getTokens, {
375
- connectionId,
376
- });
377
- if (tokenDoc) {
378
- try {
379
- const base = (args.baseUrl ?? "https://www.strava.com").replace(
380
- /\/+$/,
381
- "",
382
- );
383
- await fetch(`${base}/oauth/deauthorize`, {
384
- method: "POST",
385
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
386
- body: `access_token=${tokenDoc.accessToken}`,
387
- });
388
- } catch {
389
- // Deauthorization is best-effort — clean up locally regardless
390
- }
391
-
392
- // 3. Delete stored tokens
393
- await ctx.runMutation(internalApi.strava.deleteTokens, { connectionId });
394
- }
395
-
396
- // 4. Set connection inactive
397
- await ctx.runMutation(publicApi.public.disconnect, {
398
- userId: args.userId,
399
- provider: "STRAVA",
400
- });
401
-
402
- return null;
403
- },
404
- });
1
+ // ─── Strava Component Actions ────────────────────────────────────────────────
2
+ // Public actions that handle the full Strava OAuth + sync lifecycle.
3
+ // The host app calls these through the Soma class, which threads the
4
+ // credentials automatically from env vars or constructor config.
5
+
6
+ import { v } from "convex/values";
7
+ import { action } from "../_generated/server";
8
+ import { api, internal } from "../_generated/api";
9
+ import type { Doc, Id } from "../_generated/dataModel";
10
+ import { StravaClient } from "../../strava/client";
11
+ import {
12
+ exchangeCode,
13
+ refreshToken as refreshStravaToken,
14
+ } from "../../strava/auth";
15
+ import { transformActivity } from "../../strava/activity";
16
+ import { transformAthlete } from "../../strava/athlete";
17
+
18
+ // ─── Public Actions ──────────────────────────────────────────────────────────
19
+
20
+ /**
21
+ * Full Strava OAuth callback handler.
22
+ *
23
+ * Exchanges the authorization code for tokens, creates/reactivates the
24
+ * Soma connection, stores tokens securely, syncs the athlete profile,
25
+ * and syncs all activities.
26
+ *
27
+ * Returns `{ connectionId, synced, errors }`.
28
+ */
29
+ export const connectStrava = action({
30
+ args: {
31
+ userId: v.string(),
32
+ code: v.string(),
33
+ clientId: v.string(),
34
+ clientSecret: v.string(),
35
+ baseUrl: v.optional(v.string()),
36
+ includeStreams: v.optional(v.boolean()),
37
+ },
38
+ returns: v.object({
39
+ connectionId: v.string(),
40
+ synced: v.number(),
41
+ errors: v.array(
42
+ v.object({ activityId: v.number(), error: v.string() }),
43
+ ),
44
+ }),
45
+ handler: async (ctx, args) => {
46
+ // 1. Exchange authorization code for tokens
47
+ const tokens = await exchangeCode({
48
+ clientId: args.clientId,
49
+ clientSecret: args.clientSecret,
50
+ code: args.code,
51
+ baseUrl: args.baseUrl,
52
+ });
53
+
54
+ // 2. Create/reactivate the Soma connection
55
+ const connectionId: Id<"connections"> = await ctx.runMutation(
56
+ api.public.connect,
57
+ {
58
+ userId: args.userId,
59
+ provider: "STRAVA",
60
+ },
61
+ );
62
+
63
+ // 3. Store OAuth tokens in providerTokens table
64
+ const _stored: null = await ctx.runMutation(
65
+ internal.strava.private.storeTokens,
66
+ {
67
+ connectionId,
68
+ accessToken: tokens.access_token,
69
+ refreshToken: tokens.refresh_token,
70
+ expiresAt: tokens.expires_at,
71
+ },
72
+ );
73
+
74
+ // 4. Sync athlete profile
75
+ const client = new StravaClient({
76
+ accessToken: tokens.access_token,
77
+ baseUrl: args.baseUrl,
78
+ });
79
+
80
+ const athlete = await client.getAthlete();
81
+ const athleteData = transformAthlete(athlete);
82
+ const _athleteId: Id<"athletes"> = await ctx.runMutation(
83
+ api.public.ingestAthlete,
84
+ {
85
+ connectionId,
86
+ userId: args.userId,
87
+ ...athleteData,
88
+ } as never,
89
+ );
90
+
91
+ // 5. Sync all activities
92
+ const summaries = await client.listAllActivities();
93
+ let synced = 0;
94
+ const errors: Array<{ activityId: number; error: string }> = [];
95
+
96
+ for (const summary of summaries) {
97
+ try {
98
+ const detailed = await client.getActivity(summary.id);
99
+ const streams = args.includeStreams
100
+ ? await client.getActivityStreams(summary.id)
101
+ : undefined;
102
+ const data = transformActivity(detailed, { streams });
103
+ const _activityId: Id<"activities"> = await ctx.runMutation(
104
+ api.public.ingestActivity,
105
+ {
106
+ connectionId,
107
+ userId: args.userId,
108
+ ...data,
109
+ } as never,
110
+ );
111
+ synced++;
112
+ } catch (err) {
113
+ errors.push({
114
+ activityId: summary.id,
115
+ error: err instanceof Error ? err.message : String(err),
116
+ });
117
+ }
118
+ }
119
+
120
+ // 6. Update lastDataUpdate timestamp
121
+ const _updated: null = await ctx.runMutation(
122
+ api.public.updateConnection,
123
+ {
124
+ connectionId,
125
+ lastDataUpdate: new Date().toISOString(),
126
+ },
127
+ );
128
+
129
+ return { connectionId, synced, errors };
130
+ },
131
+ });
132
+
133
+ /**
134
+ * Incremental Strava sync for an already-connected user.
135
+ *
136
+ * Looks up the stored tokens, auto-refreshes if expired, then syncs
137
+ * the athlete profile and activities.
138
+ *
139
+ * Returns `{ synced, errors }`.
140
+ */
141
+ export const syncStrava = action({
142
+ args: {
143
+ userId: v.string(),
144
+ clientId: v.string(),
145
+ clientSecret: v.string(),
146
+ baseUrl: v.optional(v.string()),
147
+ includeStreams: v.optional(v.boolean()),
148
+ after: v.optional(v.number()),
149
+ },
150
+ returns: v.object({
151
+ synced: v.number(),
152
+ errors: v.array(
153
+ v.object({ activityId: v.number(), error: v.string() }),
154
+ ),
155
+ }),
156
+ handler: async (ctx, args) => {
157
+ // 1. Look up connection
158
+ const connection: Doc<"connections"> | null = await ctx.runQuery(
159
+ internal.private.getConnectionByProvider,
160
+ { userId: args.userId, provider: "STRAVA" },
161
+ );
162
+ if (!connection) {
163
+ throw new Error(
164
+ `No Strava connection found for user "${args.userId}". ` +
165
+ "Call connectStrava first.",
166
+ );
167
+ }
168
+ if (!connection.active) {
169
+ throw new Error(
170
+ `Strava connection for user "${args.userId}" is inactive. Reconnect first.`,
171
+ );
172
+ }
173
+
174
+ const connectionId = connection._id;
175
+
176
+ // 2. Get stored tokens
177
+ const tokenDoc: Doc<"providerTokens"> | null = await ctx.runQuery(
178
+ internal.strava.private.getTokens,
179
+ { connectionId },
180
+ );
181
+ if (!tokenDoc) {
182
+ throw new Error(
183
+ `No tokens found for Strava connection. ` +
184
+ "The connection may have been created before token storage was available.",
185
+ );
186
+ }
187
+
188
+ // 3. Auto-refresh if token is expired or expiring within 5 minutes
189
+ let accessToken = tokenDoc.accessToken;
190
+ const now = Math.floor(Date.now() / 1000);
191
+ if (!tokenDoc.refreshToken || !tokenDoc.expiresAt) {
192
+ throw new Error(
193
+ "Strava tokens are missing refreshToken or expiresAt. " +
194
+ "This connection may have been created with an incompatible version.",
195
+ );
196
+ }
197
+ if (tokenDoc.expiresAt < now + 300) {
198
+ const refreshed = await refreshStravaToken({
199
+ clientId: args.clientId,
200
+ clientSecret: args.clientSecret,
201
+ refreshToken: tokenDoc.refreshToken,
202
+ baseUrl: args.baseUrl,
203
+ });
204
+ accessToken = refreshed.access_token;
205
+ const _refreshed: null = await ctx.runMutation(
206
+ internal.strava.private.storeTokens,
207
+ {
208
+ connectionId,
209
+ accessToken: refreshed.access_token,
210
+ refreshToken: refreshed.refresh_token,
211
+ expiresAt: refreshed.expires_at,
212
+ },
213
+ );
214
+ }
215
+
216
+ // 4. Create client and sync
217
+ const client = new StravaClient({
218
+ accessToken,
219
+ baseUrl: args.baseUrl,
220
+ });
221
+
222
+ // Sync athlete profile
223
+ const athlete = await client.getAthlete();
224
+ const athleteData = transformAthlete(athlete);
225
+ const _athleteId: Id<"athletes"> = await ctx.runMutation(
226
+ api.public.ingestAthlete,
227
+ {
228
+ connectionId,
229
+ userId: args.userId,
230
+ ...athleteData,
231
+ } as never,
232
+ );
233
+
234
+ // Sync activities (optionally incremental via `after`)
235
+ const summaries = await client.listAllActivities({
236
+ after: args.after,
237
+ });
238
+ let synced = 0;
239
+ const errors: Array<{ activityId: number; error: string }> = [];
240
+
241
+ for (const summary of summaries) {
242
+ try {
243
+ const detailed = await client.getActivity(summary.id);
244
+ const streams = args.includeStreams
245
+ ? await client.getActivityStreams(summary.id)
246
+ : undefined;
247
+ const data = transformActivity(detailed, { streams });
248
+ const _activityId: Id<"activities"> = await ctx.runMutation(
249
+ api.public.ingestActivity,
250
+ {
251
+ connectionId,
252
+ userId: args.userId,
253
+ ...data,
254
+ } as never,
255
+ );
256
+ synced++;
257
+ } catch (err) {
258
+ errors.push({
259
+ activityId: summary.id,
260
+ error: err instanceof Error ? err.message : String(err),
261
+ });
262
+ }
263
+ }
264
+
265
+ // 5. Update lastDataUpdate timestamp
266
+ const _updated: null = await ctx.runMutation(
267
+ api.public.updateConnection,
268
+ {
269
+ connectionId,
270
+ lastDataUpdate: new Date().toISOString(),
271
+ },
272
+ );
273
+
274
+ return { synced, errors };
275
+ },
276
+ });
277
+
278
+ /**
279
+ * Disconnect a user from Strava.
280
+ *
281
+ * Revokes the token at Strava (best-effort), deletes stored tokens,
282
+ * and sets the connection to inactive.
283
+ */
284
+ export const disconnectStrava = action({
285
+ args: {
286
+ userId: v.string(),
287
+ clientId: v.string(),
288
+ clientSecret: v.string(),
289
+ baseUrl: v.optional(v.string()),
290
+ },
291
+ returns: v.null(),
292
+ handler: async (ctx, args) => {
293
+ // 1. Look up connection
294
+ const connection: Doc<"connections"> | null = await ctx.runQuery(
295
+ internal.private.getConnectionByProvider,
296
+ { userId: args.userId, provider: "STRAVA" },
297
+ );
298
+ if (!connection) {
299
+ throw new Error(
300
+ `No Strava connection found for user "${args.userId}".`,
301
+ );
302
+ }
303
+
304
+ const connectionId = connection._id;
305
+
306
+ // 2. Revoke token at Strava (best-effort, don't fail if it errors)
307
+ const tokenDoc: Doc<"providerTokens"> | null = await ctx.runQuery(
308
+ internal.strava.private.getTokens,
309
+ { connectionId },
310
+ );
311
+ if (tokenDoc) {
312
+ try {
313
+ const base = (args.baseUrl ?? "https://www.strava.com").replace(
314
+ /\/+$/,
315
+ "",
316
+ );
317
+ await fetch(`${base}/oauth/deauthorize`, {
318
+ method: "POST",
319
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
320
+ body: `access_token=${tokenDoc.accessToken}`,
321
+ });
322
+ } catch {
323
+ // Deauthorization is best-effort — clean up locally regardless
324
+ }
325
+
326
+ // 3. Delete stored tokens
327
+ const _deleted: null = await ctx.runMutation(
328
+ internal.strava.private.deleteTokens,
329
+ { connectionId },
330
+ );
331
+ }
332
+
333
+ // 4. Set connection inactive
334
+ const _disconnected: null = await ctx.runMutation(api.public.disconnect, {
335
+ userId: args.userId,
336
+ provider: "STRAVA",
337
+ });
338
+
339
+ return null;
340
+ },
341
+ });