@nativesquare/soma 0.16.2 → 0.16.4

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 (37) hide show
  1. package/dist/client/healthkit.d.ts +5 -85
  2. package/dist/client/healthkit.d.ts.map +1 -1
  3. package/dist/client/healthkit.js +14 -506
  4. package/dist/client/healthkit.js.map +1 -1
  5. package/dist/component/_generated/api.d.ts +4 -0
  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 +472 -0
  9. package/dist/component/_generated/component.d.ts.map +1 -1
  10. package/dist/component/healthkit/index.d.ts +1 -0
  11. package/dist/component/healthkit/index.d.ts.map +1 -1
  12. package/dist/component/healthkit/index.js +5 -0
  13. package/dist/component/healthkit/index.js.map +1 -1
  14. package/dist/component/healthkit/public.d.ts +451 -0
  15. package/dist/component/healthkit/public.d.ts.map +1 -0
  16. package/dist/component/healthkit/public.js +386 -0
  17. package/dist/component/healthkit/public.js.map +1 -0
  18. package/dist/component/healthkit/types.d.ts +13 -76
  19. package/dist/component/healthkit/types.d.ts.map +1 -1
  20. package/dist/component/healthkit/types.js +9 -6
  21. package/dist/component/healthkit/types.js.map +1 -1
  22. package/dist/component/healthkit/validators.d.ts +1933 -0
  23. package/dist/component/healthkit/validators.d.ts.map +1 -0
  24. package/dist/component/healthkit/validators.js +199 -0
  25. package/dist/component/healthkit/validators.js.map +1 -0
  26. package/dist/component/strava/types/stravaApi/zod.gen.d.ts +3 -3
  27. package/dist/component/validators/connection.js +1 -1
  28. package/dist/component/validators/connection.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/client/healthkit.ts +44 -625
  31. package/src/component/_generated/api.ts +4 -0
  32. package/src/component/_generated/component.ts +412 -0
  33. package/src/component/healthkit/index.ts +74 -46
  34. package/src/component/healthkit/public.ts +597 -0
  35. package/src/component/healthkit/types.ts +45 -114
  36. package/src/component/healthkit/validators.ts +253 -0
  37. package/src/component/validators/connection.ts +1 -1
@@ -7,18 +7,6 @@ import type {
7
7
  HKActivitySummary,
8
8
  HKCharacteristics,
9
9
  } from "../component/healthkit/types.js";
10
- import { transformWorkout } from "../component/healthkit/transform/activity.js";
11
- import { transformSleep } from "../component/healthkit/transform/sleep.js";
12
- import { transformBody } from "../component/healthkit/transform/body.js";
13
- import {
14
- transformDaily,
15
- transformDailyFromSummary,
16
- } from "../component/healthkit/transform/daily.js";
17
- import { transformNutrition } from "../component/healthkit/transform/nutrition.js";
18
- import { transformMenstruation } from "../component/healthkit/transform/menstruation.js";
19
- import { transformAthlete } from "../component/healthkit/transform/athlete.js";
20
-
21
- const PROVIDER = "APPLE";
22
10
 
23
11
  /**
24
12
  * Client class for Apple HealthKit integration with Soma.
@@ -26,8 +14,8 @@ const PROVIDER = "APPLE";
26
14
  * Unlike {@link import("./strava.js").SomaStrava | SomaStrava} and
27
15
  * {@link import("./garmin.js").SomaGarmin | SomaGarmin}, HealthKit is an
28
16
  * on-device provider — data is queried locally on iOS, not fetched from a
29
- * cloud API. This class wraps the transform + ingest pipeline so the host
30
- * app gets the same ergonomic interface as cloud providers.
17
+ * cloud API. This class is a thin wrapper over the component's public
18
+ * mutations, which own the transform + ingest pipeline.
31
19
  *
32
20
  * Because HealthKit permissions are managed in iOS Settings, the server
33
21
  * cannot independently verify whether the user has granted or revoked
@@ -75,13 +63,9 @@ export class SomaHealthKit {
75
63
  * Assert that HealthKit is connected for this user.
76
64
  *
77
65
  * Call this once after the React Native HealthKit library confirms
78
- * permissions were granted. Creates the APPLE connection if missing,
66
+ * permissions were granted. Creates the HEALTHKIT connection if missing,
79
67
  * or re-activates it if previously disconnected. Idempotent.
80
68
  *
81
- * Because iOS permission grants happen on-device, the server cannot
82
- * verify them independently — this method records the host app's
83
- * assertion that the user has authorized HealthKit access.
84
- *
85
69
  * @param ctx - Mutation context from the host app
86
70
  * @param args.userId - The host app's user identifier
87
71
  * @returns The connection document ID
@@ -90,10 +74,7 @@ export class SomaHealthKit {
90
74
  ctx: MutationCtx,
91
75
  args: { userId: string },
92
76
  ): Promise<string> {
93
- return (await ctx.runMutation(this.component.public.connect, {
94
- userId: args.userId,
95
- provider: PROVIDER,
96
- })) as string;
77
+ return await ctx.runMutation(this.component.healthkit.public.connect, args);
97
78
  }
98
79
 
99
80
  /**
@@ -114,10 +95,7 @@ export class SomaHealthKit {
114
95
  ctx: MutationCtx,
115
96
  args: { userId: string },
116
97
  ): Promise<null> {
117
- return await ctx.runMutation(this.component.public.disconnect, {
118
- userId: args.userId,
119
- provider: PROVIDER,
120
- });
98
+ return await ctx.runMutation(this.component.healthkit.public.disconnect, args);
121
99
  }
122
100
 
123
101
  // ─── Per-Type Sync Methods ─────────────────────────────────────────────
@@ -127,39 +105,15 @@ export class SomaHealthKit {
127
105
  *
128
106
  * Transforms each `HKWorkout` into the Soma Activity schema and ingests it
129
107
  * with automatic deduplication by `workout.uuid`.
130
- *
131
- * @param ctx - Mutation (or action) context from the host app
132
- * @param args.userId - The host app's user identifier
133
- * @param args.workouts - Array of HKWorkout objects from HealthKit
134
108
  */
135
109
  async syncActivities(
136
110
  ctx: MutationCtx,
137
111
  args: { userId: string; workouts: HKWorkout[] },
138
112
  ): Promise<SomaResult<{ activities: number }>> {
139
- const connectionId = await this.requireActiveConnection(ctx, args.userId);
140
- const errors: SomaError[] = [];
141
- let count = 0;
142
-
143
- for (const workout of args.workouts) {
144
- try {
145
- const data = transformWorkout(workout);
146
- await ctx.runMutation(this.component.public.ingestActivity, {
147
- connectionId,
148
- userId: args.userId,
149
- ...data,
150
- });
151
- count++;
152
- } catch (err) {
153
- errors.push({
154
- type: "activity",
155
- id: workout.uuid,
156
- message: err instanceof Error ? err.message : String(err),
157
- });
158
- }
159
- }
160
-
161
- await this.updateLastDataUpdate(ctx, connectionId);
162
- return { data: { activities: count }, errors };
113
+ return (await ctx.runMutation(
114
+ this.component.healthkit.public.syncActivities,
115
+ args,
116
+ )) as SomaResult<{ activities: number }>;
163
117
  }
164
118
 
165
119
  /**
@@ -168,40 +122,15 @@ export class SomaHealthKit {
168
122
  * Each inner array represents one sleep session (all `HKCategorySample`
169
123
  * stage records for a single night). Each session is aggregated into a
170
124
  * single Soma Sleep document.
171
- *
172
- * @param ctx - Mutation (or action) context from the host app
173
- * @param args.userId - The host app's user identifier
174
- * @param args.sessions - Array of sessions, each an array of sleep-stage samples
175
125
  */
176
126
  async syncSleep(
177
127
  ctx: MutationCtx,
178
128
  args: { userId: string; sessions: HKCategorySample[][] },
179
129
  ): Promise<SomaResult<{ sleep: number }>> {
180
- const connectionId = await this.requireActiveConnection(ctx, args.userId);
181
- const errors: SomaError[] = [];
182
- let count = 0;
183
-
184
- for (const session of args.sessions) {
185
- const sessionId = session[0]?.uuid ?? "unknown";
186
- try {
187
- const data = transformSleep(session);
188
- await ctx.runMutation(this.component.public.ingestSleep, {
189
- connectionId,
190
- userId: args.userId,
191
- ...data,
192
- });
193
- count++;
194
- } catch (err) {
195
- errors.push({
196
- type: "sleep",
197
- id: sessionId,
198
- message: err instanceof Error ? err.message : String(err),
199
- });
200
- }
201
- }
202
-
203
- await this.updateLastDataUpdate(ctx, connectionId);
204
- return { data: { sleep: count }, errors };
130
+ return (await ctx.runMutation(
131
+ this.component.healthkit.public.syncSleep,
132
+ args,
133
+ )) as SomaResult<{ sleep: number }>;
205
134
  }
206
135
 
207
136
  /**
@@ -210,11 +139,6 @@ export class SomaHealthKit {
210
139
  * Accepts a mixed array of body-related quantity samples (heart rate, HRV,
211
140
  * blood pressure, SpO2, weight, etc.) for a single time window and produces
212
141
  * one Soma Body document.
213
- *
214
- * @param ctx - Mutation (or action) context from the host app
215
- * @param args.userId - The host app's user identifier
216
- * @param args.samples - Array of HKQuantitySample for the desired time range
217
- * @param args.timeRange - Optional explicit time range; auto-detected from samples if omitted
218
142
  */
219
143
  async syncBody(
220
144
  ctx: MutationCtx,
@@ -224,40 +148,14 @@ export class SomaHealthKit {
224
148
  timeRange?: { start_time: string; end_time: string };
225
149
  },
226
150
  ): Promise<SomaResult<{ body: number }>> {
227
- const connectionId = await this.requireActiveConnection(ctx, args.userId);
228
- const errors: SomaError[] = [];
229
- let count = 0;
230
-
231
- try {
232
- const data = transformBody(args.samples, args.timeRange);
233
- await ctx.runMutation(this.component.public.ingestBody, {
234
- connectionId,
235
- userId: args.userId,
236
- ...data,
237
- });
238
- count++;
239
- } catch (err) {
240
- errors.push({
241
- type: "body",
242
- id: "transform",
243
- message: err instanceof Error ? err.message : String(err),
244
- });
245
- }
246
-
247
- await this.updateLastDataUpdate(ctx, connectionId);
248
- return { data: { body: count }, errors };
151
+ return (await ctx.runMutation(
152
+ this.component.healthkit.public.syncBody,
153
+ args,
154
+ )) as SomaResult<{ body: number }>;
249
155
  }
250
156
 
251
157
  /**
252
158
  * Sync daily activity data from HealthKit quantity samples.
253
- *
254
- * Accepts samples for a single day (steps, distance, energy, exercise time,
255
- * heart rate, etc.) and produces one Soma Daily document.
256
- *
257
- * @param ctx - Mutation (or action) context from the host app
258
- * @param args.userId - The host app's user identifier
259
- * @param args.samples - Array of HKQuantitySample for the desired day
260
- * @param args.timeRange - Optional explicit time range; auto-detected from samples if omitted
261
159
  */
262
160
  async syncDaily(
263
161
  ctx: MutationCtx,
@@ -267,82 +165,27 @@ export class SomaHealthKit {
267
165
  timeRange?: { start_time: string; end_time: string };
268
166
  },
269
167
  ): Promise<SomaResult<{ daily: number }>> {
270
- const connectionId = await this.requireActiveConnection(ctx, args.userId);
271
- const errors: SomaError[] = [];
272
- let count = 0;
273
-
274
- try {
275
- const data = transformDaily(args.samples, args.timeRange);
276
- await ctx.runMutation(this.component.public.ingestDaily, {
277
- connectionId,
278
- userId: args.userId,
279
- ...data,
280
- });
281
- count++;
282
- } catch (err) {
283
- errors.push({
284
- type: "daily",
285
- id: "transform",
286
- message: err instanceof Error ? err.message : String(err),
287
- });
288
- }
289
-
290
- await this.updateLastDataUpdate(ctx, connectionId);
291
- return { data: { daily: count }, errors };
168
+ return (await ctx.runMutation(
169
+ this.component.healthkit.public.syncDaily,
170
+ args,
171
+ )) as SomaResult<{ daily: number }>;
292
172
  }
293
173
 
294
174
  /**
295
175
  * Sync daily activity data from HealthKit activity ring summaries.
296
- *
297
- * Each `HKActivitySummary` represents one day's activity rings (Move,
298
- * Exercise, Stand). Each summary produces one Soma Daily document.
299
- *
300
- * @param ctx - Mutation (or action) context from the host app
301
- * @param args.userId - The host app's user identifier
302
- * @param args.summaries - Array of HKActivitySummary from HealthKit
303
176
  */
304
177
  async syncDailyFromSummary(
305
178
  ctx: MutationCtx,
306
179
  args: { userId: string; summaries: HKActivitySummary[] },
307
180
  ): Promise<SomaResult<{ daily: number }>> {
308
- const connectionId = await this.requireActiveConnection(ctx, args.userId);
309
- const errors: SomaError[] = [];
310
- let count = 0;
311
-
312
- for (const summary of args.summaries) {
313
- const { year, month, day } = summary.dateComponents;
314
- const summaryId = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
315
- try {
316
- const data = transformDailyFromSummary(summary);
317
- await ctx.runMutation(this.component.public.ingestDaily, {
318
- connectionId,
319
- userId: args.userId,
320
- ...data,
321
- });
322
- count++;
323
- } catch (err) {
324
- errors.push({
325
- type: "daily",
326
- id: summaryId,
327
- message: err instanceof Error ? err.message : String(err),
328
- });
329
- }
330
- }
331
-
332
- await this.updateLastDataUpdate(ctx, connectionId);
333
- return { data: { daily: count }, errors };
181
+ return (await ctx.runMutation(
182
+ this.component.healthkit.public.syncDailyFromSummary,
183
+ args,
184
+ )) as SomaResult<{ daily: number }>;
334
185
  }
335
186
 
336
187
  /**
337
188
  * Sync nutrition data from HealthKit.
338
- *
339
- * Accepts dietary quantity samples for a single time window and produces
340
- * one Soma Nutrition document with macros and micros.
341
- *
342
- * @param ctx - Mutation (or action) context from the host app
343
- * @param args.userId - The host app's user identifier
344
- * @param args.samples - Array of HKQuantitySample with dietary type identifiers
345
- * @param args.timeRange - Optional explicit time range; auto-detected from samples if omitted
346
189
  */
347
190
  async syncNutrition(
348
191
  ctx: MutationCtx,
@@ -352,40 +195,14 @@ export class SomaHealthKit {
352
195
  timeRange?: { start_time: string; end_time: string };
353
196
  },
354
197
  ): Promise<SomaResult<{ nutrition: number }>> {
355
- const connectionId = await this.requireActiveConnection(ctx, args.userId);
356
- const errors: SomaError[] = [];
357
- let count = 0;
358
-
359
- try {
360
- const data = transformNutrition(args.samples, args.timeRange);
361
- await ctx.runMutation(this.component.public.ingestNutrition, {
362
- connectionId,
363
- userId: args.userId,
364
- ...data,
365
- });
366
- count++;
367
- } catch (err) {
368
- errors.push({
369
- type: "nutrition" as SomaError["type"],
370
- id: "transform",
371
- message: err instanceof Error ? err.message : String(err),
372
- });
373
- }
374
-
375
- await this.updateLastDataUpdate(ctx, connectionId);
376
- return { data: { nutrition: count }, errors };
198
+ return (await ctx.runMutation(
199
+ this.component.healthkit.public.syncNutrition,
200
+ args,
201
+ )) as SomaResult<{ nutrition: number }>;
377
202
  }
378
203
 
379
204
  /**
380
205
  * Sync menstruation data from HealthKit.
381
- *
382
- * Accepts menstrual flow category samples for a single time window and
383
- * produces one Soma Menstruation document.
384
- *
385
- * @param ctx - Mutation (or action) context from the host app
386
- * @param args.userId - The host app's user identifier
387
- * @param args.samples - Array of HKCategorySample with menstrual flow values
388
- * @param args.timeRange - Optional explicit time range; auto-detected from samples if omitted
389
206
  */
390
207
  async syncMenstruation(
391
208
  ctx: MutationCtx,
@@ -395,28 +212,10 @@ export class SomaHealthKit {
395
212
  timeRange?: { start_time: string; end_time: string };
396
213
  },
397
214
  ): Promise<SomaResult<{ menstruation: number }>> {
398
- const connectionId = await this.requireActiveConnection(ctx, args.userId);
399
- const errors: SomaError[] = [];
400
- let count = 0;
401
-
402
- try {
403
- const data = transformMenstruation(args.samples, args.timeRange);
404
- await ctx.runMutation(this.component.public.ingestMenstruation, {
405
- connectionId,
406
- userId: args.userId,
407
- ...data,
408
- });
409
- count++;
410
- } catch (err) {
411
- errors.push({
412
- type: "menstruation",
413
- id: "transform",
414
- message: err instanceof Error ? err.message : String(err),
415
- });
416
- }
417
-
418
- await this.updateLastDataUpdate(ctx, connectionId);
419
- return { data: { menstruation: count }, errors };
215
+ return (await ctx.runMutation(
216
+ this.component.healthkit.public.syncMenstruation,
217
+ args,
218
+ )) as SomaResult<{ menstruation: number }>;
420
219
  }
421
220
 
422
221
  /**
@@ -424,37 +223,15 @@ export class SomaHealthKit {
424
223
  *
425
224
  * HealthKit exposes limited profile data (biological sex, date of birth).
426
225
  * Produces one Soma Athlete document per connection.
427
- *
428
- * @param ctx - Mutation (or action) context from the host app
429
- * @param args.userId - The host app's user identifier
430
- * @param args.characteristics - The HKCharacteristics from HealthKit
431
226
  */
432
227
  async syncAthlete(
433
228
  ctx: MutationCtx,
434
229
  args: { userId: string; characteristics: HKCharacteristics },
435
230
  ): Promise<SomaResult<{ athletes: number }>> {
436
- const connectionId = await this.requireActiveConnection(ctx, args.userId);
437
- const errors: SomaError[] = [];
438
- let count = 0;
439
-
440
- try {
441
- const data = transformAthlete(args.characteristics);
442
- await ctx.runMutation(this.component.public.ingestAthlete, {
443
- connectionId,
444
- userId: args.userId,
445
- ...data,
446
- });
447
- count++;
448
- } catch (err) {
449
- errors.push({
450
- type: "athlete",
451
- id: "transform",
452
- message: err instanceof Error ? err.message : String(err),
453
- });
454
- }
455
-
456
- await this.updateLastDataUpdate(ctx, connectionId);
457
- return { data: { athletes: count }, errors };
231
+ return (await ctx.runMutation(
232
+ this.component.healthkit.public.syncAthlete,
233
+ args,
234
+ )) as SomaResult<{ athletes: number }>;
458
235
  }
459
236
 
460
237
  // ─── Orchestrator ────────────────────────────────────────────────────────
@@ -465,17 +242,6 @@ export class SomaHealthKit {
465
242
  * Only data types with values provided are synced. Errors from individual
466
243
  * data types are collected — one failing type does not block others.
467
244
  *
468
- * @param ctx - Mutation (or action) context from the host app
469
- * @param args.userId - The host app's user identifier
470
- * @param args.workouts - Optional array of HKWorkout objects
471
- * @param args.sleepSessions - Optional array of sleep sessions (each an array of stage samples)
472
- * @param args.bodySamples - Optional body-related quantity samples
473
- * @param args.dailySamples - Optional daily activity quantity samples
474
- * @param args.dailySummaries - Optional daily activity ring summaries
475
- * @param args.nutritionSamples - Optional dietary quantity samples
476
- * @param args.menstruationSamples - Optional menstrual flow category samples
477
- * @param args.characteristics - Optional user characteristics
478
- *
479
245
  * @example
480
246
  * ```ts
481
247
  * const result = await soma.healthkit.syncAll(ctx, {
@@ -506,359 +272,12 @@ export class SomaHealthKit {
506
272
  characteristics?: HKCharacteristics;
507
273
  },
508
274
  ): Promise<SomaResult<Record<string, number>>> {
509
- const connectionId = await this.requireActiveConnection(ctx, args.userId);
510
- const allErrors: SomaError[] = [];
511
- const counts: Record<string, number> = {};
512
-
513
- const run = async <T extends Record<string, number>>(
514
- fn: () => Promise<SomaResult<T>>,
515
- fallbackType: SomaError["type"],
516
- ) => {
517
- try {
518
- const result = await fn();
519
- Object.assign(counts, result.data);
520
- allErrors.push(...result.errors);
521
- } catch (err) {
522
- allErrors.push({
523
- type: fallbackType,
524
- id: "sync",
525
- message: err instanceof Error ? err.message : String(err),
526
- });
527
- }
528
- };
529
-
530
- if (args.workouts) {
531
- await run(
532
- () => this.syncActivitiesInternal(ctx, connectionId, args.userId, args.workouts!),
533
- "activity",
534
- );
535
- }
536
- if (args.sleepSessions) {
537
- await run(
538
- () => this.syncSleepInternal(ctx, connectionId, args.userId, args.sleepSessions!),
539
- "sleep",
540
- );
541
- }
542
- if (args.bodySamples) {
543
- await run(
544
- () => this.syncBodyInternal(ctx, connectionId, args.userId, args.bodySamples!, args.bodyTimeRange),
545
- "body",
546
- );
547
- }
548
- if (args.dailySamples) {
549
- await run(
550
- () => this.syncDailyInternal(ctx, connectionId, args.userId, args.dailySamples!, args.dailyTimeRange),
551
- "daily",
552
- );
553
- }
554
- if (args.dailySummaries) {
555
- await run(
556
- () => this.syncDailyFromSummaryInternal(ctx, connectionId, args.userId, args.dailySummaries!),
557
- "daily",
558
- );
559
- }
560
- if (args.nutritionSamples) {
561
- await run(
562
- () => this.syncNutritionInternal(ctx, connectionId, args.userId, args.nutritionSamples!, args.nutritionTimeRange),
563
- "nutrition" as SomaError["type"],
564
- );
565
- }
566
- if (args.menstruationSamples) {
567
- await run(
568
- () => this.syncMenstruationInternal(ctx, connectionId, args.userId, args.menstruationSamples!, args.menstruationTimeRange),
569
- "menstruation",
570
- );
571
- }
572
- if (args.characteristics) {
573
- await run(
574
- () => this.syncAthleteInternal(ctx, connectionId, args.userId, args.characteristics!),
575
- "athlete",
576
- );
577
- }
578
-
579
- // Update lastDataUpdate once at the end
580
- await this.updateLastDataUpdate(ctx, connectionId);
581
-
582
- return { data: counts, errors: allErrors };
583
- }
584
-
585
- // ─── Private Helpers ───────────��─────────────────────────────────────────
586
-
587
- /**
588
- * Load the active APPLE connection for a user, or throw if missing/inactive.
589
- *
590
- * HealthKit permissions are managed on-device, so the connection state is
591
- * an assertion made by the host app via {@link SomaHealthKit.connect} and
592
- * {@link SomaHealthKit.disconnect}. Sync methods refuse to run without an
593
- * active connection — the host app must explicitly reconnect to resume.
594
- */
595
- private async requireActiveConnection(
596
- ctx: MutationCtx,
597
- userId: string,
598
- ): Promise<string> {
599
- const connection = await ctx.runQuery(
600
- this.component.public.getConnectionByProvider,
601
- { userId, provider: PROVIDER },
602
- );
603
- if (!connection) {
604
- throw new Error(
605
- `No HealthKit connection for user "${userId}". Call soma.healthkit.connect(ctx, { userId }) after the React Native HealthKit library confirms permissions.`,
606
- );
607
- }
608
- if (connection.active === false) {
609
- throw new Error(
610
- `HealthKit connection for user "${userId}" is disconnected. Call soma.healthkit.connect(ctx, { userId }) to re-activate.`,
611
- );
612
- }
613
- return connection._id;
614
- }
615
-
616
- private async updateLastDataUpdate(
617
- ctx: MutationCtx,
618
- connectionId: string,
619
- ): Promise<void> {
620
- await ctx.runMutation(this.component.public.updateConnection, {
621
- connectionId,
622
- lastDataUpdate: new Date().toISOString(),
623
- });
624
- }
625
-
626
- // ─── Internal sync methods (skip connection resolution + lastDataUpdate) ─
627
-
628
- private async syncActivitiesInternal(
629
- ctx: MutationCtx,
630
- connectionId: string,
631
- userId: string,
632
- workouts: HKWorkout[],
633
- ): Promise<SomaResult<{ activities: number }>> {
634
- const errors: SomaError[] = [];
635
- let count = 0;
636
-
637
- for (const workout of workouts) {
638
- try {
639
- const data = transformWorkout(workout);
640
- await ctx.runMutation(this.component.public.ingestActivity, {
641
- connectionId,
642
- userId,
643
- ...data,
644
- });
645
- count++;
646
- } catch (err) {
647
- errors.push({
648
- type: "activity",
649
- id: workout.uuid,
650
- message: err instanceof Error ? err.message : String(err),
651
- });
652
- }
653
- }
654
-
655
- return { data: { activities: count }, errors };
656
- }
657
-
658
- private async syncSleepInternal(
659
- ctx: MutationCtx,
660
- connectionId: string,
661
- userId: string,
662
- sessions: HKCategorySample[][],
663
- ): Promise<SomaResult<{ sleep: number }>> {
664
- const errors: SomaError[] = [];
665
- let count = 0;
666
-
667
- for (const session of sessions) {
668
- const sessionId = session[0]?.uuid ?? "unknown";
669
- try {
670
- const data = transformSleep(session);
671
- await ctx.runMutation(this.component.public.ingestSleep, {
672
- connectionId,
673
- userId,
674
- ...data,
675
- });
676
- count++;
677
- } catch (err) {
678
- errors.push({
679
- type: "sleep",
680
- id: sessionId,
681
- message: err instanceof Error ? err.message : String(err),
682
- });
683
- }
684
- }
685
-
686
- return { data: { sleep: count }, errors };
687
- }
688
-
689
- private async syncBodyInternal(
690
- ctx: MutationCtx,
691
- connectionId: string,
692
- userId: string,
693
- samples: HKQuantitySample[],
694
- timeRange?: { start_time: string; end_time: string },
695
- ): Promise<SomaResult<{ body: number }>> {
696
- const errors: SomaError[] = [];
697
- let count = 0;
698
-
699
- try {
700
- const data = transformBody(samples, timeRange);
701
- await ctx.runMutation(this.component.public.ingestBody, {
702
- connectionId,
703
- userId,
704
- ...data,
705
- });
706
- count++;
707
- } catch (err) {
708
- errors.push({
709
- type: "body",
710
- id: "transform",
711
- message: err instanceof Error ? err.message : String(err),
712
- });
713
- }
714
-
715
- return { data: { body: count }, errors };
716
- }
717
-
718
- private async syncDailyInternal(
719
- ctx: MutationCtx,
720
- connectionId: string,
721
- userId: string,
722
- samples: HKQuantitySample[],
723
- timeRange?: { start_time: string; end_time: string },
724
- ): Promise<SomaResult<{ daily: number }>> {
725
- const errors: SomaError[] = [];
726
- let count = 0;
727
-
728
- try {
729
- const data = transformDaily(samples, timeRange);
730
- await ctx.runMutation(this.component.public.ingestDaily, {
731
- connectionId,
732
- userId,
733
- ...data,
734
- });
735
- count++;
736
- } catch (err) {
737
- errors.push({
738
- type: "daily",
739
- id: "transform",
740
- message: err instanceof Error ? err.message : String(err),
741
- });
742
- }
743
-
744
- return { data: { daily: count }, errors };
745
- }
746
-
747
- private async syncDailyFromSummaryInternal(
748
- ctx: MutationCtx,
749
- connectionId: string,
750
- userId: string,
751
- summaries: HKActivitySummary[],
752
- ): Promise<SomaResult<{ daily: number }>> {
753
- const errors: SomaError[] = [];
754
- let count = 0;
755
-
756
- for (const summary of summaries) {
757
- const { year, month, day } = summary.dateComponents;
758
- const summaryId = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
759
- try {
760
- const data = transformDailyFromSummary(summary);
761
- await ctx.runMutation(this.component.public.ingestDaily, {
762
- connectionId,
763
- userId,
764
- ...data,
765
- });
766
- count++;
767
- } catch (err) {
768
- errors.push({
769
- type: "daily",
770
- id: summaryId,
771
- message: err instanceof Error ? err.message : String(err),
772
- });
773
- }
774
- }
775
-
776
- return { data: { daily: count }, errors };
777
- }
778
-
779
- private async syncNutritionInternal(
780
- ctx: MutationCtx,
781
- connectionId: string,
782
- userId: string,
783
- samples: HKQuantitySample[],
784
- timeRange?: { start_time: string; end_time: string },
785
- ): Promise<SomaResult<{ nutrition: number }>> {
786
- const errors: SomaError[] = [];
787
- let count = 0;
788
-
789
- try {
790
- const data = transformNutrition(samples, timeRange);
791
- await ctx.runMutation(this.component.public.ingestNutrition, {
792
- connectionId,
793
- userId,
794
- ...data,
795
- });
796
- count++;
797
- } catch (err) {
798
- errors.push({
799
- type: "nutrition" as SomaError["type"],
800
- id: "transform",
801
- message: err instanceof Error ? err.message : String(err),
802
- });
803
- }
804
-
805
- return { data: { nutrition: count }, errors };
806
- }
807
-
808
- private async syncMenstruationInternal(
809
- ctx: MutationCtx,
810
- connectionId: string,
811
- userId: string,
812
- samples: HKCategorySample[],
813
- timeRange?: { start_time: string; end_time: string },
814
- ): Promise<SomaResult<{ menstruation: number }>> {
815
- const errors: SomaError[] = [];
816
- let count = 0;
817
-
818
- try {
819
- const data = transformMenstruation(samples, timeRange);
820
- await ctx.runMutation(this.component.public.ingestMenstruation, {
821
- connectionId,
822
- userId,
823
- ...data,
824
- });
825
- count++;
826
- } catch (err) {
827
- errors.push({
828
- type: "menstruation",
829
- id: "transform",
830
- message: err instanceof Error ? err.message : String(err),
831
- });
832
- }
833
-
834
- return { data: { menstruation: count }, errors };
835
- }
836
-
837
- private async syncAthleteInternal(
838
- ctx: MutationCtx,
839
- connectionId: string,
840
- userId: string,
841
- characteristics: HKCharacteristics,
842
- ): Promise<SomaResult<{ athletes: number }>> {
843
- const errors: SomaError[] = [];
844
- let count = 0;
845
-
846
- try {
847
- const data = transformAthlete(characteristics);
848
- await ctx.runMutation(this.component.public.ingestAthlete, {
849
- connectionId,
850
- userId,
851
- ...data,
852
- });
853
- count++;
854
- } catch (err) {
855
- errors.push({
856
- type: "athlete",
857
- id: "transform",
858
- message: err instanceof Error ? err.message : String(err),
859
- });
860
- }
861
-
862
- return { data: { athletes: count }, errors };
275
+ return (await ctx.runMutation(
276
+ this.component.healthkit.public.syncAll,
277
+ args,
278
+ )) as SomaResult<Record<string, number>>;
863
279
  }
864
280
  }
281
+
282
+ // Re-export for JSDoc link resolution above.
283
+ export type { SomaError };