@clipin/convex-wearables 0.0.2 → 0.1.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 +395 -0
- package/dist/client/index.d.ts +47 -6
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +30 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts +83 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js.map +1 -1
- package/dist/component/_generated/component.d.ts +50 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/backfillJobs.d.ts +11 -11
- package/dist/component/connections.d.ts +9 -9
- package/dist/component/connections.d.ts.map +1 -1
- package/dist/component/connections.js +2 -0
- package/dist/component/connections.js.map +1 -1
- package/dist/component/dataPoints.d.ts +153 -39
- package/dist/component/dataPoints.d.ts.map +1 -1
- package/dist/component/dataPoints.js +1048 -139
- package/dist/component/dataPoints.js.map +1 -1
- package/dist/component/events.d.ts +13 -13
- package/dist/component/garminBackfill.d.ts +2 -2
- package/dist/component/garminWebhooks.d.ts +2 -2
- package/dist/component/garminWebhooks.d.ts.map +1 -1
- package/dist/component/garminWebhooks.js +2 -0
- package/dist/component/garminWebhooks.js.map +1 -1
- package/dist/component/lifecycle.d.ts +1 -1
- package/dist/component/lifecycle.d.ts.map +1 -1
- package/dist/component/lifecycle.js +39 -1
- package/dist/component/lifecycle.js.map +1 -1
- package/dist/component/oauthStates.d.ts +3 -3
- package/dist/component/schema.d.ts +192 -28
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +89 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/component/sdkPush.d.ts +11 -11
- package/dist/component/summaries.d.ts +4 -4
- package/dist/component/syncJobs.d.ts +23 -23
- package/dist/component/syncWorkflow.d.ts +2 -2
- package/dist/component/timeSeriesPolicyUtils.d.ts +97 -0
- package/dist/component/timeSeriesPolicyUtils.d.ts.map +1 -0
- package/dist/component/timeSeriesPolicyUtils.js +163 -0
- package/dist/component/timeSeriesPolicyUtils.js.map +1 -0
- package/dist/test.d.ts +581 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +17 -0
- package/dist/test.js.map +1 -0
- package/package.json +12 -2
- package/src/client/_generated/_ignore.ts +2 -0
- package/src/client/index.test.ts +149 -0
- package/src/client/index.ts +859 -0
- package/src/client/types.ts +632 -0
- package/src/component/_generated/_ignore.ts +2 -0
- package/src/component/_generated/api.ts +16 -0
- package/src/component/_generated/component.ts +74 -0
- package/src/component/_generated/dataModel.ts +40 -0
- package/src/component/_generated/server.ts +48 -0
- package/src/component/backfillJobs.test.ts +47 -0
- package/src/component/backfillJobs.ts +245 -0
- package/src/component/connections.test.ts +297 -0
- package/src/component/connections.ts +329 -0
- package/src/component/convex.config.ts +7 -0
- package/src/component/dataPoints.test.ts +827 -0
- package/src/component/dataPoints.ts +1676 -0
- package/src/component/dataSources.test.ts +247 -0
- package/src/component/dataSources.ts +109 -0
- package/src/component/events.test.ts +380 -0
- package/src/component/events.ts +288 -0
- package/src/component/garminBackfill.ts +343 -0
- package/src/component/garminWebhooks.test.ts +609 -0
- package/src/component/garminWebhooks.ts +656 -0
- package/src/component/httpHandlers.ts +153 -0
- package/src/component/lifecycle.test.ts +179 -0
- package/src/component/lifecycle.ts +128 -0
- package/src/component/menstrualCycles.ts +124 -0
- package/src/component/oauthActions.ts +261 -0
- package/src/component/oauthStates.test.ts +170 -0
- package/src/component/oauthStates.ts +85 -0
- package/src/component/providerSettings.ts +66 -0
- package/src/component/providers/additionalProviders.test.ts +401 -0
- package/src/component/providers/garmin.ts +1169 -0
- package/src/component/providers/oauth.test.ts +174 -0
- package/src/component/providers/oauth.ts +246 -0
- package/src/component/providers/polar.ts +220 -0
- package/src/component/providers/registry.ts +37 -0
- package/src/component/providers/strava.test.ts +195 -0
- package/src/component/providers/strava.ts +253 -0
- package/src/component/providers/suunto.ts +592 -0
- package/src/component/providers/types.ts +189 -0
- package/src/component/providers/whoop.ts +600 -0
- package/src/component/schema.ts +445 -0
- package/src/component/sdkPush.test.ts +367 -0
- package/src/component/sdkPush.ts +440 -0
- package/src/component/summaries.test.ts +201 -0
- package/src/component/summaries.ts +143 -0
- package/src/component/syncJobs.test.ts +254 -0
- package/src/component/syncJobs.ts +140 -0
- package/src/component/syncWorkflow.test.ts +87 -0
- package/src/component/syncWorkflow.ts +739 -0
- package/src/component/test.setup.ts +6 -0
- package/src/component/timeSeriesPolicyUtils.ts +243 -0
- package/src/component/workflowManager.ts +19 -0
- package/src/test.ts +25 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for @clipin/convex-wearables.
|
|
3
|
+
*
|
|
4
|
+
* These types are used by both the component internals and the host app.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Provider types
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
export type ProviderName =
|
|
12
|
+
| "garmin"
|
|
13
|
+
| "suunto"
|
|
14
|
+
| "polar"
|
|
15
|
+
| "whoop"
|
|
16
|
+
| "strava"
|
|
17
|
+
| "apple"
|
|
18
|
+
| "samsung"
|
|
19
|
+
| "google";
|
|
20
|
+
|
|
21
|
+
export type ConnectionStatus = "active" | "inactive" | "revoked" | "expired" | "error";
|
|
22
|
+
|
|
23
|
+
export type EventCategory = "workout" | "sleep";
|
|
24
|
+
|
|
25
|
+
export type SyncJobStatus = "queued" | "running" | "completed" | "failed" | "canceled";
|
|
26
|
+
|
|
27
|
+
export type BackfillJobStatus = "queued" | "running" | "completed" | "failed" | "canceled";
|
|
28
|
+
|
|
29
|
+
export type DurationInput = string | number;
|
|
30
|
+
|
|
31
|
+
export type TimeSeriesRollupAggregation = "avg" | "min" | "max" | "last" | "count";
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Provider configuration (passed by app)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
export interface ProviderCredentials {
|
|
38
|
+
clientId: string;
|
|
39
|
+
clientSecret: string;
|
|
40
|
+
/** Suunto requires an additional subscription key. */
|
|
41
|
+
subscriptionKey?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface WearablesConfig {
|
|
45
|
+
providers: Partial<Record<ProviderName, ProviderCredentials>>;
|
|
46
|
+
/**
|
|
47
|
+
* Optional function reference called when new data is synced.
|
|
48
|
+
* The host app can use this to trigger downstream processing.
|
|
49
|
+
*/
|
|
50
|
+
onDataSynced?: unknown; // FunctionReference — typed loosely to avoid coupling
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface GarminRoutesConfig {
|
|
54
|
+
/** Route that receives Garmin push notifications. */
|
|
55
|
+
webhookPath?: string;
|
|
56
|
+
/** Optional health-check route for the Garmin webhook integration. */
|
|
57
|
+
healthPath?: string | false;
|
|
58
|
+
/** Expected Garmin client ID. Defaults to `process.env.GARMIN_CLIENT_ID`. */
|
|
59
|
+
clientId?: string;
|
|
60
|
+
/** Garmin client secret. Defaults to `process.env.GARMIN_CLIENT_SECRET`. */
|
|
61
|
+
clientSecret?: string;
|
|
62
|
+
/** Route that receives the Garmin OAuth callback. */
|
|
63
|
+
oauthCallbackPath?: string | false;
|
|
64
|
+
/** Where to redirect after a successful OAuth callback. */
|
|
65
|
+
successRedirectUrl?: string;
|
|
66
|
+
/** Query parameter set on successful OAuth redirect. */
|
|
67
|
+
successQueryParam?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface SdkRoutesConfig {
|
|
71
|
+
/** Route that receives normalized SDK/mobile health pushes. */
|
|
72
|
+
syncPath?: string | false;
|
|
73
|
+
/**
|
|
74
|
+
* Optional shared bearer token expected on the SDK sync route.
|
|
75
|
+
* Defaults to `process.env.WEARABLES_SDK_AUTH_TOKEN` when omitted.
|
|
76
|
+
*/
|
|
77
|
+
authToken?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface RegisterRoutesConfig {
|
|
81
|
+
/**
|
|
82
|
+
* Garmin webhook routes.
|
|
83
|
+
* Pass `false` to skip registering Garmin routes.
|
|
84
|
+
*/
|
|
85
|
+
garmin?: GarminRoutesConfig | false;
|
|
86
|
+
/**
|
|
87
|
+
* Normalized SDK push route for Apple Health / Google Health Connect / Samsung Health.
|
|
88
|
+
* Omitted by default; pass a config object to register it.
|
|
89
|
+
*/
|
|
90
|
+
sdk?: SdkRoutesConfig | false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type SdkProviderName = "apple" | "google" | "samsung";
|
|
94
|
+
|
|
95
|
+
export interface SdkDeviceMetadata {
|
|
96
|
+
model?: string;
|
|
97
|
+
softwareVersion?: string;
|
|
98
|
+
source?: string;
|
|
99
|
+
deviceType?: string;
|
|
100
|
+
originalSourceName?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface SdkSourceMetadata {
|
|
104
|
+
deviceModel?: string;
|
|
105
|
+
softwareVersion?: string;
|
|
106
|
+
source?: string;
|
|
107
|
+
deviceType?: string;
|
|
108
|
+
originalSourceName?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface SdkPushEvent extends SdkSourceMetadata {
|
|
112
|
+
category: EventCategory;
|
|
113
|
+
type?: string;
|
|
114
|
+
sourceName?: string;
|
|
115
|
+
durationSeconds?: number;
|
|
116
|
+
startDatetime: number;
|
|
117
|
+
endDatetime?: number;
|
|
118
|
+
externalId?: string;
|
|
119
|
+
heartRateMin?: number;
|
|
120
|
+
heartRateMax?: number;
|
|
121
|
+
heartRateAvg?: number;
|
|
122
|
+
energyBurned?: number;
|
|
123
|
+
distance?: number;
|
|
124
|
+
stepsCount?: number;
|
|
125
|
+
maxSpeed?: number;
|
|
126
|
+
maxWatts?: number;
|
|
127
|
+
movingTimeSeconds?: number;
|
|
128
|
+
totalElevationGain?: number;
|
|
129
|
+
averageSpeed?: number;
|
|
130
|
+
averageWatts?: number;
|
|
131
|
+
elevHigh?: number;
|
|
132
|
+
elevLow?: number;
|
|
133
|
+
sleepTotalDurationMinutes?: number;
|
|
134
|
+
sleepTimeInBedMinutes?: number;
|
|
135
|
+
sleepEfficiencyScore?: number;
|
|
136
|
+
sleepDeepMinutes?: number;
|
|
137
|
+
sleepRemMinutes?: number;
|
|
138
|
+
sleepLightMinutes?: number;
|
|
139
|
+
sleepAwakeMinutes?: number;
|
|
140
|
+
isNap?: boolean;
|
|
141
|
+
sleepStages?: Array<{
|
|
142
|
+
stage: string;
|
|
143
|
+
startTime: number;
|
|
144
|
+
endTime: number;
|
|
145
|
+
}>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface SdkPushDataPoint extends SdkSourceMetadata {
|
|
149
|
+
seriesType: string;
|
|
150
|
+
recordedAt: number;
|
|
151
|
+
value: number;
|
|
152
|
+
externalId?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface SdkPushSummary {
|
|
156
|
+
date: string;
|
|
157
|
+
category: string;
|
|
158
|
+
totalSteps?: number;
|
|
159
|
+
totalCalories?: number;
|
|
160
|
+
activeCalories?: number;
|
|
161
|
+
activeMinutes?: number;
|
|
162
|
+
totalDistance?: number;
|
|
163
|
+
floorsClimbed?: number;
|
|
164
|
+
avgHeartRate?: number;
|
|
165
|
+
maxHeartRate?: number;
|
|
166
|
+
minHeartRate?: number;
|
|
167
|
+
sleepDurationMinutes?: number;
|
|
168
|
+
sleepEfficiency?: number;
|
|
169
|
+
deepSleepMinutes?: number;
|
|
170
|
+
remSleepMinutes?: number;
|
|
171
|
+
lightSleepMinutes?: number;
|
|
172
|
+
awakeDuringMinutes?: number;
|
|
173
|
+
timeInBedMinutes?: number;
|
|
174
|
+
hrvAvg?: number;
|
|
175
|
+
hrvRmssd?: number;
|
|
176
|
+
restingHeartRate?: number;
|
|
177
|
+
recoveryScore?: number;
|
|
178
|
+
weight?: number;
|
|
179
|
+
bodyFatPercentage?: number;
|
|
180
|
+
bodyMassIndex?: number;
|
|
181
|
+
leanBodyMass?: number;
|
|
182
|
+
bodyTemperature?: number;
|
|
183
|
+
avgStressLevel?: number;
|
|
184
|
+
bodyBattery?: number;
|
|
185
|
+
spo2Avg?: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface SdkPushPayload {
|
|
189
|
+
userId: string;
|
|
190
|
+
provider: SdkProviderName;
|
|
191
|
+
providerUserId?: string;
|
|
192
|
+
providerUsername?: string;
|
|
193
|
+
syncTimestamp?: number;
|
|
194
|
+
/**
|
|
195
|
+
* Compatibility alias for older mobile payloads.
|
|
196
|
+
* Prefer `sourceMetadata` for new integrations.
|
|
197
|
+
*/
|
|
198
|
+
device?: SdkDeviceMetadata;
|
|
199
|
+
sourceMetadata?: SdkSourceMetadata;
|
|
200
|
+
events?: SdkPushEvent[];
|
|
201
|
+
dataPoints?: SdkPushDataPoint[];
|
|
202
|
+
summaries?: SdkPushSummary[];
|
|
203
|
+
/**
|
|
204
|
+
* Compatibility alias for older mobile payloads.
|
|
205
|
+
* Prefer `summaries` for new integrations.
|
|
206
|
+
*/
|
|
207
|
+
dailySummaries?: SdkPushSummary[];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export type SdkSyncPayload = SdkPushPayload;
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Connection types
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
export interface Connection {
|
|
217
|
+
_id: string;
|
|
218
|
+
userId: string;
|
|
219
|
+
provider: ProviderName;
|
|
220
|
+
providerUserId?: string;
|
|
221
|
+
providerUsername?: string;
|
|
222
|
+
status: ConnectionStatus;
|
|
223
|
+
lastSyncedAt?: number;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Event types
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
export interface WorkoutEvent {
|
|
231
|
+
_id: string;
|
|
232
|
+
userId: string;
|
|
233
|
+
category: "workout";
|
|
234
|
+
type?: string;
|
|
235
|
+
sourceName?: string;
|
|
236
|
+
durationSeconds?: number;
|
|
237
|
+
startDatetime: number;
|
|
238
|
+
endDatetime?: number;
|
|
239
|
+
externalId?: string;
|
|
240
|
+
// Workout details
|
|
241
|
+
heartRateMin?: number;
|
|
242
|
+
heartRateMax?: number;
|
|
243
|
+
heartRateAvg?: number;
|
|
244
|
+
energyBurned?: number;
|
|
245
|
+
distance?: number;
|
|
246
|
+
stepsCount?: number;
|
|
247
|
+
maxSpeed?: number;
|
|
248
|
+
maxWatts?: number;
|
|
249
|
+
movingTimeSeconds?: number;
|
|
250
|
+
totalElevationGain?: number;
|
|
251
|
+
averageSpeed?: number;
|
|
252
|
+
averageWatts?: number;
|
|
253
|
+
elevHigh?: number;
|
|
254
|
+
elevLow?: number;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface SleepStage {
|
|
258
|
+
stage: string;
|
|
259
|
+
startTime: number;
|
|
260
|
+
endTime: number;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export interface SleepEvent {
|
|
264
|
+
_id: string;
|
|
265
|
+
userId: string;
|
|
266
|
+
category: "sleep";
|
|
267
|
+
type?: string;
|
|
268
|
+
sourceName?: string;
|
|
269
|
+
durationSeconds?: number;
|
|
270
|
+
startDatetime: number;
|
|
271
|
+
endDatetime?: number;
|
|
272
|
+
externalId?: string;
|
|
273
|
+
// Sleep details
|
|
274
|
+
sleepTotalDurationMinutes?: number;
|
|
275
|
+
sleepTimeInBedMinutes?: number;
|
|
276
|
+
sleepEfficiencyScore?: number;
|
|
277
|
+
sleepDeepMinutes?: number;
|
|
278
|
+
sleepRemMinutes?: number;
|
|
279
|
+
sleepLightMinutes?: number;
|
|
280
|
+
sleepAwakeMinutes?: number;
|
|
281
|
+
isNap?: boolean;
|
|
282
|
+
sleepStages?: SleepStage[];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export type HealthEvent = WorkoutEvent | SleepEvent;
|
|
286
|
+
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// Data point types
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
export interface DataPoint {
|
|
292
|
+
timestamp: number;
|
|
293
|
+
value: number;
|
|
294
|
+
resolution?: "raw" | "rollup";
|
|
295
|
+
bucketMinutes?: number;
|
|
296
|
+
avg?: number;
|
|
297
|
+
min?: number;
|
|
298
|
+
max?: number;
|
|
299
|
+
last?: number;
|
|
300
|
+
count?: number;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export interface TimeSeriesPage {
|
|
304
|
+
points: DataPoint[];
|
|
305
|
+
nextCursor: string | null;
|
|
306
|
+
hasMore: boolean;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export interface TimeSeriesRawTierInput {
|
|
310
|
+
kind: "raw";
|
|
311
|
+
fromAge: DurationInput;
|
|
312
|
+
toAge: DurationInput | null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface TimeSeriesRollupTierInput {
|
|
316
|
+
kind: "rollup";
|
|
317
|
+
fromAge: DurationInput;
|
|
318
|
+
toAge: DurationInput | null;
|
|
319
|
+
bucket: DurationInput;
|
|
320
|
+
aggregations?: TimeSeriesRollupAggregation[];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export type TimeSeriesTierInput = TimeSeriesRawTierInput | TimeSeriesRollupTierInput;
|
|
324
|
+
|
|
325
|
+
export interface TimeSeriesPolicyRuleInput {
|
|
326
|
+
provider?: ProviderName;
|
|
327
|
+
seriesType?: string;
|
|
328
|
+
tiers: TimeSeriesTierInput[];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export interface TimeSeriesPolicyPresetInput {
|
|
332
|
+
key: string;
|
|
333
|
+
rules: TimeSeriesPolicyRuleInput[];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export interface TimeSeriesMaintenanceInput {
|
|
337
|
+
enabled?: boolean;
|
|
338
|
+
interval?: DurationInput;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export interface TimeSeriesRawTier {
|
|
342
|
+
kind: "raw";
|
|
343
|
+
fromAgeMs: number;
|
|
344
|
+
toAgeMs: number | null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export interface TimeSeriesRollupTier {
|
|
348
|
+
kind: "rollup";
|
|
349
|
+
fromAgeMs: number;
|
|
350
|
+
toAgeMs: number | null;
|
|
351
|
+
bucketMs: number;
|
|
352
|
+
aggregations: TimeSeriesRollupAggregation[];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export type TimeSeriesTier = TimeSeriesRawTier | TimeSeriesRollupTier;
|
|
356
|
+
|
|
357
|
+
export interface TimeSeriesPolicyRule {
|
|
358
|
+
_id: string;
|
|
359
|
+
policySetKind: "default" | "preset";
|
|
360
|
+
policySetKey: string;
|
|
361
|
+
updatedAt: number;
|
|
362
|
+
scope: "global" | "provider" | "series" | "provider_series";
|
|
363
|
+
provider?: ProviderName;
|
|
364
|
+
seriesType?: string;
|
|
365
|
+
tiers: TimeSeriesTier[];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export interface TimeSeriesPolicyPreset {
|
|
369
|
+
key: string;
|
|
370
|
+
rules: TimeSeriesPolicyRule[];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export interface TimeSeriesPolicyConfiguration {
|
|
374
|
+
maintenance: {
|
|
375
|
+
enabled: boolean;
|
|
376
|
+
intervalMs: number;
|
|
377
|
+
};
|
|
378
|
+
defaultRules: TimeSeriesPolicyRule[];
|
|
379
|
+
presets: TimeSeriesPolicyPreset[];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export interface UserTimeSeriesPolicyPreset {
|
|
383
|
+
userId: string;
|
|
384
|
+
presetKey: string;
|
|
385
|
+
updatedAt: number;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export interface EffectiveTimeSeriesPolicy {
|
|
389
|
+
provider: ProviderName;
|
|
390
|
+
seriesType: string;
|
|
391
|
+
sourceKind: "preset" | "default" | "builtin";
|
|
392
|
+
sourceKey: string | null;
|
|
393
|
+
matchedScope: "default" | "global" | "provider" | "series" | "provider_series";
|
|
394
|
+
tiers: TimeSeriesTier[];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
// Events page
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
|
|
401
|
+
export interface EventsPage {
|
|
402
|
+
events: HealthEvent[];
|
|
403
|
+
nextCursor: string | null;
|
|
404
|
+
hasMore: boolean;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
// Summary types
|
|
409
|
+
// ---------------------------------------------------------------------------
|
|
410
|
+
|
|
411
|
+
export interface DailySummary {
|
|
412
|
+
_id: string;
|
|
413
|
+
userId: string;
|
|
414
|
+
date: string;
|
|
415
|
+
category: string;
|
|
416
|
+
// Activity
|
|
417
|
+
totalSteps?: number;
|
|
418
|
+
totalCalories?: number;
|
|
419
|
+
activeCalories?: number;
|
|
420
|
+
activeMinutes?: number;
|
|
421
|
+
totalDistance?: number;
|
|
422
|
+
floorsClimbed?: number;
|
|
423
|
+
avgHeartRate?: number;
|
|
424
|
+
maxHeartRate?: number;
|
|
425
|
+
minHeartRate?: number;
|
|
426
|
+
// Sleep
|
|
427
|
+
sleepDurationMinutes?: number;
|
|
428
|
+
sleepEfficiency?: number;
|
|
429
|
+
deepSleepMinutes?: number;
|
|
430
|
+
remSleepMinutes?: number;
|
|
431
|
+
lightSleepMinutes?: number;
|
|
432
|
+
awakeDuringMinutes?: number;
|
|
433
|
+
timeInBedMinutes?: number;
|
|
434
|
+
// Recovery
|
|
435
|
+
hrvAvg?: number;
|
|
436
|
+
hrvRmssd?: number;
|
|
437
|
+
restingHeartRate?: number;
|
|
438
|
+
recoveryScore?: number;
|
|
439
|
+
// Body
|
|
440
|
+
weight?: number;
|
|
441
|
+
bodyFatPercentage?: number;
|
|
442
|
+
bodyMassIndex?: number;
|
|
443
|
+
leanBodyMass?: number;
|
|
444
|
+
bodyTemperature?: number;
|
|
445
|
+
// Other
|
|
446
|
+
avgStressLevel?: number;
|
|
447
|
+
bodyBattery?: number;
|
|
448
|
+
spo2Avg?: number;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export interface AggregateStats {
|
|
452
|
+
sum: number;
|
|
453
|
+
count: number;
|
|
454
|
+
avg: number;
|
|
455
|
+
min: number | null;
|
|
456
|
+
max: number | null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ---------------------------------------------------------------------------
|
|
460
|
+
// Sync status
|
|
461
|
+
// ---------------------------------------------------------------------------
|
|
462
|
+
|
|
463
|
+
export interface SyncJob {
|
|
464
|
+
_id: string;
|
|
465
|
+
connectionId: string;
|
|
466
|
+
userId: string;
|
|
467
|
+
provider: ProviderName;
|
|
468
|
+
status: SyncJobStatus;
|
|
469
|
+
mode?: "manual" | "cron" | "webhook";
|
|
470
|
+
triggerSource?: string;
|
|
471
|
+
startedAt: number;
|
|
472
|
+
completedAt?: number;
|
|
473
|
+
error?: string;
|
|
474
|
+
recordsProcessed?: number;
|
|
475
|
+
workflowId?: string;
|
|
476
|
+
windowStart?: number;
|
|
477
|
+
windowEnd?: number;
|
|
478
|
+
attempt?: number;
|
|
479
|
+
lastHeartbeatAt?: number;
|
|
480
|
+
cursor?: string;
|
|
481
|
+
currentPhase?: "events" | "dataPoints" | "summaries";
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export interface SyncStatus {
|
|
485
|
+
provider: ProviderName;
|
|
486
|
+
connectionStatus: ConnectionStatus;
|
|
487
|
+
lastSyncedAt?: number;
|
|
488
|
+
syncJobStatus: SyncJobStatus | null;
|
|
489
|
+
syncJobError: string | null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export interface BackfillJob {
|
|
493
|
+
_id: string;
|
|
494
|
+
connectionId: string;
|
|
495
|
+
userId: string;
|
|
496
|
+
provider: ProviderName;
|
|
497
|
+
dataType: string;
|
|
498
|
+
status: BackfillJobStatus;
|
|
499
|
+
startedAt: number;
|
|
500
|
+
completedAt?: number;
|
|
501
|
+
error?: string;
|
|
502
|
+
workflowId?: string;
|
|
503
|
+
windowStart?: number;
|
|
504
|
+
windowEnd?: number;
|
|
505
|
+
currentDataType?: string;
|
|
506
|
+
currentAttempt?: number;
|
|
507
|
+
currentEventId?: string;
|
|
508
|
+
completedDataTypes?: string[];
|
|
509
|
+
lastHeartbeatAt?: number;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
// Series type definitions — all 48 pre-defined metric types
|
|
514
|
+
// ---------------------------------------------------------------------------
|
|
515
|
+
|
|
516
|
+
export const SERIES_TYPES = {
|
|
517
|
+
// Heart & Cardiovascular
|
|
518
|
+
heart_rate: { id: 1, unit: "bpm" },
|
|
519
|
+
resting_heart_rate: { id: 2, unit: "bpm" },
|
|
520
|
+
heart_rate_variability_sdnn: { id: 3, unit: "ms" },
|
|
521
|
+
heart_rate_recovery_one_minute: { id: 4, unit: "bpm" },
|
|
522
|
+
walking_heart_rate_average: { id: 5, unit: "bpm" },
|
|
523
|
+
recovery_score: { id: 6, unit: "score" },
|
|
524
|
+
heart_rate_variability_rmssd: { id: 7, unit: "ms" },
|
|
525
|
+
|
|
526
|
+
// Blood & Respiratory
|
|
527
|
+
oxygen_saturation: { id: 20, unit: "percent" },
|
|
528
|
+
blood_glucose: { id: 21, unit: "mg_dl" },
|
|
529
|
+
blood_pressure_systolic: { id: 22, unit: "mmHg" },
|
|
530
|
+
blood_pressure_diastolic: { id: 23, unit: "mmHg" },
|
|
531
|
+
respiratory_rate: { id: 24, unit: "brpm" },
|
|
532
|
+
sleeping_breathing_disturbances: { id: 25, unit: "count" },
|
|
533
|
+
blood_alcohol_content: { id: 26, unit: "mg_dl" },
|
|
534
|
+
peripheral_perfusion_index: { id: 27, unit: "score" },
|
|
535
|
+
forced_vital_capacity: { id: 28, unit: "liters" },
|
|
536
|
+
forced_expiratory_volume_1: { id: 29, unit: "liters" },
|
|
537
|
+
peak_expiratory_flow_rate: { id: 30, unit: "liters" },
|
|
538
|
+
|
|
539
|
+
// Body Composition
|
|
540
|
+
height: { id: 40, unit: "cm" },
|
|
541
|
+
weight: { id: 41, unit: "kg" },
|
|
542
|
+
body_fat_percentage: { id: 42, unit: "percent" },
|
|
543
|
+
body_mass_index: { id: 43, unit: "kg_m2" },
|
|
544
|
+
lean_body_mass: { id: 44, unit: "kg" },
|
|
545
|
+
body_temperature: { id: 45, unit: "celsius" },
|
|
546
|
+
skin_temperature: { id: 46, unit: "celsius" },
|
|
547
|
+
waist_circumference: { id: 47, unit: "cm" },
|
|
548
|
+
body_fat_mass: { id: 48, unit: "kg" },
|
|
549
|
+
skeletal_muscle_mass: { id: 49, unit: "kg" },
|
|
550
|
+
|
|
551
|
+
// Fitness
|
|
552
|
+
vo2_max: { id: 60, unit: "ml_kg_min" },
|
|
553
|
+
six_minute_walk_test_distance: { id: 61, unit: "meters" },
|
|
554
|
+
|
|
555
|
+
// Activity — Basic
|
|
556
|
+
steps: { id: 80, unit: "count" },
|
|
557
|
+
energy: { id: 81, unit: "kcal" },
|
|
558
|
+
basal_energy: { id: 82, unit: "kcal" },
|
|
559
|
+
total_calories: { id: 88, unit: "kcal" },
|
|
560
|
+
active_calories: { id: 89, unit: "kcal" },
|
|
561
|
+
stand_time: { id: 83, unit: "minutes" },
|
|
562
|
+
exercise_time: { id: 84, unit: "minutes" },
|
|
563
|
+
physical_effort: { id: 85, unit: "score" },
|
|
564
|
+
flights_climbed: { id: 86, unit: "count" },
|
|
565
|
+
floors_climbed: { id: 90, unit: "count" },
|
|
566
|
+
average_met: { id: 87, unit: "met" },
|
|
567
|
+
|
|
568
|
+
// Activity — Distance
|
|
569
|
+
distance: { id: 99, unit: "meters" },
|
|
570
|
+
distance_walking_running: { id: 100, unit: "meters" },
|
|
571
|
+
distance_cycling: { id: 101, unit: "meters" },
|
|
572
|
+
distance_swimming: { id: 102, unit: "meters" },
|
|
573
|
+
distance_downhill_snow_sports: { id: 103, unit: "meters" },
|
|
574
|
+
distance_other: { id: 104, unit: "meters" },
|
|
575
|
+
elevation_gain: { id: 105, unit: "meters" },
|
|
576
|
+
|
|
577
|
+
// Activity — Walking
|
|
578
|
+
walking_step_length: { id: 120, unit: "cm" },
|
|
579
|
+
walking_speed: { id: 121, unit: "m_per_s" },
|
|
580
|
+
walking_double_support_percentage: { id: 122, unit: "percent" },
|
|
581
|
+
walking_asymmetry_percentage: { id: 123, unit: "percent" },
|
|
582
|
+
walking_steadiness: { id: 124, unit: "percent" },
|
|
583
|
+
stair_descent_speed: { id: 125, unit: "m_per_s" },
|
|
584
|
+
stair_ascent_speed: { id: 126, unit: "m_per_s" },
|
|
585
|
+
|
|
586
|
+
// Activity — Running
|
|
587
|
+
running_power: { id: 140, unit: "watts" },
|
|
588
|
+
running_speed: { id: 141, unit: "m_per_s" },
|
|
589
|
+
running_vertical_oscillation: { id: 142, unit: "cm" },
|
|
590
|
+
running_ground_contact_time: { id: 143, unit: "ms" },
|
|
591
|
+
running_stride_length: { id: 144, unit: "cm" },
|
|
592
|
+
|
|
593
|
+
// Activity — Swimming
|
|
594
|
+
swimming_stroke_count: { id: 160, unit: "count" },
|
|
595
|
+
underwater_depth: { id: 161, unit: "meters" },
|
|
596
|
+
|
|
597
|
+
// Activity — Generic
|
|
598
|
+
cadence: { id: 180, unit: "rpm" },
|
|
599
|
+
power: { id: 181, unit: "watts" },
|
|
600
|
+
speed: { id: 182, unit: "m_per_s" },
|
|
601
|
+
workout_effort_score: { id: 183, unit: "score" },
|
|
602
|
+
estimated_workout_effort_score: { id: 184, unit: "score" },
|
|
603
|
+
|
|
604
|
+
// Environmental
|
|
605
|
+
environmental_audio_exposure: { id: 200, unit: "dB" },
|
|
606
|
+
headphone_audio_exposure: { id: 201, unit: "dB" },
|
|
607
|
+
environmental_sound_reduction: { id: 202, unit: "dB" },
|
|
608
|
+
time_in_daylight: { id: 203, unit: "minutes" },
|
|
609
|
+
water_temperature: { id: 204, unit: "celsius" },
|
|
610
|
+
uv_exposure: { id: 205, unit: "count" },
|
|
611
|
+
inhaler_usage: { id: 206, unit: "count" },
|
|
612
|
+
weather_temperature: { id: 207, unit: "celsius" },
|
|
613
|
+
weather_humidity: { id: 208, unit: "percent" },
|
|
614
|
+
|
|
615
|
+
// Garmin-Specific
|
|
616
|
+
garmin_stress_level: { id: 220, unit: "score" },
|
|
617
|
+
garmin_skin_temperature: { id: 221, unit: "celsius" },
|
|
618
|
+
garmin_fitness_age: { id: 222, unit: "years" },
|
|
619
|
+
garmin_body_battery: { id: 223, unit: "percent" },
|
|
620
|
+
|
|
621
|
+
// Other
|
|
622
|
+
electrodermal_activity: { id: 500, unit: "count" },
|
|
623
|
+
push_count: { id: 501, unit: "count" },
|
|
624
|
+
atrial_fibrillation_burden: { id: 502, unit: "count" },
|
|
625
|
+
insulin_delivery: { id: 503, unit: "count" },
|
|
626
|
+
number_of_times_fallen: { id: 504, unit: "count" },
|
|
627
|
+
number_of_alcoholic_beverages: { id: 505, unit: "count" },
|
|
628
|
+
nike_fuel: { id: 506, unit: "count" },
|
|
629
|
+
hydration: { id: 507, unit: "mL" },
|
|
630
|
+
} as const;
|
|
631
|
+
|
|
632
|
+
export type SeriesType = keyof typeof SERIES_TYPES;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* Generated API.
|
|
4
|
+
*
|
|
5
|
+
* THIS CODE IS AUTOMATICALLY GENERATED.
|
|
6
|
+
*
|
|
7
|
+
* To regenerate, run `npx convex dev`.
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { AnyApi, AnyComponents } from "convex/server";
|
|
12
|
+
import { anyApi, componentsGeneric } from "convex/server";
|
|
13
|
+
|
|
14
|
+
export const api: AnyApi = anyApi;
|
|
15
|
+
export const internal: AnyApi = anyApi;
|
|
16
|
+
export const components: AnyComponents = componentsGeneric();
|