@clipin/convex-wearables 0.2.1 → 0.4.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.
- package/README.md +30 -4
- package/dist/client/index.d.ts +18 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +26 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/providerCapabilities.d.ts +12 -0
- package/dist/client/providerCapabilities.d.ts.map +1 -0
- package/dist/client/providerCapabilities.js +163 -0
- package/dist/client/providerCapabilities.js.map +1 -0
- package/dist/client/types.d.ts +61 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js +1 -1
- package/dist/client/types.js.map +1 -1
- package/dist/component/garminBackfill.d.ts +9 -1
- package/dist/component/garminBackfill.d.ts.map +1 -1
- package/dist/component/garminBackfill.js +27 -6
- package/dist/component/garminBackfill.js.map +1 -1
- package/dist/component/garminWebhooks.d.ts.map +1 -1
- package/dist/component/garminWebhooks.js +26 -13
- package/dist/component/garminWebhooks.js.map +1 -1
- package/dist/component/providers/garmin.d.ts +4 -0
- package/dist/component/providers/garmin.d.ts.map +1 -1
- package/dist/component/providers/garmin.js +23 -8
- package/dist/component/providers/garmin.js.map +1 -1
- package/dist/component/sdkPush.d.ts +24 -0
- package/dist/component/sdkPush.d.ts.map +1 -1
- package/dist/component/sdkPush.js +101 -6
- package/dist/component/sdkPush.js.map +1 -1
- package/dist/component/syncWorkflow.d.ts.map +1 -1
- package/dist/component/syncWorkflow.js +5 -3
- package/dist/component/syncWorkflow.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.test.ts +41 -0
- package/src/client/index.ts +57 -1
- package/src/client/providerCapabilities.ts +182 -0
- package/src/client/types.ts +64 -1
- package/src/component/garminBackfill.ts +33 -6
- package/src/component/garminWebhooks.test.ts +24 -7
- package/src/component/garminWebhooks.ts +32 -13
- package/src/component/providers/garmin.ts +36 -12
- package/src/component/sdkPush.test.ts +54 -0
- package/src/component/sdkPush.ts +120 -6
- package/src/component/syncWorkflow.ts +5 -3
|
@@ -148,6 +148,7 @@ export interface GarminHrv {
|
|
|
148
148
|
userId: string;
|
|
149
149
|
summaryId?: string;
|
|
150
150
|
startTimeInSeconds: number;
|
|
151
|
+
startTimeOffsetInSeconds?: number;
|
|
151
152
|
calendarDate?: string;
|
|
152
153
|
lastNightAvg?: number;
|
|
153
154
|
hrvValues?: Record<string, number>;
|
|
@@ -258,12 +259,15 @@ export interface GarminPushPayload {
|
|
|
258
259
|
bodyComps?: GarminBodyComp[];
|
|
259
260
|
hrv?: GarminHrv[];
|
|
260
261
|
stressDetails?: GarminStressDetails[];
|
|
262
|
+
allDayRespiration?: GarminRespiration[];
|
|
261
263
|
respiration?: GarminRespiration[];
|
|
262
264
|
pulseOx?: GarminPulseOx[];
|
|
265
|
+
pulseox?: GarminPulseOx[];
|
|
263
266
|
bloodPressures?: GarminBloodPressure[];
|
|
264
267
|
userMetrics?: GarminUserMetrics[];
|
|
265
268
|
skinTemp?: GarminSkinTemp[];
|
|
266
269
|
healthSnapshot?: GarminHealthSnapshot[];
|
|
270
|
+
moveIQActivities?: GarminMoveIQ[];
|
|
267
271
|
moveiq?: GarminMoveIQ[];
|
|
268
272
|
menstrualCycleTracking?: GarminMCTSummary[];
|
|
269
273
|
mct?: GarminMCTSummary[];
|
|
@@ -282,6 +286,13 @@ function isoDateFromCalendarDate(
|
|
|
282
286
|
return calendarDate ?? isoDateFromTimestamp(fallbackTimestampMs);
|
|
283
287
|
}
|
|
284
288
|
|
|
289
|
+
function isoDateFromTimestampWithOffset(timestampMs: number, offsetSeconds?: number): string {
|
|
290
|
+
if (offsetSeconds === undefined) {
|
|
291
|
+
return isoDateFromTimestamp(timestampMs);
|
|
292
|
+
}
|
|
293
|
+
return isoDateFromTimestamp(timestampMs + offsetSeconds * 1000);
|
|
294
|
+
}
|
|
295
|
+
|
|
285
296
|
function calendarDateToMiddayTimestamp(calendarDate: string | undefined): number | null {
|
|
286
297
|
if (!calendarDate) {
|
|
287
298
|
return null;
|
|
@@ -541,8 +552,9 @@ export function normalizeSleep(sleep: GarminSleep): NormalizedEvent {
|
|
|
541
552
|
}
|
|
542
553
|
|
|
543
554
|
export function normalizeSleepSummary(sleep: GarminSleep): NormalizedDailySummary {
|
|
555
|
+
const endMs = (sleep.startTimeInSeconds + sleep.durationInSeconds) * 1000;
|
|
544
556
|
return {
|
|
545
|
-
date:
|
|
557
|
+
date: isoDateFromTimestampWithOffset(endMs, sleep.startTimeOffsetInSeconds),
|
|
546
558
|
category: "sleep",
|
|
547
559
|
sleepDurationMinutes: Math.floor(sleep.durationInSeconds / 60),
|
|
548
560
|
sleepEfficiency: sleep.overallSleepScore?.value,
|
|
@@ -734,7 +746,7 @@ export function normalizeHrvDataPoints(hrv: GarminHrv): NormalizedDataPoint[] {
|
|
|
734
746
|
const points: NormalizedDataPoint[] = [];
|
|
735
747
|
if (hrv.lastNightAvg != null) {
|
|
736
748
|
points.push({
|
|
737
|
-
seriesType: "
|
|
749
|
+
seriesType: "heart_rate_variability_rmssd",
|
|
738
750
|
recordedAt: hrv.startTimeInSeconds * 1000,
|
|
739
751
|
value: hrv.lastNightAvg,
|
|
740
752
|
externalId: hrv.summaryId,
|
|
@@ -745,7 +757,7 @@ export function normalizeHrvDataPoints(hrv: GarminHrv): NormalizedDataPoint[] {
|
|
|
745
757
|
...buildOffsetDataPoints(
|
|
746
758
|
hrv.hrvValues,
|
|
747
759
|
hrv.startTimeInSeconds,
|
|
748
|
-
"
|
|
760
|
+
"heart_rate_variability_rmssd",
|
|
749
761
|
hrv.summaryId,
|
|
750
762
|
),
|
|
751
763
|
);
|
|
@@ -761,7 +773,7 @@ export function normalizeHrvSummary(hrv: GarminHrv): NormalizedDailySummary | nu
|
|
|
761
773
|
return {
|
|
762
774
|
date: isoDateFromCalendarDate(hrv.calendarDate, hrv.startTimeInSeconds * 1000),
|
|
763
775
|
category: "recovery",
|
|
764
|
-
|
|
776
|
+
hrvRmssd: hrv.lastNightAvg,
|
|
765
777
|
};
|
|
766
778
|
}
|
|
767
779
|
|
|
@@ -1138,6 +1150,7 @@ export async function triggerBackfill(
|
|
|
1138
1150
|
startTimeSeconds: number,
|
|
1139
1151
|
endTimeSeconds: number,
|
|
1140
1152
|
): Promise<void> {
|
|
1153
|
+
const endpointDataType = normalizeBackfillDataType(dataType);
|
|
1141
1154
|
const validTypes = [
|
|
1142
1155
|
"activities",
|
|
1143
1156
|
"activityDetails",
|
|
@@ -1147,23 +1160,34 @@ export async function triggerBackfill(
|
|
|
1147
1160
|
"bodyComps",
|
|
1148
1161
|
"hrv",
|
|
1149
1162
|
"stressDetails",
|
|
1150
|
-
"
|
|
1163
|
+
"allDayRespiration",
|
|
1151
1164
|
"pulseOx",
|
|
1152
1165
|
"bloodPressures",
|
|
1153
1166
|
"userMetrics",
|
|
1154
1167
|
"skinTemp",
|
|
1155
1168
|
"healthSnapshot",
|
|
1156
|
-
"
|
|
1169
|
+
"moveIQActivities",
|
|
1157
1170
|
"mct",
|
|
1158
1171
|
];
|
|
1159
|
-
if (!validTypes.includes(
|
|
1172
|
+
if (!validTypes.includes(endpointDataType)) {
|
|
1160
1173
|
throw new Error(`Invalid backfill data type: ${dataType}`);
|
|
1161
1174
|
}
|
|
1162
1175
|
|
|
1163
|
-
await makeAuthenticatedRequest(
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1176
|
+
await makeAuthenticatedRequest(
|
|
1177
|
+
API_BASE,
|
|
1178
|
+
`/wellness-api/rest/backfill/${endpointDataType}`,
|
|
1179
|
+
accessToken,
|
|
1180
|
+
{
|
|
1181
|
+
params: {
|
|
1182
|
+
summaryStartTimeInSeconds: String(startTimeSeconds),
|
|
1183
|
+
summaryEndTimeInSeconds: String(endTimeSeconds),
|
|
1184
|
+
},
|
|
1167
1185
|
},
|
|
1168
|
-
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function normalizeBackfillDataType(dataType: string): string {
|
|
1190
|
+
if (dataType === "respiration") return "allDayRespiration";
|
|
1191
|
+
if (dataType === "moveiq") return "moveIQActivities";
|
|
1192
|
+
return dataType;
|
|
1169
1193
|
}
|
|
@@ -245,7 +245,17 @@ describe("sdkPush", () => {
|
|
|
245
245
|
model: "Pixel 9 Pro",
|
|
246
246
|
softwareVersion: "Android 16",
|
|
247
247
|
source: "health-connect",
|
|
248
|
+
appId: "com.thirdparty.writer",
|
|
248
249
|
},
|
|
250
|
+
events: [
|
|
251
|
+
{
|
|
252
|
+
category: "workout",
|
|
253
|
+
type: "CYCLING_STATIONARY",
|
|
254
|
+
startDatetime: Date.parse("2026-03-18T08:00:00Z"),
|
|
255
|
+
endDatetime: Date.parse("2026-03-18T08:45:00Z"),
|
|
256
|
+
externalId: "hc-cycling-1",
|
|
257
|
+
},
|
|
258
|
+
],
|
|
249
259
|
dataPoints: [
|
|
250
260
|
{
|
|
251
261
|
seriesType: "hrv_rmssd",
|
|
@@ -271,6 +281,30 @@ describe("sdkPush", () => {
|
|
|
271
281
|
value: 340,
|
|
272
282
|
externalId: "hc-active-calories-1",
|
|
273
283
|
},
|
|
284
|
+
{
|
|
285
|
+
seriesType: "POWER",
|
|
286
|
+
recordedAt: Date.parse("2026-03-18T12:03:00Z"),
|
|
287
|
+
value: 220,
|
|
288
|
+
externalId: "hc-power-1",
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
seriesType: "SPEED",
|
|
292
|
+
recordedAt: Date.parse("2026-03-18T12:04:00Z"),
|
|
293
|
+
value: 7.5,
|
|
294
|
+
externalId: "hc-speed-1",
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
seriesType: "CYCLING_PEDALING_CADENCE",
|
|
298
|
+
recordedAt: Date.parse("2026-03-18T12:05:00Z"),
|
|
299
|
+
value: 88,
|
|
300
|
+
externalId: "hc-cadence-1",
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
seriesType: "TOTAL_CALORIES_BURNED",
|
|
304
|
+
recordedAt: Date.parse("2026-03-18T12:06:00Z"),
|
|
305
|
+
value: 830,
|
|
306
|
+
externalId: "hc-total-calories-1",
|
|
307
|
+
},
|
|
274
308
|
],
|
|
275
309
|
dailySummaries: [
|
|
276
310
|
{
|
|
@@ -293,6 +327,22 @@ describe("sdkPush", () => {
|
|
|
293
327
|
deviceModel: "Pixel 9 Pro",
|
|
294
328
|
softwareVersion: "Android 16",
|
|
295
329
|
source: "health-connect",
|
|
330
|
+
originalSourceName: "com.thirdparty.writer",
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const events = await t.run(async (ctx) => {
|
|
334
|
+
return await ctx.db
|
|
335
|
+
.query("events")
|
|
336
|
+
.withIndex("by_user_category_time", (idx) =>
|
|
337
|
+
idx.eq("userId", "user-3").eq("category", "workout"),
|
|
338
|
+
)
|
|
339
|
+
.collect();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
expect(events).toHaveLength(1);
|
|
343
|
+
expect(events[0]).toMatchObject({
|
|
344
|
+
type: "indoor_cycling",
|
|
345
|
+
externalId: "hc-cycling-1",
|
|
296
346
|
});
|
|
297
347
|
|
|
298
348
|
const points = await t.run(async (ctx) => {
|
|
@@ -304,9 +354,13 @@ describe("sdkPush", () => {
|
|
|
304
354
|
|
|
305
355
|
expect(points.map((point) => point.seriesType).sort()).toEqual([
|
|
306
356
|
"active_calories",
|
|
357
|
+
"cadence",
|
|
307
358
|
"distance",
|
|
308
359
|
"floors_climbed",
|
|
309
360
|
"heart_rate_variability_rmssd",
|
|
361
|
+
"power",
|
|
362
|
+
"speed",
|
|
363
|
+
"total_calories",
|
|
310
364
|
]);
|
|
311
365
|
|
|
312
366
|
const summaries = await t.run(async (ctx) => {
|
package/src/component/sdkPush.ts
CHANGED
|
@@ -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:
|
|
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
|
-
...
|
|
367
|
-
source:
|
|
368
|
-
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:
|
|
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
|
-
|
|
529
|
-
|
|
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
|
},
|