@nativesquare/soma 0.16.4 → 0.16.6

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 (56) hide show
  1. package/dist/client/garmin.d.ts.map +1 -1
  2. package/dist/client/garmin.js +2 -0
  3. package/dist/client/garmin.js.map +1 -1
  4. package/dist/client/index.d.ts +150 -0
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/client/index.js +30 -0
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/client/types.d.ts +3 -3
  9. package/dist/client/types.d.ts.map +1 -1
  10. package/dist/component/_generated/api.d.ts +2 -0
  11. package/dist/component/_generated/api.d.ts.map +1 -1
  12. package/dist/component/_generated/api.js.map +1 -1
  13. package/dist/component/_generated/component.d.ts +127 -0
  14. package/dist/component/_generated/component.d.ts.map +1 -1
  15. package/dist/component/garmin/schemas/deregistration.d.ts +8 -0
  16. package/dist/component/garmin/schemas/deregistration.d.ts.map +1 -0
  17. package/dist/component/garmin/schemas/deregistration.js +12 -0
  18. package/dist/component/garmin/schemas/deregistration.js.map +1 -0
  19. package/dist/component/garmin/webhooks.d.ts +14 -0
  20. package/dist/component/garmin/webhooks.d.ts.map +1 -1
  21. package/dist/component/garmin/webhooks.js +65 -0
  22. package/dist/component/garmin/webhooks.js.map +1 -1
  23. package/dist/component/private.d.ts +90 -0
  24. package/dist/component/private.d.ts.map +1 -1
  25. package/dist/component/public.d.ts +118 -6
  26. package/dist/component/public.d.ts.map +1 -1
  27. package/dist/component/public.js +91 -21
  28. package/dist/component/public.js.map +1 -1
  29. package/dist/component/schema.d.ts +111 -1
  30. package/dist/component/schema.d.ts.map +1 -1
  31. package/dist/component/utils.d.ts +27 -0
  32. package/dist/component/utils.d.ts.map +1 -1
  33. package/dist/component/utils.js +64 -0
  34. package/dist/component/utils.js.map +1 -1
  35. package/dist/component/validators/connection.d.ts +160 -0
  36. package/dist/component/validators/connection.d.ts.map +1 -1
  37. package/dist/component/validators/connection.js +16 -0
  38. package/dist/component/validators/connection.js.map +1 -1
  39. package/dist/component/validators/shared.d.ts +1 -1
  40. package/dist/component/validators/shared.d.ts.map +1 -1
  41. package/dist/component/validators/shared.js.map +1 -1
  42. package/dist/validators.d.ts +80 -0
  43. package/dist/validators.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/src/client/garmin.ts +3 -1
  46. package/src/client/index.ts +34 -0
  47. package/src/client/types.ts +4 -3
  48. package/src/component/_generated/api.ts +2 -0
  49. package/src/component/_generated/component.ts +49 -0
  50. package/src/component/garmin/schemas/deregistration.ts +14 -0
  51. package/src/component/garmin/webhooks.ts +84 -0
  52. package/src/component/healthkit/public.ts +597 -597
  53. package/src/component/public.ts +130 -21
  54. package/src/component/utils.ts +116 -22
  55. package/src/component/validators/connection.ts +18 -0
  56. package/src/component/validators/shared.ts +1 -0
@@ -8,6 +8,36 @@ export declare const getActiveConnections: import("convex/server").RegisteredQue
8
8
  providerUserId?: string | undefined;
9
9
  active?: boolean | undefined;
10
10
  lastDataUpdate?: string | undefined;
11
+ stats?: {
12
+ body: {
13
+ count: number;
14
+ oldest: string | null;
15
+ };
16
+ daily: {
17
+ count: number;
18
+ oldest: string | null;
19
+ };
20
+ menstruation: {
21
+ count: number;
22
+ oldest: string | null;
23
+ };
24
+ sleep: {
25
+ count: number;
26
+ oldest: string | null;
27
+ };
28
+ activities: {
29
+ count: number;
30
+ oldest: string | null;
31
+ };
32
+ nutrition: {
33
+ count: number;
34
+ oldest: string | null;
35
+ };
36
+ plannedWorkouts: {
37
+ count: number;
38
+ oldest: string | null;
39
+ };
40
+ } | undefined;
11
41
  userId: string;
12
42
  provider: string;
13
43
  }[]>>;
@@ -23,6 +53,36 @@ export declare const getConnectionByProvider: import("convex/server").Registered
23
53
  providerUserId?: string | undefined;
24
54
  active?: boolean | undefined;
25
55
  lastDataUpdate?: string | undefined;
56
+ stats?: {
57
+ body: {
58
+ count: number;
59
+ oldest: string | null;
60
+ };
61
+ daily: {
62
+ count: number;
63
+ oldest: string | null;
64
+ };
65
+ menstruation: {
66
+ count: number;
67
+ oldest: string | null;
68
+ };
69
+ sleep: {
70
+ count: number;
71
+ oldest: string | null;
72
+ };
73
+ activities: {
74
+ count: number;
75
+ oldest: string | null;
76
+ };
77
+ nutrition: {
78
+ count: number;
79
+ oldest: string | null;
80
+ };
81
+ plannedWorkouts: {
82
+ count: number;
83
+ oldest: string | null;
84
+ };
85
+ } | undefined;
26
86
  userId: string;
27
87
  provider: string;
28
88
  } | null>>;
@@ -39,6 +99,36 @@ export declare const getConnectionByProviderUserId: import("convex/server").Regi
39
99
  providerUserId?: string | undefined;
40
100
  active?: boolean | undefined;
41
101
  lastDataUpdate?: string | undefined;
102
+ stats?: {
103
+ body: {
104
+ count: number;
105
+ oldest: string | null;
106
+ };
107
+ daily: {
108
+ count: number;
109
+ oldest: string | null;
110
+ };
111
+ menstruation: {
112
+ count: number;
113
+ oldest: string | null;
114
+ };
115
+ sleep: {
116
+ count: number;
117
+ oldest: string | null;
118
+ };
119
+ activities: {
120
+ count: number;
121
+ oldest: string | null;
122
+ };
123
+ nutrition: {
124
+ count: number;
125
+ oldest: string | null;
126
+ };
127
+ plannedWorkouts: {
128
+ count: number;
129
+ oldest: string | null;
130
+ };
131
+ } | undefined;
42
132
  userId: string;
43
133
  provider: string;
44
134
  } | null>>;
@@ -1 +1 @@
1
- {"version":3,"file":"private.d.ts","sourceRoot":"","sources":["../../src/component/private.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAO,EAAE,EAAE,MAAM,2BAA2B,CAAC;AAMzD;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;KAM/B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;UAalC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,6BAA6B;;;;;;;;;;;UAexC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;iBAU/B,CAAC;AAMH,eAAO,MAAM,iBAAiB;;;;;iBAe5B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;UAoB1B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;iBAa7B,CAAC;AAIH;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;iBA4BtB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;UAqBpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,YAAY;;iBAgBvB,CAAC;AAkBH,eAAO,MAAM,+BAA+B;;;;;;kBAQ1B,EAAE,CAAC,aAAa,CAAC;iBAClB,MAAM;GAwErB,CAAC"}
1
+ {"version":3,"file":"private.d.ts","sourceRoot":"","sources":["../../src/component/private.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAO,EAAE,EAAE,MAAM,2BAA2B,CAAC;AAMzD;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAM/B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAalC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAexC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;iBAU/B,CAAC;AAMH,eAAO,MAAM,iBAAiB;;;;;iBAe5B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;UAoB1B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;iBAa7B,CAAC;AAIH;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;iBA4BtB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;UAqBpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,YAAY;;iBAgBvB,CAAC;AAkBH,eAAO,MAAM,+BAA+B;;;;;;kBAQ1B,EAAE,CAAC,aAAa,CAAC;iBAClB,MAAM;GAwErB,CAAC"}
@@ -1,3 +1,4 @@
1
+ import type { Id } from "./_generated/dataModel.js";
1
2
  /**
2
3
  * Connect a user to a wearable provider.
3
4
  *
@@ -30,6 +31,36 @@ export declare const getConnection: import("convex/server").RegisteredQuery<"pub
30
31
  providerUserId?: string | undefined;
31
32
  active?: boolean | undefined;
32
33
  lastDataUpdate?: string | undefined;
34
+ stats?: {
35
+ body: {
36
+ count: number;
37
+ oldest: string | null;
38
+ };
39
+ daily: {
40
+ count: number;
41
+ oldest: string | null;
42
+ };
43
+ menstruation: {
44
+ count: number;
45
+ oldest: string | null;
46
+ };
47
+ sleep: {
48
+ count: number;
49
+ oldest: string | null;
50
+ };
51
+ activities: {
52
+ count: number;
53
+ oldest: string | null;
54
+ };
55
+ nutrition: {
56
+ count: number;
57
+ oldest: string | null;
58
+ };
59
+ plannedWorkouts: {
60
+ count: number;
61
+ oldest: string | null;
62
+ };
63
+ } | undefined;
33
64
  userId: string;
34
65
  provider: string;
35
66
  } | null>>;
@@ -46,9 +77,60 @@ export declare const getConnectionByProvider: import("convex/server").Registered
46
77
  providerUserId?: string | undefined;
47
78
  active?: boolean | undefined;
48
79
  lastDataUpdate?: string | undefined;
80
+ stats?: {
81
+ body: {
82
+ count: number;
83
+ oldest: string | null;
84
+ };
85
+ daily: {
86
+ count: number;
87
+ oldest: string | null;
88
+ };
89
+ menstruation: {
90
+ count: number;
91
+ oldest: string | null;
92
+ };
93
+ sleep: {
94
+ count: number;
95
+ oldest: string | null;
96
+ };
97
+ activities: {
98
+ count: number;
99
+ oldest: string | null;
100
+ };
101
+ nutrition: {
102
+ count: number;
103
+ oldest: string | null;
104
+ };
105
+ plannedWorkouts: {
106
+ count: number;
107
+ oldest: string | null;
108
+ };
109
+ } | undefined;
49
110
  userId: string;
50
111
  provider: string;
51
112
  } | null>>;
113
+ /**
114
+ * Get per-table row counts and oldest-data timestamps for a user–provider pair.
115
+ *
116
+ * Stats are denormalized onto the connection document and maintained
117
+ * automatically by ingest mutations, so this is an O(1) read regardless of
118
+ * how much data has been ingested.
119
+ *
120
+ * `oldest` is a lower bound on the earliest `metadata.start_time` (or
121
+ * `metadata.planned_date` for planned workouts). It can theoretically go
122
+ * stale if a provider mutates `start_time` on an existing record to a later
123
+ * value — this would also break the existing dedup strategy, so treat it as
124
+ * pathological.
125
+ *
126
+ * Returns `null` if the user has never connected to that provider. When a
127
+ * connection exists but no data has been ingested, every table returns
128
+ * `{ count: 0, oldest: null }` — callers never need to guard optional keys.
129
+ */
130
+ export declare const getProviderStats: import("convex/server").RegisteredQuery<"public", {
131
+ userId: string;
132
+ provider: string;
133
+ }, Promise<import("./utils.js").ConnectionStats | null>>;
52
134
  /**
53
135
  * List all connections for a user (active and inactive).
54
136
  */
@@ -60,6 +142,36 @@ export declare const listConnections: import("convex/server").RegisteredQuery<"p
60
142
  providerUserId?: string | undefined;
61
143
  active?: boolean | undefined;
62
144
  lastDataUpdate?: string | undefined;
145
+ stats?: {
146
+ body: {
147
+ count: number;
148
+ oldest: string | null;
149
+ };
150
+ daily: {
151
+ count: number;
152
+ oldest: string | null;
153
+ };
154
+ menstruation: {
155
+ count: number;
156
+ oldest: string | null;
157
+ };
158
+ sleep: {
159
+ count: number;
160
+ oldest: string | null;
161
+ };
162
+ activities: {
163
+ count: number;
164
+ oldest: string | null;
165
+ };
166
+ nutrition: {
167
+ count: number;
168
+ oldest: string | null;
169
+ };
170
+ plannedWorkouts: {
171
+ count: number;
172
+ oldest: string | null;
173
+ };
174
+ } | undefined;
63
175
  userId: string;
64
176
  provider: string;
65
177
  }[]>>;
@@ -336,7 +448,7 @@ export declare const ingestActivity: import("convex/server").RegisteredMutation<
336
448
  summary_id: string;
337
449
  upload_type: number;
338
450
  };
339
- }, Promise<import("convex/values").GenericId<"activities">>>;
451
+ }, Promise<Id<"activities">>>;
340
452
  /**
341
453
  * Ingest a sleep session record.
342
454
  *
@@ -487,7 +599,7 @@ export declare const ingestSleep: import("convex/server").RegisteredMutation<"pu
487
599
  end_time: string;
488
600
  upload_type: number;
489
601
  };
490
- }, Promise<import("convex/values").GenericId<"sleep">>>;
602
+ }, Promise<Id<"sleep">>>;
491
603
  /**
492
604
  * Ingest a body metrics record.
493
605
  *
@@ -675,7 +787,7 @@ export declare const ingestBody: import("convex/server").RegisteredMutation<"pub
675
787
  start_time: string;
676
788
  end_time: string;
677
789
  };
678
- }, Promise<import("convex/values").GenericId<"body">>>;
790
+ }, Promise<Id<"body">>>;
679
791
  /**
680
792
  * Ingest a daily activity summary record.
681
793
  *
@@ -909,7 +1021,7 @@ export declare const ingestDaily: import("convex/server").RegisteredMutation<"pu
909
1021
  end_time: string;
910
1022
  upload_type: number;
911
1023
  };
912
- }, Promise<import("convex/values").GenericId<"daily">>>;
1024
+ }, Promise<Id<"daily">>>;
913
1025
  /**
914
1026
  * Ingest a nutrition record.
915
1027
  *
@@ -1067,7 +1179,7 @@ export declare const ingestNutrition: import("convex/server").RegisteredMutation
1067
1179
  start_time: string;
1068
1180
  end_time: string;
1069
1181
  };
1070
- }, Promise<import("convex/values").GenericId<"nutrition">>>;
1182
+ }, Promise<Id<"nutrition">>>;
1071
1183
  /**
1072
1184
  * Ingest a menstruation record.
1073
1185
  *
@@ -3390,7 +3502,7 @@ export declare const ingestPlannedWorkout: import("convex/server").RegisteredMut
3390
3502
  provider_workout_id?: string | undefined;
3391
3503
  provider_schedule_id?: string | undefined;
3392
3504
  };
3393
- }, Promise<import("convex/values").GenericId<"plannedWorkouts">>>;
3505
+ }, Promise<Id<"plannedWorkouts">>>;
3394
3506
  /**
3395
3507
  * List planned workout records for a user, optionally filtered by planned date range.
3396
3508
  *
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/component/public.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/component/public.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,8 @@ import { sleepValidator } from "./validators/sleep.js";
9
9
  import { menstruationValidator } from "./validators/menstruation.js";
10
10
  import { nutritionValidator } from "./validators/nutrition.js";
11
11
  import { plannedWorkoutValidator } from "./validators/plannedWorkout.js";
12
+ import { statsValidator } from "./validators/connection.js";
13
+ import { normalizeStats, updateStatsOnIngest } from "./utils.js";
12
14
  // ─── Return Validators ──────────────────────────────────────────────────────
13
15
  const connectionDoc = v.object({
14
16
  _id: v.id("connections"),
@@ -18,6 +20,7 @@ const connectionDoc = v.object({
18
20
  providerUserId: v.optional(v.string()),
19
21
  active: v.optional(v.boolean()),
20
22
  lastDataUpdate: v.optional(v.string()),
23
+ stats: v.optional(statsValidator),
21
24
  });
22
25
  // ─── Connect / Disconnect ───────────────────────────────────────────────────
23
26
  /**
@@ -110,6 +113,39 @@ export const getConnectionByProvider = query({
110
113
  .first();
111
114
  },
112
115
  });
116
+ /**
117
+ * Get per-table row counts and oldest-data timestamps for a user–provider pair.
118
+ *
119
+ * Stats are denormalized onto the connection document and maintained
120
+ * automatically by ingest mutations, so this is an O(1) read regardless of
121
+ * how much data has been ingested.
122
+ *
123
+ * `oldest` is a lower bound on the earliest `metadata.start_time` (or
124
+ * `metadata.planned_date` for planned workouts). It can theoretically go
125
+ * stale if a provider mutates `start_time` on an existing record to a later
126
+ * value — this would also break the existing dedup strategy, so treat it as
127
+ * pathological.
128
+ *
129
+ * Returns `null` if the user has never connected to that provider. When a
130
+ * connection exists but no data has been ingested, every table returns
131
+ * `{ count: 0, oldest: null }` — callers never need to guard optional keys.
132
+ */
133
+ export const getProviderStats = query({
134
+ args: {
135
+ userId: v.string(),
136
+ provider: v.string(),
137
+ },
138
+ returns: v.union(v.null(), statsValidator),
139
+ handler: async (ctx, args) => {
140
+ const connection = await ctx.db
141
+ .query("connections")
142
+ .withIndex("by_userId_provider", (q) => q.eq("userId", args.userId).eq("provider", args.provider))
143
+ .first();
144
+ if (!connection)
145
+ return null;
146
+ return normalizeStats(connection.stats);
147
+ },
148
+ });
113
149
  /**
114
150
  * List all connections for a user (active and inactive).
115
151
  */
@@ -185,11 +221,16 @@ export const ingestActivity = mutation({
185
221
  .eq("connectionId", args.connectionId)
186
222
  .eq("metadata.summary_id", args.metadata.summary_id))
187
223
  .first();
224
+ let id;
188
225
  if (existing) {
189
226
  await ctx.db.patch(existing._id, args);
190
- return existing._id;
227
+ id = existing._id;
228
+ }
229
+ else {
230
+ id = await ctx.db.insert("activities", args);
191
231
  }
192
- return await ctx.db.insert("activities", args);
232
+ await updateStatsOnIngest(ctx, args.connectionId, "activities", args.metadata.start_time, !existing);
233
+ return id;
193
234
  },
194
235
  });
195
236
  /**
@@ -203,19 +244,25 @@ export const ingestSleep = mutation({
203
244
  returns: v.id("sleep"),
204
245
  handler: async (ctx, args) => {
205
246
  const summaryId = args.metadata.summary_id;
247
+ let existing = null;
206
248
  if (summaryId) {
207
- const existing = await ctx.db
249
+ existing = await ctx.db
208
250
  .query("sleep")
209
251
  .withIndex("by_connectionId_summaryId", (q) => q
210
252
  .eq("connectionId", args.connectionId)
211
253
  .eq("metadata.summary_id", summaryId))
212
254
  .first();
213
- if (existing) {
214
- await ctx.db.patch(existing._id, args);
215
- return existing._id;
216
- }
217
255
  }
218
- return await ctx.db.insert("sleep", args);
256
+ let id;
257
+ if (existing) {
258
+ await ctx.db.patch(existing._id, args);
259
+ id = existing._id;
260
+ }
261
+ else {
262
+ id = await ctx.db.insert("sleep", args);
263
+ }
264
+ await updateStatsOnIngest(ctx, args.connectionId, "sleep", args.metadata.start_time, !existing);
265
+ return id;
219
266
  },
220
267
  });
221
268
  /**
@@ -234,11 +281,16 @@ export const ingestBody = mutation({
234
281
  .eq("metadata.start_time", args.metadata.start_time)
235
282
  .eq("metadata.end_time", args.metadata.end_time))
236
283
  .first();
284
+ let id;
237
285
  if (existing) {
238
286
  await ctx.db.patch(existing._id, args);
239
- return existing._id;
287
+ id = existing._id;
240
288
  }
241
- return await ctx.db.insert("body", args);
289
+ else {
290
+ id = await ctx.db.insert("body", args);
291
+ }
292
+ await updateStatsOnIngest(ctx, args.connectionId, "body", args.metadata.start_time, !existing);
293
+ return id;
242
294
  },
243
295
  });
244
296
  /**
@@ -257,11 +309,16 @@ export const ingestDaily = mutation({
257
309
  .eq("metadata.start_time", args.metadata.start_time)
258
310
  .eq("metadata.end_time", args.metadata.end_time))
259
311
  .first();
312
+ let id;
260
313
  if (existing) {
261
314
  await ctx.db.patch(existing._id, args);
262
- return existing._id;
315
+ id = existing._id;
263
316
  }
264
- return await ctx.db.insert("daily", args);
317
+ else {
318
+ id = await ctx.db.insert("daily", args);
319
+ }
320
+ await updateStatsOnIngest(ctx, args.connectionId, "daily", args.metadata.start_time, !existing);
321
+ return id;
265
322
  },
266
323
  });
267
324
  /**
@@ -280,11 +337,16 @@ export const ingestNutrition = mutation({
280
337
  .eq("metadata.start_time", args.metadata.start_time)
281
338
  .eq("metadata.end_time", args.metadata.end_time))
282
339
  .first();
340
+ let id;
283
341
  if (existing) {
284
342
  await ctx.db.patch(existing._id, args);
285
- return existing._id;
343
+ id = existing._id;
286
344
  }
287
- return await ctx.db.insert("nutrition", args);
345
+ else {
346
+ id = await ctx.db.insert("nutrition", args);
347
+ }
348
+ await updateStatsOnIngest(ctx, args.connectionId, "nutrition", args.metadata.start_time, !existing);
349
+ return id;
288
350
  },
289
351
  });
290
352
  /**
@@ -296,7 +358,9 @@ export const ingestMenstruation = mutation({
296
358
  args: menstruationValidator,
297
359
  returns: v.id("menstruation"),
298
360
  handler: async (ctx, args) => {
299
- return await ctx.db.insert("menstruation", args);
361
+ const id = await ctx.db.insert("menstruation", args);
362
+ await updateStatsOnIngest(ctx, args.connectionId, "menstruation", args.metadata.start_time, true);
363
+ return id;
300
364
  },
301
365
  });
302
366
  /**
@@ -727,18 +791,24 @@ export const ingestPlannedWorkout = mutation({
727
791
  returns: v.id("plannedWorkouts"),
728
792
  handler: async (ctx, args) => {
729
793
  const metadataId = args.metadata.id;
794
+ let existing = null;
730
795
  if (metadataId) {
731
796
  const results = await ctx.db
732
797
  .query("plannedWorkouts")
733
798
  .withIndex("by_connectionId", (q) => q.eq("connectionId", args.connectionId))
734
799
  .collect();
735
- const existing = results.find((r) => r.metadata.id === metadataId);
736
- if (existing) {
737
- await ctx.db.patch(existing._id, args);
738
- return existing._id;
739
- }
800
+ existing = results.find((r) => r.metadata.id === metadataId) ?? null;
801
+ }
802
+ let id;
803
+ if (existing) {
804
+ await ctx.db.patch(existing._id, args);
805
+ id = existing._id;
806
+ }
807
+ else {
808
+ id = await ctx.db.insert("plannedWorkouts", args);
740
809
  }
741
- return await ctx.db.insert("plannedWorkouts", args);
810
+ await updateStatsOnIngest(ctx, args.connectionId, "plannedWorkouts", args.metadata.planned_date, !existing);
811
+ return id;
742
812
  },
743
813
  });
744
814
  /**