@clipin/convex-wearables 0.2.0 → 0.3.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 (48) hide show
  1. package/dist/client/index.d.ts +3 -0
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +6 -0
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/client/types.d.ts +13 -1
  6. package/dist/client/types.d.ts.map +1 -1
  7. package/dist/client/types.js +1 -1
  8. package/dist/client/types.js.map +1 -1
  9. package/dist/component/garminBackfill.d.ts +9 -1
  10. package/dist/component/garminBackfill.d.ts.map +1 -1
  11. package/dist/component/garminBackfill.js +27 -6
  12. package/dist/component/garminBackfill.js.map +1 -1
  13. package/dist/component/garminWebhooks.d.ts +44 -0
  14. package/dist/component/garminWebhooks.d.ts.map +1 -1
  15. package/dist/component/garminWebhooks.js +248 -14
  16. package/dist/component/garminWebhooks.js.map +1 -1
  17. package/dist/component/oauthActions.d.ts.map +1 -1
  18. package/dist/component/oauthActions.js +5 -0
  19. package/dist/component/oauthActions.js.map +1 -1
  20. package/dist/component/providers/garmin.d.ts +4 -0
  21. package/dist/component/providers/garmin.d.ts.map +1 -1
  22. package/dist/component/providers/garmin.js +23 -8
  23. package/dist/component/providers/garmin.js.map +1 -1
  24. package/dist/component/schema.d.ts +27 -0
  25. package/dist/component/schema.d.ts.map +1 -1
  26. package/dist/component/schema.js +19 -0
  27. package/dist/component/schema.js.map +1 -1
  28. package/dist/component/sdkPush.d.ts +24 -0
  29. package/dist/component/sdkPush.d.ts.map +1 -1
  30. package/dist/component/sdkPush.js +101 -6
  31. package/dist/component/sdkPush.js.map +1 -1
  32. package/dist/component/syncWorkflow.d.ts.map +1 -1
  33. package/dist/component/syncWorkflow.js +5 -3
  34. package/dist/component/syncWorkflow.js.map +1 -1
  35. package/dist/test.d.ts +27 -0
  36. package/dist/test.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/client/index.ts +13 -1
  39. package/src/client/types.ts +13 -1
  40. package/src/component/garminBackfill.ts +33 -6
  41. package/src/component/garminWebhooks.test.ts +112 -9
  42. package/src/component/garminWebhooks.ts +279 -14
  43. package/src/component/oauthActions.ts +6 -0
  44. package/src/component/providers/garmin.ts +36 -12
  45. package/src/component/schema.ts +25 -0
  46. package/src/component/sdkPush.test.ts +54 -0
  47. package/src/component/sdkPush.ts +120 -6
  48. package/src/component/syncWorkflow.ts +5 -3
@@ -13,6 +13,14 @@ const MAX_DATA_POINTS_PER_REQUEST = 10000;
13
13
  const MAX_SUMMARIES_PER_REQUEST = 1000;
14
14
  const SERIES_TYPE_ALIASES = {
15
15
  hrv_rmssd: "heart_rate_variability_rmssd",
16
+ POWER: "power",
17
+ power: "power",
18
+ SPEED: "speed",
19
+ speed: "speed",
20
+ CYCLING_PEDALING_CADENCE: "cadence",
21
+ cycling_pedaling_cadence: "cadence",
22
+ TOTAL_CALORIES_BURNED: "total_calories",
23
+ total_calories_burned: "total_calories",
16
24
  } as const;
17
25
  const validSeriesTypes = new Set(Object.keys(SERIES_TYPES));
18
26
 
@@ -22,6 +30,10 @@ const deviceMetadataValidator = v.object({
22
30
  source: v.optional(v.string()),
23
31
  deviceType: v.optional(v.string()),
24
32
  originalSourceName: v.optional(v.string()),
33
+ appId: v.optional(v.string()),
34
+ app_id: v.optional(v.string()),
35
+ bundleIdentifier: v.optional(v.string()),
36
+ bundle_identifier: v.optional(v.string()),
25
37
  });
26
38
 
27
39
  const sourceMetadataValidator = v.object({
@@ -30,6 +42,10 @@ const sourceMetadataValidator = v.object({
30
42
  source: v.optional(v.string()),
31
43
  deviceType: v.optional(v.string()),
32
44
  originalSourceName: v.optional(v.string()),
45
+ appId: v.optional(v.string()),
46
+ app_id: v.optional(v.string()),
47
+ bundleIdentifier: v.optional(v.string()),
48
+ bundle_identifier: v.optional(v.string()),
33
49
  });
34
50
 
35
51
  const sdkEventValidator = v.object({
@@ -76,6 +92,10 @@ const sdkEventValidator = v.object({
76
92
  source: v.optional(v.string()),
77
93
  deviceType: v.optional(v.string()),
78
94
  originalSourceName: v.optional(v.string()),
95
+ appId: v.optional(v.string()),
96
+ app_id: v.optional(v.string()),
97
+ bundleIdentifier: v.optional(v.string()),
98
+ bundle_identifier: v.optional(v.string()),
79
99
  });
80
100
 
81
101
  const sdkDataPointValidator = v.object({
@@ -88,6 +108,10 @@ const sdkDataPointValidator = v.object({
88
108
  source: v.optional(v.string()),
89
109
  deviceType: v.optional(v.string()),
90
110
  originalSourceName: v.optional(v.string()),
111
+ appId: v.optional(v.string()),
112
+ app_id: v.optional(v.string()),
113
+ bundleIdentifier: v.optional(v.string()),
114
+ bundle_identifier: v.optional(v.string()),
91
115
  });
92
116
 
93
117
  const sdkSummaryValidator = v.object({
@@ -95,6 +119,10 @@ const sdkSummaryValidator = v.object({
95
119
  category: v.string(),
96
120
  source: v.optional(v.string()),
97
121
  originalSourceName: v.optional(v.string()),
122
+ appId: v.optional(v.string()),
123
+ app_id: v.optional(v.string()),
124
+ bundleIdentifier: v.optional(v.string()),
125
+ bundle_identifier: v.optional(v.string()),
98
126
  totalSteps: v.optional(v.number()),
99
127
  totalCalories: v.optional(v.number()),
100
128
  activeCalories: v.optional(v.number()),
@@ -133,6 +161,10 @@ type SourceMetadata = {
133
161
  source?: string;
134
162
  deviceType?: string;
135
163
  originalSourceName?: string;
164
+ appId?: string;
165
+ app_id?: string;
166
+ bundleIdentifier?: string;
167
+ bundle_identifier?: string;
136
168
  };
137
169
 
138
170
  type DataSourceCache = Map<string, Id<"dataSources">>;
@@ -158,7 +190,17 @@ function resolveSourceMetadata(
158
190
  softwareVersion: item.softwareVersion ?? defaults?.softwareVersion,
159
191
  source: item.source ?? defaults?.source,
160
192
  deviceType: item.deviceType ?? defaults?.deviceType,
161
- originalSourceName: item.originalSourceName ?? defaults?.originalSourceName,
193
+ originalSourceName:
194
+ item.originalSourceName ??
195
+ item.appId ??
196
+ item.app_id ??
197
+ item.bundleIdentifier ??
198
+ item.bundle_identifier ??
199
+ defaults?.originalSourceName,
200
+ appId: item.appId ?? defaults?.appId,
201
+ app_id: item.app_id ?? defaults?.app_id,
202
+ bundleIdentifier: item.bundleIdentifier ?? defaults?.bundleIdentifier,
203
+ bundle_identifier: item.bundle_identifier ?? defaults?.bundle_identifier,
162
204
  };
163
205
  }
164
206
 
@@ -245,6 +287,10 @@ export const ingestNormalizedPayload = action({
245
287
  source: event.source,
246
288
  deviceType: event.deviceType,
247
289
  originalSourceName: event.originalSourceName,
290
+ appId: event.appId,
291
+ app_id: event.app_id,
292
+ bundleIdentifier: event.bundleIdentifier,
293
+ bundle_identifier: event.bundle_identifier,
248
294
  });
249
295
  const dataSourceId = await ensureDataSource(
250
296
  ctx,
@@ -261,7 +307,7 @@ export const ingestNormalizedPayload = action({
261
307
  dataSourceId,
262
308
  userId: args.userId,
263
309
  category: event.category,
264
- type: event.type,
310
+ type: event.category === "workout" ? normalizeWorkoutType(event.type) : event.type,
265
311
  sourceName: event.sourceName ?? defaultSourceName(args.provider),
266
312
  durationSeconds: event.durationSeconds,
267
313
  startDatetime: event.startDatetime,
@@ -322,6 +368,10 @@ export const ingestNormalizedPayload = action({
322
368
  source: point.source,
323
369
  deviceType: point.deviceType,
324
370
  originalSourceName: point.originalSourceName,
371
+ appId: point.appId,
372
+ app_id: point.app_id,
373
+ bundleIdentifier: point.bundleIdentifier,
374
+ bundle_identifier: point.bundle_identifier,
325
375
  });
326
376
  const dataSourceId = await ensureDataSource(
327
377
  ctx,
@@ -360,12 +410,27 @@ export const ingestNormalizedPayload = action({
360
410
  }
361
411
 
362
412
  for (const summary of summaries) {
413
+ const {
414
+ appId,
415
+ app_id,
416
+ bundleIdentifier,
417
+ bundle_identifier,
418
+ originalSourceName,
419
+ source,
420
+ ...summaryMetrics
421
+ } = summary;
363
422
  await ctx.runMutation(internal.summaries.upsert, {
364
423
  userId: args.userId,
365
424
  provider: args.provider,
366
- ...summary,
367
- source: summary.source ?? defaultMetadata.source,
368
- originalSourceName: summary.originalSourceName ?? defaultMetadata.originalSourceName,
425
+ ...summaryMetrics,
426
+ source: source ?? defaultMetadata.source,
427
+ originalSourceName:
428
+ originalSourceName ??
429
+ appId ??
430
+ app_id ??
431
+ bundleIdentifier ??
432
+ bundle_identifier ??
433
+ defaultMetadata.originalSourceName,
369
434
  });
370
435
  }
371
436
 
@@ -390,6 +455,10 @@ function sourceMetadataFromDevice(
390
455
  source?: string;
391
456
  deviceType?: string;
392
457
  originalSourceName?: string;
458
+ appId?: string;
459
+ app_id?: string;
460
+ bundleIdentifier?: string;
461
+ bundle_identifier?: string;
393
462
  }
394
463
  | undefined,
395
464
  ): SourceMetadata | undefined {
@@ -399,7 +468,16 @@ function sourceMetadataFromDevice(
399
468
  softwareVersion: device.softwareVersion,
400
469
  source: device.source,
401
470
  deviceType: device.deviceType,
402
- originalSourceName: device.originalSourceName,
471
+ originalSourceName:
472
+ device.originalSourceName ??
473
+ device.appId ??
474
+ device.app_id ??
475
+ device.bundleIdentifier ??
476
+ device.bundle_identifier,
477
+ appId: device.appId,
478
+ app_id: device.app_id,
479
+ bundleIdentifier: device.bundleIdentifier,
480
+ bundle_identifier: device.bundle_identifier,
403
481
  };
404
482
  }
405
483
 
@@ -412,6 +490,42 @@ function normalizeSeriesType(seriesType: string): string {
412
490
  return normalized;
413
491
  }
414
492
 
493
+ const SDK_WORKOUT_TYPE_ALIASES: Record<string, string> = {
494
+ cycling_stationary: "indoor_cycling",
495
+ boot_camp: "cardio_training",
496
+ calisthenics: "strength_training",
497
+ dancing: "dance",
498
+ exercise_class: "cardio_training",
499
+ football_american: "american_football",
500
+ football_australian: "football",
501
+ frisbee_disc: "disc_sports",
502
+ guided_breathing: "meditation",
503
+ ice_hockey: "hockey",
504
+ ice_skating: "ice_skating",
505
+ paddling: "paddling",
506
+ paragliding: "paragliding",
507
+ rock_climbing: "rock_climbing",
508
+ roller_hockey: "hockey",
509
+ rowing_machine: "rowing_machine",
510
+ running_treadmill: "treadmill",
511
+ scuba_diving: "diving",
512
+ skiing: "alpine_skiing",
513
+ snowshoeing: "snowshoeing",
514
+ stair_climbing_machine: "stair_climbing",
515
+ stretching: "stretching",
516
+ swimming_open_water: "open_water_swimming",
517
+ swimming_pool: "pool_swimming",
518
+ weightlifting: "strength_training",
519
+ wheelchair: "wheelchair",
520
+ };
521
+
522
+ function normalizeWorkoutType(type: string | undefined): string | undefined {
523
+ if (type === undefined) return undefined;
524
+ const normalized = type.trim().toLowerCase();
525
+ if (!normalized) return undefined;
526
+ return SDK_WORKOUT_TYPE_ALIASES[normalized] ?? normalized;
527
+ }
528
+
415
529
  function assertPayloadWithinLimits(args: {
416
530
  events: unknown[];
417
531
  dataPoints: unknown[];
@@ -525,9 +525,11 @@ export const runConnectionSync = durableWorkflow.define({
525
525
  }
526
526
  }
527
527
 
528
- await step.runMutation(internal.connections.markSynced, {
529
- connectionId: connection._id,
530
- });
528
+ if (processed > 0) {
529
+ await step.runMutation(internal.connections.markSynced, {
530
+ connectionId: connection._id,
531
+ });
532
+ }
531
533
 
532
534
  return { recordsProcessed: processed };
533
535
  },