@nativesquare/soma 0.16.2 → 0.16.3

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 (34) hide show
  1. package/dist/client/healthkit.d.ts +4 -84
  2. package/dist/client/healthkit.d.ts.map +1 -1
  3. package/dist/client/healthkit.js +13 -505
  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/package.json +1 -1
  28. package/src/client/healthkit.ts +43 -624
  29. package/src/component/_generated/api.ts +4 -0
  30. package/src/component/_generated/component.ts +412 -0
  31. package/src/component/healthkit/index.ts +74 -46
  32. package/src/component/healthkit/public.ts +597 -0
  33. package/src/component/healthkit/types.ts +45 -114
  34. package/src/component/healthkit/validators.ts +253 -0
@@ -1,112 +1,49 @@
1
1
  // ─── Apple HealthKit Input Types ──────────────────────────────────────────────
2
- // Library-agnostic TypeScript interfaces representing HealthKit data shapes.
2
+ // Library-agnostic TypeScript types representing HealthKit data shapes.
3
3
  // Compatible with @kingstinct/react-native-healthkit, react-native-health,
4
4
  // expo-health, or any custom native module.
5
5
  //
6
- // These types define the CONTRACT that the host app must satisfy when passing
7
- // HealthKit data to Soma's transformer functions. They intentionally do NOT
8
- // depend on any specific React Native library.
9
-
10
- // ─── Device ──────────────────────────────────────────────────────────────────
11
-
12
- export interface HKDevice {
13
- name?: string;
14
- manufacturer?: string;
15
- model?: string;
16
- hardwareVersion?: string;
17
- softwareVersion?: string;
18
- }
19
-
20
- // ─── Source ──────────────────────────────────────────────────────────────────
21
-
22
- export interface HKSource {
23
- name: string;
24
- bundleIdentifier: string;
25
- }
26
-
27
- // ─── Quantity Sample ─────────────────────────────────────────────────────────
28
- // Represents a numeric measurement from HealthKit (heart rate, steps, etc.)
29
-
30
- export interface HKQuantitySample {
31
- uuid: string;
32
- sampleType: HKQuantityTypeIdentifier;
33
- startDate: string; // ISO-8601
34
- endDate: string; // ISO-8601
35
- value: number;
36
- unit: string; // "count", "count/min", "kcal", "m", "mg/dL", etc.
37
- source?: HKSource;
38
- device?: HKDevice;
39
- }
40
-
41
- // ─── Category Sample ─────────────────────────────────────────────────────────
42
- // Represents a categorized observation (sleep stage, menstrual flow, etc.)
43
-
44
- export interface HKCategorySample {
45
- uuid: string;
46
- sampleType: HKCategoryTypeIdentifier;
47
- startDate: string; // ISO-8601
48
- endDate: string; // ISO-8601
49
- value: number; // Category-specific enum value
50
- source?: HKSource;
51
- device?: HKDevice;
52
- }
53
-
54
- // ─── Workout ─────────────────────────────────────────────────────────────────
55
-
56
- export interface HKWorkoutRoute {
57
- locations: Array<{
58
- latitude: number;
59
- longitude: number;
60
- altitude?: number;
61
- timestamp: string; // ISO-8601
62
- }>;
63
- }
64
-
65
- export interface HKWorkout {
66
- uuid: string;
67
- workoutActivityType: number; // HKWorkoutActivityType raw value
68
- startDate: string; // ISO-8601
69
- endDate: string; // ISO-8601
70
- duration: number; // seconds
71
- totalEnergyBurned?: number; // kcal
72
- totalDistance?: number; // meters
73
- totalSwimmingStrokeCount?: number;
74
- totalFlightsClimbed?: number;
75
- source?: HKSource;
76
- device?: HKDevice;
77
- // Associated samples (queried separately in HealthKit, attached by host app)
78
- heartRateSamples?: HKQuantitySample[];
79
- routeData?: HKWorkoutRoute[];
80
- }
81
-
82
- // ─── Activity Summary ────────────────────────────────────────────────────────
83
- // Daily activity ring data from HealthKit
84
-
85
- export interface HKActivitySummary {
86
- activeEnergyBurned: number; // kcal
87
- activeEnergyBurnedGoal: number; // kcal
88
- appleExerciseTime: number; // minutes
89
- appleExerciseTimeGoal: number; // minutes
90
- appleStandHours: number; // count
91
- appleStandHoursGoal: number; // count
92
- dateComponents: { year: number; month: number; day: number };
93
- }
94
-
95
- // ─── User Characteristics ────────────────────────────────────────────────────
96
- // Static user profile data from HealthKit
97
-
98
- export interface HKCharacteristics {
99
- biologicalSex?: HKBiologicalSex;
100
- dateOfBirth?: string; // ISO-8601 date
101
- bloodType?: string;
102
- fitzpatrickSkinType?: number;
103
- wheelchairUse?: boolean;
104
- }
105
-
106
- export type HKBiologicalSex = "female" | "male" | "other" | "notSet";
6
+ // These types are INFERRED from the Convex validators in ./validators.js
7
+ // edit validators.ts to change shapes, not this file. The identifier unions
8
+ // and runtime enum constants at the bottom are hand-written: they exist as
9
+ // documentation / autocomplete aids only and are not enforced at runtime.
10
+
11
+ import type { Infer } from "convex/values";
12
+ import {
13
+ hkDeviceValidator,
14
+ hkSourceValidator,
15
+ hkQuantitySampleValidator,
16
+ hkCategorySampleValidator,
17
+ hkWorkoutRouteValidator,
18
+ hkWorkoutValidator,
19
+ hkActivitySummaryValidator,
20
+ hkCharacteristicsValidator,
21
+ hkBiologicalSexValidator,
22
+ hkSleepCategoryValueValidator,
23
+ hkMenstrualFlowCategoryValueValidator,
24
+ } from "./validators.js";
25
+
26
+ // ─── Inferred shapes ─────────────────────────────────────────────────────────
27
+
28
+ export type HKDevice = Infer<typeof hkDeviceValidator>;
29
+ export type HKSource = Infer<typeof hkSourceValidator>;
30
+ export type HKQuantitySample = Infer<typeof hkQuantitySampleValidator>;
31
+ export type HKCategorySample = Infer<typeof hkCategorySampleValidator>;
32
+ export type HKWorkoutRoute = Infer<typeof hkWorkoutRouteValidator>;
33
+ export type HKWorkout = Infer<typeof hkWorkoutValidator>;
34
+ export type HKActivitySummary = Infer<typeof hkActivitySummaryValidator>;
35
+ export type HKCharacteristics = Infer<typeof hkCharacteristicsValidator>;
36
+ export type HKBiologicalSex = Infer<typeof hkBiologicalSexValidator>;
37
+ export type HKSleepCategoryValue = Infer<typeof hkSleepCategoryValueValidator>;
38
+ export type HKMenstrualFlowCategoryValue = Infer<
39
+ typeof hkMenstrualFlowCategoryValueValidator
40
+ >;
107
41
 
108
42
  // ─── HealthKit Quantity Type Identifiers ─────────────────────────────────────
109
- // Subset covering types that map to the Soma schema.
43
+ // Documentary union of known identifiers. The runtime validator is a bare
44
+ // `v.string()` — any string is accepted. Use this type for host-side
45
+ // autocomplete by annotating values you construct, e.g.
46
+ // `const t: HKQuantityTypeIdentifier = "HKQuantityTypeIdentifierHeartRate"`.
110
47
 
111
48
  export type HKQuantityTypeIdentifier =
112
49
  // Body measurements
@@ -178,9 +115,9 @@ export type HKQuantityTypeIdentifier =
178
115
  | "HKQuantityTypeIdentifierDietaryCaffeine"
179
116
  | "HKQuantityTypeIdentifierDietaryIodine"
180
117
  | "HKQuantityTypeIdentifierDietaryChloride"
181
- // Hydration
182
118
  | "HKQuantityTypeIdentifierDietaryPanthothenicAcid"
183
- // Catch-all for future types
119
+ // Catch-all for future types (preserves autocomplete on the known literals
120
+ // while accepting any string, matching the runtime `v.string()` validator).
184
121
  | (string & {});
185
122
 
186
123
  // ─── HealthKit Category Type Identifiers ─────────────────────────────────────
@@ -191,7 +128,9 @@ export type HKCategoryTypeIdentifier =
191
128
  | "HKCategoryTypeIdentifierAppleStandHour"
192
129
  | (string & {});
193
130
 
194
- // ─── HealthKit Sleep Category Values ─────────────────────────────────────────
131
+ // ─── Runtime enum constants ──────────────────────────────────────────────────
132
+ // Kept as real values (not just types) so host code can compare against them
133
+ // at runtime: `if (sample.value === HKSleepCategory.AsleepREM) { ... }`
195
134
 
196
135
  export const HKSleepCategory = {
197
136
  InBed: 0,
@@ -202,11 +141,6 @@ export const HKSleepCategory = {
202
141
  AsleepREM: 5,
203
142
  } as const;
204
143
 
205
- export type HKSleepCategoryValue =
206
- (typeof HKSleepCategory)[keyof typeof HKSleepCategory];
207
-
208
- // ─── HealthKit Menstrual Flow Values ─────────────────────────────────────────
209
-
210
144
  export const HKMenstrualFlowCategory = {
211
145
  Unspecified: 1,
212
146
  Light: 2,
@@ -214,6 +148,3 @@ export const HKMenstrualFlowCategory = {
214
148
  Heavy: 4,
215
149
  None: 5,
216
150
  } as const;
217
-
218
- export type HKMenstrualFlowCategoryValue =
219
- (typeof HKMenstrualFlowCategory)[keyof typeof HKMenstrualFlowCategory];
@@ -0,0 +1,253 @@
1
+ // ─── HealthKit Input Validators ──────────────────────────────────────────────
2
+ // Convex validators for the library-agnostic HealthKit input shapes the host
3
+ // app hands to Soma. TypeScript types in ./types.js are inferred from these.
4
+ //
5
+ // Keep in lockstep with the documented HKQuantityTypeIdentifier /
6
+ // HKCategoryTypeIdentifier string-literal unions in types.ts. Those unions are
7
+ // documentation / autocomplete aids only; at runtime we accept any string so
8
+ // that future iOS HealthKit identifiers don't require a Soma release.
9
+
10
+ import { v } from "convex/values";
11
+ import { somaErrorValidator } from "../validators/shared.js";
12
+
13
+ // ─── Primitive shapes ───────────────────────────────────────────────────────
14
+
15
+ export const hkDeviceValidator = v.object({
16
+ name: v.optional(v.string()),
17
+ manufacturer: v.optional(v.string()),
18
+ model: v.optional(v.string()),
19
+ hardwareVersion: v.optional(v.string()),
20
+ softwareVersion: v.optional(v.string()),
21
+ });
22
+
23
+ export const hkSourceValidator = v.object({
24
+ name: v.string(),
25
+ bundleIdentifier: v.string(),
26
+ });
27
+
28
+ // ─── Enums ──────────────────────────────────────────────────────────────────
29
+
30
+ /**
31
+ * Biological sex values produced by HealthKit.
32
+ * Matches the `HKBiologicalSex` string union in types.ts.
33
+ */
34
+ export const hkBiologicalSexValidator = v.union(
35
+ v.literal("female"),
36
+ v.literal("male"),
37
+ v.literal("other"),
38
+ v.literal("notSet"),
39
+ );
40
+
41
+ /**
42
+ * Numeric sleep-stage value from `HKCategoryTypeIdentifierSleepAnalysis`:
43
+ * 0=InBed, 1=AsleepUnspecified, 2=Awake, 3=AsleepCore, 4=AsleepDeep, 5=AsleepREM.
44
+ */
45
+ export const hkSleepCategoryValueValidator = v.union(
46
+ v.literal(0),
47
+ v.literal(1),
48
+ v.literal(2),
49
+ v.literal(3),
50
+ v.literal(4),
51
+ v.literal(5),
52
+ );
53
+
54
+ /**
55
+ * Numeric menstrual flow value from `HKCategoryTypeIdentifierMenstrualFlow`:
56
+ * 1=Unspecified, 2=Light, 3=Medium, 4=Heavy, 5=None.
57
+ */
58
+ export const hkMenstrualFlowCategoryValueValidator = v.union(
59
+ v.literal(1),
60
+ v.literal(2),
61
+ v.literal(3),
62
+ v.literal(4),
63
+ v.literal(5),
64
+ );
65
+
66
+ // ─── Samples ────────────────────────────────────────────────────────────────
67
+
68
+ /**
69
+ * Numeric measurement from HealthKit (heart rate, steps, weight, etc.).
70
+ *
71
+ * `sampleType` is a bare string for forward-compat with iOS updates that add
72
+ * new identifiers. The `HKQuantityTypeIdentifier` alias in types.ts lists
73
+ * known values for host-side autocomplete; it is not enforced at runtime.
74
+ */
75
+ export const hkQuantitySampleValidator = v.object({
76
+ uuid: v.string(),
77
+ sampleType: v.string(),
78
+ startDate: v.string(),
79
+ endDate: v.string(),
80
+ value: v.number(),
81
+ unit: v.string(),
82
+ source: v.optional(hkSourceValidator),
83
+ device: v.optional(hkDeviceValidator),
84
+ });
85
+
86
+ /**
87
+ * Categorized observation (sleep stage, menstrual flow, etc.).
88
+ *
89
+ * `value` is a category-specific enum integer. `sampleType` is a bare string
90
+ * for the same forward-compat reason as on `HKQuantitySample`.
91
+ */
92
+ export const hkCategorySampleValidator = v.object({
93
+ uuid: v.string(),
94
+ sampleType: v.string(),
95
+ startDate: v.string(),
96
+ endDate: v.string(),
97
+ value: v.number(),
98
+ source: v.optional(hkSourceValidator),
99
+ device: v.optional(hkDeviceValidator),
100
+ });
101
+
102
+ // ─── Workout ────────────────────────────────────────────────────────────────
103
+
104
+ export const hkWorkoutRouteValidator = v.object({
105
+ locations: v.array(
106
+ v.object({
107
+ latitude: v.number(),
108
+ longitude: v.number(),
109
+ altitude: v.optional(v.number()),
110
+ timestamp: v.string(),
111
+ }),
112
+ ),
113
+ });
114
+
115
+ export const hkWorkoutValidator = v.object({
116
+ uuid: v.string(),
117
+ workoutActivityType: v.number(),
118
+ startDate: v.string(),
119
+ endDate: v.string(),
120
+ duration: v.number(),
121
+ totalEnergyBurned: v.optional(v.number()),
122
+ totalDistance: v.optional(v.number()),
123
+ totalSwimmingStrokeCount: v.optional(v.number()),
124
+ totalFlightsClimbed: v.optional(v.number()),
125
+ source: v.optional(hkSourceValidator),
126
+ device: v.optional(hkDeviceValidator),
127
+ heartRateSamples: v.optional(v.array(hkQuantitySampleValidator)),
128
+ routeData: v.optional(v.array(hkWorkoutRouteValidator)),
129
+ });
130
+
131
+ // ─── Activity Summary ───────────────────────────────────────────────────────
132
+
133
+ export const hkActivitySummaryValidator = v.object({
134
+ activeEnergyBurned: v.number(),
135
+ activeEnergyBurnedGoal: v.number(),
136
+ appleExerciseTime: v.number(),
137
+ appleExerciseTimeGoal: v.number(),
138
+ appleStandHours: v.number(),
139
+ appleStandHoursGoal: v.number(),
140
+ dateComponents: v.object({
141
+ year: v.number(),
142
+ month: v.number(),
143
+ day: v.number(),
144
+ }),
145
+ });
146
+
147
+ // ─── Characteristics ────────────────────────────────────────────────────────
148
+
149
+ export const hkCharacteristicsValidator = v.object({
150
+ biologicalSex: v.optional(hkBiologicalSexValidator),
151
+ dateOfBirth: v.optional(v.string()),
152
+ bloodType: v.optional(v.string()),
153
+ fitzpatrickSkinType: v.optional(v.number()),
154
+ wheelchairUse: v.optional(v.boolean()),
155
+ });
156
+
157
+ // ─── Time range ─────────────────────────────────────────────────────────────
158
+
159
+ export const timeRangeValidator = v.object({
160
+ start_time: v.string(),
161
+ end_time: v.string(),
162
+ });
163
+
164
+ // ─── Per-mutation args ──────────────────────────────────────────────────────
165
+ // Plain object literals so they can be dropped straight into `args:` on a
166
+ // `mutation({ args, ... })` definition.
167
+
168
+ export const syncActivitiesArgs = {
169
+ userId: v.string(),
170
+ workouts: v.array(hkWorkoutValidator),
171
+ };
172
+
173
+ export const syncSleepArgs = {
174
+ userId: v.string(),
175
+ sessions: v.array(v.array(hkCategorySampleValidator)),
176
+ };
177
+
178
+ export const syncBodyArgs = {
179
+ userId: v.string(),
180
+ samples: v.array(hkQuantitySampleValidator),
181
+ timeRange: v.optional(timeRangeValidator),
182
+ };
183
+
184
+ export const syncDailyArgs = {
185
+ userId: v.string(),
186
+ samples: v.array(hkQuantitySampleValidator),
187
+ timeRange: v.optional(timeRangeValidator),
188
+ };
189
+
190
+ export const syncDailyFromSummaryArgs = {
191
+ userId: v.string(),
192
+ summaries: v.array(hkActivitySummaryValidator),
193
+ };
194
+
195
+ export const syncNutritionArgs = {
196
+ userId: v.string(),
197
+ samples: v.array(hkQuantitySampleValidator),
198
+ timeRange: v.optional(timeRangeValidator),
199
+ };
200
+
201
+ export const syncMenstruationArgs = {
202
+ userId: v.string(),
203
+ samples: v.array(hkCategorySampleValidator),
204
+ timeRange: v.optional(timeRangeValidator),
205
+ };
206
+
207
+ export const syncAthleteArgs = {
208
+ userId: v.string(),
209
+ characteristics: hkCharacteristicsValidator,
210
+ };
211
+
212
+ /**
213
+ * Args bag for `syncAll`. Every data-type field is optional — only provided
214
+ * types are synced. Use as: `mutation({ args: syncAllHealthKitArgs, ... })`
215
+ * or `args: v.object(syncAllHealthKitArgs)` on the host side.
216
+ */
217
+ export const syncAllHealthKitArgs = {
218
+ userId: v.string(),
219
+ workouts: v.optional(v.array(hkWorkoutValidator)),
220
+ sleepSessions: v.optional(v.array(v.array(hkCategorySampleValidator))),
221
+ bodySamples: v.optional(v.array(hkQuantitySampleValidator)),
222
+ bodyTimeRange: v.optional(timeRangeValidator),
223
+ dailySamples: v.optional(v.array(hkQuantitySampleValidator)),
224
+ dailyTimeRange: v.optional(timeRangeValidator),
225
+ dailySummaries: v.optional(v.array(hkActivitySummaryValidator)),
226
+ nutritionSamples: v.optional(v.array(hkQuantitySampleValidator)),
227
+ nutritionTimeRange: v.optional(timeRangeValidator),
228
+ menstruationSamples: v.optional(v.array(hkCategorySampleValidator)),
229
+ menstruationTimeRange: v.optional(timeRangeValidator),
230
+ characteristics: v.optional(hkCharacteristicsValidator),
231
+ };
232
+
233
+ // ─── Return validators ──────────────────────────────────────────────────────
234
+
235
+ const countResult = (countKey: string) =>
236
+ v.object({
237
+ data: v.object({ [countKey]: v.number() }),
238
+ errors: v.array(somaErrorValidator),
239
+ });
240
+
241
+ export const syncActivitiesReturn = countResult("activities");
242
+ export const syncSleepReturn = countResult("sleep");
243
+ export const syncBodyReturn = countResult("body");
244
+ export const syncDailyReturn = countResult("daily");
245
+ export const syncNutritionReturn = countResult("nutrition");
246
+ export const syncMenstruationReturn = countResult("menstruation");
247
+ export const syncAthleteReturn = countResult("athletes");
248
+
249
+ /** syncAll aggregates counts across data types; keys are not enumerated. */
250
+ export const syncAllReturn = v.object({
251
+ data: v.record(v.string(), v.number()),
252
+ errors: v.array(somaErrorValidator),
253
+ });