@nativesquare/soma 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.
Files changed (194) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +142 -0
  3. package/dist/client/_generated/_ignore.d.ts +1 -0
  4. package/dist/client/_generated/_ignore.d.ts.map +1 -0
  5. package/dist/client/_generated/_ignore.js +3 -0
  6. package/dist/client/_generated/_ignore.js.map +1 -0
  7. package/dist/client/index.d.ts +279 -0
  8. package/dist/client/index.d.ts.map +1 -0
  9. package/dist/client/index.js +264 -0
  10. package/dist/client/index.js.map +1 -0
  11. package/dist/client/types.d.ts +5 -0
  12. package/dist/client/types.d.ts.map +1 -0
  13. package/dist/client/types.js +2 -0
  14. package/dist/client/types.js.map +1 -0
  15. package/dist/component/_generated/api.d.ts +62 -0
  16. package/dist/component/_generated/api.d.ts.map +1 -0
  17. package/dist/component/_generated/api.js +31 -0
  18. package/dist/component/_generated/api.js.map +1 -0
  19. package/dist/component/_generated/component.d.ts +1063 -0
  20. package/dist/component/_generated/component.d.ts.map +1 -0
  21. package/dist/component/_generated/component.js +11 -0
  22. package/dist/component/_generated/component.js.map +1 -0
  23. package/dist/component/_generated/dataModel.d.ts +46 -0
  24. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  25. package/dist/component/_generated/dataModel.js +11 -0
  26. package/dist/component/_generated/dataModel.js.map +1 -0
  27. package/dist/component/_generated/server.d.ts +121 -0
  28. package/dist/component/_generated/server.d.ts.map +1 -0
  29. package/dist/component/_generated/server.js +78 -0
  30. package/dist/component/_generated/server.js.map +1 -0
  31. package/dist/component/convex.config.d.ts +3 -0
  32. package/dist/component/convex.config.d.ts.map +1 -0
  33. package/dist/component/convex.config.js +3 -0
  34. package/dist/component/convex.config.js.map +1 -0
  35. package/dist/component/private.d.ts +33 -0
  36. package/dist/component/private.d.ts.map +1 -0
  37. package/dist/component/private.js +45 -0
  38. package/dist/component/private.js.map +1 -0
  39. package/dist/component/public.d.ts +1107 -0
  40. package/dist/component/public.d.ts.map +1 -0
  41. package/dist/component/public.js +310 -0
  42. package/dist/component/public.js.map +1 -0
  43. package/dist/component/schema.d.ts +4419 -0
  44. package/dist/component/schema.d.ts.map +1 -0
  45. package/dist/component/schema.js +106 -0
  46. package/dist/component/schema.js.map +1 -0
  47. package/dist/component/validators/activity.d.ts +747 -0
  48. package/dist/component/validators/activity.d.ts.map +1 -0
  49. package/dist/component/validators/activity.js +146 -0
  50. package/dist/component/validators/activity.js.map +1 -0
  51. package/dist/component/validators/athlete.d.ts +18 -0
  52. package/dist/component/validators/athlete.d.ts.map +1 -0
  53. package/dist/component/validators/athlete.js +25 -0
  54. package/dist/component/validators/athlete.js.map +1 -0
  55. package/dist/component/validators/body.d.ts +634 -0
  56. package/dist/component/validators/body.d.ts.map +1 -0
  57. package/dist/component/validators/body.js +70 -0
  58. package/dist/component/validators/body.js.map +1 -0
  59. package/dist/component/validators/connection.d.ts +7 -0
  60. package/dist/component/validators/connection.d.ts.map +1 -0
  61. package/dist/component/validators/connection.js +16 -0
  62. package/dist/component/validators/connection.js.map +1 -0
  63. package/dist/component/validators/daily.d.ts +650 -0
  64. package/dist/component/validators/daily.d.ts.map +1 -0
  65. package/dist/component/validators/daily.js +119 -0
  66. package/dist/component/validators/daily.js.map +1 -0
  67. package/dist/component/validators/enums.d.ts +24 -0
  68. package/dist/component/validators/enums.d.ts.map +1 -0
  69. package/dist/component/validators/enums.js +69 -0
  70. package/dist/component/validators/enums.js.map +1 -0
  71. package/dist/component/validators/index.d.ts +13 -0
  72. package/dist/component/validators/index.d.ts.map +1 -0
  73. package/dist/component/validators/index.js +16 -0
  74. package/dist/component/validators/index.js.map +1 -0
  75. package/dist/component/validators/menstruation.d.ts +51 -0
  76. package/dist/component/validators/menstruation.d.ts.map +1 -0
  77. package/dist/component/validators/menstruation.js +32 -0
  78. package/dist/component/validators/menstruation.js.map +1 -0
  79. package/dist/component/validators/nutrition.d.ts +498 -0
  80. package/dist/component/validators/nutrition.d.ts.map +1 -0
  81. package/dist/component/validators/nutrition.js +31 -0
  82. package/dist/component/validators/nutrition.js.map +1 -0
  83. package/dist/component/validators/plannedWorkout.d.ts +277 -0
  84. package/dist/component/validators/plannedWorkout.d.ts.map +1 -0
  85. package/dist/component/validators/plannedWorkout.js +105 -0
  86. package/dist/component/validators/plannedWorkout.js.map +1 -0
  87. package/dist/component/validators/samples.d.ts +609 -0
  88. package/dist/component/validators/samples.d.ts.map +1 -0
  89. package/dist/component/validators/samples.js +336 -0
  90. package/dist/component/validators/samples.js.map +1 -0
  91. package/dist/component/validators/shared.d.ts +402 -0
  92. package/dist/component/validators/shared.d.ts.map +1 -0
  93. package/dist/component/validators/shared.js +146 -0
  94. package/dist/component/validators/shared.js.map +1 -0
  95. package/dist/component/validators/sleep.d.ts +438 -0
  96. package/dist/component/validators/sleep.d.ts.map +1 -0
  97. package/dist/component/validators/sleep.js +95 -0
  98. package/dist/component/validators/sleep.js.map +1 -0
  99. package/dist/healthkit/activity.d.ts +75 -0
  100. package/dist/healthkit/activity.d.ts.map +1 -0
  101. package/dist/healthkit/activity.js +93 -0
  102. package/dist/healthkit/activity.js.map +1 -0
  103. package/dist/healthkit/athlete.d.ts +26 -0
  104. package/dist/healthkit/athlete.d.ts.map +1 -0
  105. package/dist/healthkit/athlete.js +34 -0
  106. package/dist/healthkit/athlete.js.map +1 -0
  107. package/dist/healthkit/body.d.ts +102 -0
  108. package/dist/healthkit/body.d.ts.map +1 -0
  109. package/dist/healthkit/body.js +167 -0
  110. package/dist/healthkit/body.js.map +1 -0
  111. package/dist/healthkit/daily.d.ts +119 -0
  112. package/dist/healthkit/daily.d.ts.map +1 -0
  113. package/dist/healthkit/daily.js +160 -0
  114. package/dist/healthkit/daily.js.map +1 -0
  115. package/dist/healthkit/index.d.ts +21 -0
  116. package/dist/healthkit/index.d.ts.map +1 -0
  117. package/dist/healthkit/index.js +21 -0
  118. package/dist/healthkit/index.js.map +1 -0
  119. package/dist/healthkit/maps/activity-type.d.ts +6 -0
  120. package/dist/healthkit/maps/activity-type.d.ts.map +1 -0
  121. package/dist/healthkit/maps/activity-type.js +184 -0
  122. package/dist/healthkit/maps/activity-type.js.map +1 -0
  123. package/dist/healthkit/maps/menstruation-flow.d.ts +6 -0
  124. package/dist/healthkit/maps/menstruation-flow.d.ts.map +1 -0
  125. package/dist/healthkit/maps/menstruation-flow.js +21 -0
  126. package/dist/healthkit/maps/menstruation-flow.js.map +1 -0
  127. package/dist/healthkit/maps/sleep-level.d.ts +11 -0
  128. package/dist/healthkit/maps/sleep-level.d.ts.map +1 -0
  129. package/dist/healthkit/maps/sleep-level.js +32 -0
  130. package/dist/healthkit/maps/sleep-level.js.map +1 -0
  131. package/dist/healthkit/menstruation.d.ts +35 -0
  132. package/dist/healthkit/menstruation.d.ts.map +1 -0
  133. package/dist/healthkit/menstruation.js +37 -0
  134. package/dist/healthkit/menstruation.js.map +1 -0
  135. package/dist/healthkit/nutrition.d.ts +77 -0
  136. package/dist/healthkit/nutrition.d.ts.map +1 -0
  137. package/dist/healthkit/nutrition.js +135 -0
  138. package/dist/healthkit/nutrition.js.map +1 -0
  139. package/dist/healthkit/sleep.d.ts +60 -0
  140. package/dist/healthkit/sleep.d.ts.map +1 -0
  141. package/dist/healthkit/sleep.js +108 -0
  142. package/dist/healthkit/sleep.js.map +1 -0
  143. package/dist/healthkit/types.d.ts +94 -0
  144. package/dist/healthkit/types.d.ts.map +1 -0
  145. package/dist/healthkit/types.js +26 -0
  146. package/dist/healthkit/types.js.map +1 -0
  147. package/dist/healthkit/utils.d.ts +63 -0
  148. package/dist/healthkit/utils.d.ts.map +1 -0
  149. package/dist/healthkit/utils.js +93 -0
  150. package/dist/healthkit/utils.js.map +1 -0
  151. package/dist/react/index.d.ts +2 -0
  152. package/dist/react/index.d.ts.map +1 -0
  153. package/dist/react/index.js +6 -0
  154. package/dist/react/index.js.map +1 -0
  155. package/package.json +112 -0
  156. package/src/client/_generated/_ignore.ts +1 -0
  157. package/src/client/index.ts +371 -0
  158. package/src/client/types.ts +18 -0
  159. package/src/component/_generated/api.ts +78 -0
  160. package/src/component/_generated/component.ts +1090 -0
  161. package/src/component/_generated/dataModel.ts +60 -0
  162. package/src/component/_generated/server.ts +156 -0
  163. package/src/component/convex.config.ts +3 -0
  164. package/src/component/private.ts +50 -0
  165. package/src/component/public.ts +358 -0
  166. package/src/component/schema.ts +115 -0
  167. package/src/component/validators/activity.ts +216 -0
  168. package/src/component/validators/athlete.ts +25 -0
  169. package/src/component/validators/body.ts +114 -0
  170. package/src/component/validators/connection.ts +16 -0
  171. package/src/component/validators/daily.ts +173 -0
  172. package/src/component/validators/enums.ts +119 -0
  173. package/src/component/validators/index.ts +16 -0
  174. package/src/component/validators/menstruation.ts +36 -0
  175. package/src/component/validators/nutrition.ts +37 -0
  176. package/src/component/validators/plannedWorkout.ts +110 -0
  177. package/src/component/validators/samples.ts +380 -0
  178. package/src/component/validators/shared.ts +165 -0
  179. package/src/component/validators/sleep.ts +133 -0
  180. package/src/healthkit/activity.ts +120 -0
  181. package/src/healthkit/athlete.ts +43 -0
  182. package/src/healthkit/body.ts +266 -0
  183. package/src/healthkit/daily.ts +245 -0
  184. package/src/healthkit/index.ts +59 -0
  185. package/src/healthkit/maps/activity-type.ts +185 -0
  186. package/src/healthkit/maps/menstruation-flow.ts +23 -0
  187. package/src/healthkit/maps/sleep-level.ts +37 -0
  188. package/src/healthkit/menstruation.ts +52 -0
  189. package/src/healthkit/nutrition.ts +162 -0
  190. package/src/healthkit/sleep.ts +136 -0
  191. package/src/healthkit/types.ts +219 -0
  192. package/src/healthkit/utils.ts +122 -0
  193. package/src/react/index.ts +7 -0
  194. package/src/test.ts +18 -0
@@ -0,0 +1,93 @@
1
+ // ─── Shared Utilities ────────────────────────────────────────────────────────
2
+ // Pure helper functions used across HealthKit transformer modules.
3
+ /**
4
+ * Compute the difference in seconds between two ISO-8601 timestamps.
5
+ */
6
+ export function diffSeconds(start, end) {
7
+ return (new Date(end).getTime() - new Date(start).getTime()) / 1000;
8
+ }
9
+ /**
10
+ * Build the start-of-day and end-of-day ISO-8601 strings for a date.
11
+ */
12
+ export function dayRange(date) {
13
+ const pad = (n) => String(n).padStart(2, "0");
14
+ const dateStr = `${date.year}-${pad(date.month)}-${pad(date.day)}`;
15
+ return {
16
+ start_time: `${dateStr}T00:00:00.000Z`,
17
+ end_time: `${dateStr}T23:59:59.999Z`,
18
+ };
19
+ }
20
+ /**
21
+ * Find the earliest startDate and latest endDate in an array of samples.
22
+ * Returns ISO-8601 strings. Falls back to provided defaults if array is empty.
23
+ */
24
+ export function sampleTimeRange(samples, fallback) {
25
+ if (samples.length === 0) {
26
+ return (fallback ?? {
27
+ start_time: new Date().toISOString(),
28
+ end_time: new Date().toISOString(),
29
+ });
30
+ }
31
+ let minStart = samples[0].startDate;
32
+ let maxEnd = samples[0].endDate;
33
+ for (const s of samples) {
34
+ if (s.startDate < minStart)
35
+ minStart = s.startDate;
36
+ if (s.endDate > maxEnd)
37
+ maxEnd = s.endDate;
38
+ }
39
+ return { start_time: minStart, end_time: maxEnd };
40
+ }
41
+ /**
42
+ * Build a Soma DeviceData object from HealthKit source and device metadata.
43
+ */
44
+ export function buildDeviceData(source, device) {
45
+ if (!source && !device)
46
+ return undefined;
47
+ return {
48
+ name: device?.name ?? source?.name,
49
+ manufacturer: device?.manufacturer,
50
+ hardware_version: device?.hardwareVersion,
51
+ software_version: device?.softwareVersion,
52
+ };
53
+ }
54
+ /**
55
+ * Filter an array of HKQuantitySamples by sampleType identifier.
56
+ */
57
+ export function filterByType(samples, sampleType) {
58
+ return samples.filter((s) => s.sampleType === sampleType);
59
+ }
60
+ /**
61
+ * Sum the values of quantity samples.
62
+ */
63
+ export function sumValues(samples) {
64
+ return samples.reduce((acc, s) => acc + s.value, 0);
65
+ }
66
+ /**
67
+ * Average the values of quantity samples.
68
+ * Returns undefined if the array is empty.
69
+ */
70
+ export function avgValue(samples) {
71
+ if (samples.length === 0)
72
+ return undefined;
73
+ return sumValues(samples) / samples.length;
74
+ }
75
+ /**
76
+ * Min value in a set of quantity samples.
77
+ * Returns undefined if the array is empty.
78
+ */
79
+ export function minValue(samples) {
80
+ if (samples.length === 0)
81
+ return undefined;
82
+ return Math.min(...samples.map((s) => s.value));
83
+ }
84
+ /**
85
+ * Max value in a set of quantity samples.
86
+ * Returns undefined if the array is empty.
87
+ */
88
+ export function maxValue(samples) {
89
+ if (samples.length === 0)
90
+ return undefined;
91
+ return Math.max(...samples.map((s) => s.value));
92
+ }
93
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/healthkit/utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const useMyComponent: () => {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,UAE1B,CAAC"}
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ // This is where React components / hooks go.
3
+ export const useMyComponent = () => {
4
+ return {};
5
+ };
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,6CAA6C;AAE7C,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,EAAE;IACjC,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,112 @@
1
+ {
2
+ "name": "@nativesquare/soma",
3
+ "description": "A Convex component that normalizes health and fitness data from multiple wearable providers into a single, consistent schema.",
4
+ "repository": "github:NativeSquare/soma",
5
+ "homepage": "https://github.com/NativeSquare/soma#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/NativeSquare/soma/issues"
8
+ },
9
+ "version": "0.1.0",
10
+ "license": "Apache-2.0",
11
+ "keywords": [
12
+ "convex",
13
+ "component"
14
+ ],
15
+ "type": "module",
16
+ "scripts": {
17
+ "dev": "run-p -r dev:*",
18
+ "dev:backend": "convex dev --typecheck-components",
19
+ "dev:frontend": "cd example && vite --clearScreen false",
20
+ "dev:build": "chokidar \"tsconfig*.json\" \"src/**/*.ts\" -i \"**/*.test.ts\" -c \"npm run build:codegen\" --initial",
21
+ "predev": "path-exists .env.local dist || (npm run build && convex dev --once)",
22
+ "build": "tsc --project ./tsconfig.build.json",
23
+ "build:codegen": "npx convex codegen --component-dir ./src/component && npm run build",
24
+ "build:clean": "npx rimraf dist *.tsbuildinfo && npm run build:codegen",
25
+ "typecheck": "tsc --noEmit && tsc -p example && tsc -p example/convex",
26
+ "lint": "eslint .",
27
+ "all": "run-p -r dev:* test:watch",
28
+ "test": "vitest run --typecheck",
29
+ "test:watch": "vitest --typecheck --clearScreen false",
30
+ "test:debug": "vitest --inspect-brk --no-file-parallelism",
31
+ "test:coverage": "vitest run --coverage --coverage.reporter=text",
32
+ "preversion": "npm ci && npm run build:clean && run-p test lint typecheck",
33
+ "prepublishOnly": "npm whoami || npm login",
34
+ "alpha": "npm version prerelease --preid alpha && npm publish --tag alpha && git push --follow-tags",
35
+ "release": "npm version patch && npm publish && git push --follow-tags",
36
+ "version": "vim -c 'normal o' -c 'normal o## '$npm_package_version CHANGELOG.md && prettier -w CHANGELOG.md && git add CHANGELOG.md"
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "src"
41
+ ],
42
+ "exports": {
43
+ "./package.json": "./package.json",
44
+ ".": {
45
+ "types": "./dist/client/index.d.ts",
46
+ "default": "./dist/client/index.js"
47
+ },
48
+ "./react": {
49
+ "types": "./dist/react/index.d.ts",
50
+ "default": "./dist/react/index.js"
51
+ },
52
+ "./healthkit": {
53
+ "types": "./dist/healthkit/index.d.ts",
54
+ "default": "./dist/healthkit/index.js"
55
+ },
56
+ "./test": "./src/test.ts",
57
+ "./_generated/component.js": {
58
+ "types": "./dist/component/_generated/component.d.ts"
59
+ },
60
+ "./_generated/component": {
61
+ "types": "./dist/component/_generated/component.d.ts"
62
+ },
63
+ "./convex.config.js": {
64
+ "types": "./dist/component/convex.config.d.ts",
65
+ "default": "./dist/component/convex.config.js"
66
+ },
67
+ "./convex.config": {
68
+ "types": "./dist/component/convex.config.d.ts",
69
+ "default": "./dist/component/convex.config.js"
70
+ }
71
+ },
72
+ "peerDependencies": {
73
+ "convex": "^1.31.7",
74
+ "react": "^18.3.1 || ^19.0.0"
75
+ },
76
+ "devDependencies": {
77
+ "@convex-dev/eslint-plugin": "^1.1.1",
78
+ "@edge-runtime/vm": "^5.0.0",
79
+ "@eslint/eslintrc": "^3.3.3",
80
+ "@eslint/js": "9.39.2",
81
+ "@types/node": "^24.10.11",
82
+ "@types/react": "^19.2.13",
83
+ "@types/react-dom": "^19.2.3",
84
+ "@vitejs/plugin-react": "^5.1.3",
85
+ "chokidar-cli": "3.0.0",
86
+ "convex": "1.31.7",
87
+ "convex-test": "0.0.41",
88
+ "eslint": "9.39.2",
89
+ "eslint-plugin-react": "^7.37.5",
90
+ "eslint-plugin-react-hooks": "^7.0.1",
91
+ "eslint-plugin-react-refresh": "^0.5.0",
92
+ "globals": "^17.3.0",
93
+ "npm-run-all2": "8.0.4",
94
+ "path-exists-cli": "2.0.0",
95
+ "pkg-pr-new": "^0.0.63",
96
+ "prettier": "3.8.1",
97
+ "react": "^19.2.4",
98
+ "react-dom": "^19.2.4",
99
+ "typescript": "5.9.3",
100
+ "typescript-eslint": "8.54.0",
101
+ "vite": "7.3.1",
102
+ "vitest": "4.0.18"
103
+ },
104
+ "types": "./dist/client/index.d.ts",
105
+ "module": "./dist/client/index.js",
106
+ "pnpm": {
107
+ "onlyBuiltDependencies": [
108
+ "esbuild",
109
+ "@tailwindcss/oxide"
110
+ ]
111
+ }
112
+ }
@@ -0,0 +1 @@
1
+ // This is only here so convex-test can detect a _generated folder
@@ -0,0 +1,371 @@
1
+ import type { ComponentApi } from "../component/_generated/component.js";
2
+ import type { MutationCtx, QueryCtx } from "./types.js";
3
+
4
+ export type SomaComponent = ComponentApi;
5
+
6
+ /**
7
+ * Client class for the @nativesquare/soma Convex component.
8
+ *
9
+ * Provides a type-safe interface for managing wearable provider connections
10
+ * and querying normalized health & fitness data.
11
+ *
12
+ * All capabilities are also accessible via direct component function calls:
13
+ * `ctx.runMutation(components.soma.public.connect, { userId, provider: "GARMIN" })`
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // In your Convex function file:
18
+ * import { Soma } from "@nativesquare/soma";
19
+ * import { components } from "./_generated/api";
20
+ *
21
+ * const soma = new Soma(components.soma);
22
+ *
23
+ * // Connect a user to a provider:
24
+ * const connectionId = await soma.connect(ctx, {
25
+ * userId: "user_123",
26
+ * provider: "GARMIN",
27
+ * });
28
+ *
29
+ * // List all connections:
30
+ * const connections = await soma.listConnections(ctx, { userId: "user_123" });
31
+ * ```
32
+ */
33
+ export class Soma {
34
+ constructor(public component: SomaComponent) { }
35
+
36
+ // ─── Connect / Disconnect ───────────────────────────────────────────────────
37
+
38
+ /**
39
+ * Connect a user to a wearable provider.
40
+ *
41
+ * Creates the connection if it doesn't exist, or re-activates it if it was
42
+ * previously disconnected. Idempotent — calling twice is a no-op.
43
+ *
44
+ * Use this when the host app has completed the provider's auth flow and
45
+ * wants to register the connection in Soma.
46
+ *
47
+ * @param ctx - Mutation context from the host app
48
+ * @param args.userId - The host app's user identifier (Clerk ID, etc.)
49
+ * @param args.provider - The wearable provider name ("GARMIN", "FITBIT", "OURA", etc.)
50
+ * @returns The connection document ID
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * // "Connect to Garmin" button handler
55
+ * const connectionId = await soma.connect(ctx, {
56
+ * userId: "user_123",
57
+ * provider: "GARMIN",
58
+ * });
59
+ * ```
60
+ */
61
+ async connect(
62
+ ctx: MutationCtx,
63
+ args: { userId: string; provider: string },
64
+ ): Promise<string> {
65
+ return await ctx.runMutation(this.component.public.connect, args);
66
+ }
67
+
68
+ /**
69
+ * Disconnect a user from a wearable provider.
70
+ *
71
+ * Sets the connection to inactive. Does not delete any synced data,
72
+ * so re-connecting later preserves historical records.
73
+ *
74
+ * @param ctx - Mutation context from the host app
75
+ * @param args.userId - The host app's user identifier
76
+ * @param args.provider - The wearable provider name
77
+ *
78
+ * @throws Error if no connection exists for the given user–provider pair
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * // "Disconnect Garmin" button handler
83
+ * await soma.disconnect(ctx, {
84
+ * userId: "user_123",
85
+ * provider: "GARMIN",
86
+ * });
87
+ * ```
88
+ */
89
+ async disconnect(
90
+ ctx: MutationCtx,
91
+ args: { userId: string; provider: string },
92
+ ): Promise<null> {
93
+ return await ctx.runMutation(this.component.public.disconnect, args);
94
+ }
95
+
96
+ // ─── Connection Queries ─────────────────────────────────────────────────────
97
+
98
+ /**
99
+ * Get a connection by its document ID.
100
+ *
101
+ * @param ctx - Query context from the host app
102
+ * @param args.connectionId - The connection document ID
103
+ * @returns The connection document, or null if not found
104
+ */
105
+ async getConnection(
106
+ ctx: QueryCtx,
107
+ args: { connectionId: string },
108
+ ) {
109
+ return await ctx.runQuery(this.component.public.getConnection, args);
110
+ }
111
+
112
+ /**
113
+ * Get the connection for a specific user–provider pair.
114
+ *
115
+ * Useful for checking whether a user is connected to a specific provider
116
+ * (e.g., rendering a "Connected" badge on a provider card).
117
+ *
118
+ * @param ctx - Query context from the host app
119
+ * @param args.userId - The host app's user identifier
120
+ * @param args.provider - The wearable provider name
121
+ * @returns The connection document, or null if never connected
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const garmin = await soma.getConnectionByProvider(ctx, {
126
+ * userId: "user_123",
127
+ * provider: "GARMIN",
128
+ * });
129
+ * const isConnected = garmin?.active === true;
130
+ * ```
131
+ */
132
+ async getConnectionByProvider(
133
+ ctx: QueryCtx,
134
+ args: { userId: string; provider: string },
135
+ ) {
136
+ return await ctx.runQuery(
137
+ this.component.public.getConnectionByProvider,
138
+ args,
139
+ );
140
+ }
141
+
142
+ /**
143
+ * List all connections for a user (active and inactive).
144
+ *
145
+ * @param ctx - Query context from the host app
146
+ * @param args.userId - The host app's user identifier
147
+ * @returns Array of connection documents
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * const connections = await soma.listConnections(ctx, { userId: "user_123" });
152
+ * const activeProviders = connections
153
+ * .filter(c => c.active)
154
+ * .map(c => c.provider);
155
+ * ```
156
+ */
157
+ async listConnections(
158
+ ctx: QueryCtx,
159
+ args: { userId: string },
160
+ ) {
161
+ return await ctx.runQuery(this.component.public.listConnections, args);
162
+ }
163
+
164
+ // ─── Connection Mutations ───────────────────────────────────────────────────
165
+
166
+ /**
167
+ * Update a connection's mutable fields.
168
+ *
169
+ * @param ctx - Mutation context from the host app
170
+ * @param args.connectionId - The connection document ID
171
+ * @param args.active - Optional new active status
172
+ * @param args.lastDataUpdate - Optional ISO-8601 timestamp of last data sync
173
+ *
174
+ * @throws Error if the connection does not exist
175
+ */
176
+ async updateConnection(
177
+ ctx: MutationCtx,
178
+ args: {
179
+ connectionId: string;
180
+ active?: boolean;
181
+ lastDataUpdate?: string;
182
+ },
183
+ ): Promise<null> {
184
+ return await ctx.runMutation(
185
+ this.component.public.updateConnection,
186
+ args,
187
+ );
188
+ }
189
+
190
+ /**
191
+ * Delete a connection record entirely.
192
+ *
193
+ * This is a hard delete — the connection row is removed.
194
+ * Synced health data linked to this connection is NOT cascade-deleted.
195
+ *
196
+ * @param ctx - Mutation context from the host app
197
+ * @param args.connectionId - The connection document ID
198
+ *
199
+ * @throws Error if the connection does not exist
200
+ */
201
+ async deleteConnection(
202
+ ctx: MutationCtx,
203
+ args: { connectionId: string },
204
+ ): Promise<null> {
205
+ return await ctx.runMutation(
206
+ this.component.public.deleteConnection,
207
+ args,
208
+ );
209
+ }
210
+
211
+ // ─── Data Ingestion ─────────────────────────────────────────────────────────
212
+ // Store normalized health data into Soma with automatic deduplication.
213
+ // Use with transformer functions from @nativesquare/soma/healthkit:
214
+ //
215
+ // import { transformWorkout } from "@nativesquare/soma/healthkit";
216
+ // const data = transformWorkout(hkWorkout);
217
+ // await soma.ingestActivity(ctx, { connectionId, userId, ...data });
218
+
219
+ /**
220
+ * Ingest an activity (workout) record.
221
+ *
222
+ * Upserts by `connectionId + metadata.summary_id` — re-ingesting the same
223
+ * workout updates the existing record rather than creating a duplicate.
224
+ *
225
+ * @param ctx - Mutation context from the host app
226
+ * @param args - Activity data including connectionId, userId, metadata, and all activity fields
227
+ * @returns The activity document ID
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * import { transformWorkout } from "@nativesquare/soma/healthkit";
232
+ * const data = transformWorkout(hkWorkout);
233
+ * const id = await soma.ingestActivity(ctx, { connectionId, userId, ...data });
234
+ * ```
235
+ */
236
+ async ingestActivity(
237
+ ctx: MutationCtx,
238
+ args: IngestArgs,
239
+ ): Promise<string> {
240
+ return await ctx.runMutation(
241
+ this.component.public.ingestActivity,
242
+ args as never,
243
+ );
244
+ }
245
+
246
+ /**
247
+ * Ingest a sleep session record.
248
+ *
249
+ * Upserts by `connectionId + metadata.summary_id`.
250
+ *
251
+ * @param ctx - Mutation context from the host app
252
+ * @param args - Sleep data including connectionId, userId, metadata, and all sleep fields
253
+ * @returns The sleep document ID
254
+ */
255
+ async ingestSleep(
256
+ ctx: MutationCtx,
257
+ args: IngestArgs,
258
+ ): Promise<string> {
259
+ return await ctx.runMutation(
260
+ this.component.public.ingestSleep,
261
+ args as never,
262
+ );
263
+ }
264
+
265
+ /**
266
+ * Ingest a body metrics record.
267
+ *
268
+ * Upserts by `connectionId + metadata.start_time + metadata.end_time`.
269
+ *
270
+ * @param ctx - Mutation context from the host app
271
+ * @param args - Body data including connectionId, userId, metadata, and all body fields
272
+ * @returns The body document ID
273
+ */
274
+ async ingestBody(
275
+ ctx: MutationCtx,
276
+ args: IngestArgs,
277
+ ): Promise<string> {
278
+ return await ctx.runMutation(
279
+ this.component.public.ingestBody,
280
+ args as never,
281
+ );
282
+ }
283
+
284
+ /**
285
+ * Ingest a daily activity summary record.
286
+ *
287
+ * Upserts by `connectionId + metadata.start_time + metadata.end_time`.
288
+ *
289
+ * @param ctx - Mutation context from the host app
290
+ * @param args - Daily data including connectionId, userId, metadata, and all daily fields
291
+ * @returns The daily document ID
292
+ */
293
+ async ingestDaily(
294
+ ctx: MutationCtx,
295
+ args: IngestArgs,
296
+ ): Promise<string> {
297
+ return await ctx.runMutation(
298
+ this.component.public.ingestDaily,
299
+ args as never,
300
+ );
301
+ }
302
+
303
+ /**
304
+ * Ingest a nutrition record.
305
+ *
306
+ * Upserts by `connectionId + metadata.start_time + metadata.end_time`.
307
+ *
308
+ * @param ctx - Mutation context from the host app
309
+ * @param args - Nutrition data including connectionId, userId, metadata, and all nutrition fields
310
+ * @returns The nutrition document ID
311
+ */
312
+ async ingestNutrition(
313
+ ctx: MutationCtx,
314
+ args: IngestArgs,
315
+ ): Promise<string> {
316
+ return await ctx.runMutation(
317
+ this.component.public.ingestNutrition,
318
+ args as never,
319
+ );
320
+ }
321
+
322
+ /**
323
+ * Ingest a menstruation record.
324
+ *
325
+ * Append-only — each call creates a new document.
326
+ *
327
+ * @param ctx - Mutation context from the host app
328
+ * @param args - Menstruation data including connectionId, userId, metadata, and menstruation fields
329
+ * @returns The menstruation document ID
330
+ */
331
+ async ingestMenstruation(
332
+ ctx: MutationCtx,
333
+ args: IngestArgs,
334
+ ): Promise<string> {
335
+ return await ctx.runMutation(
336
+ this.component.public.ingestMenstruation,
337
+ args as never,
338
+ );
339
+ }
340
+
341
+ /**
342
+ * Ingest an athlete (user profile) record.
343
+ *
344
+ * Upserts by `connectionId` — one athlete record per connection.
345
+ *
346
+ * @param ctx - Mutation context from the host app
347
+ * @param args - Athlete data including connectionId, userId, and profile fields
348
+ * @returns The athlete document ID
349
+ */
350
+ async ingestAthlete(
351
+ ctx: MutationCtx,
352
+ args: IngestArgs,
353
+ ): Promise<string> {
354
+ return await ctx.runMutation(
355
+ this.component.public.ingestAthlete,
356
+ args as never,
357
+ );
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Common args shape for all ingestion methods.
363
+ *
364
+ * Requires `connectionId` and `userId` at minimum — additional fields
365
+ * come from the transformer output (e.g., `metadata`, `calories_data`, etc.)
366
+ * and are validated server-side by Convex validators.
367
+ */
368
+ type IngestArgs = {
369
+ connectionId: string;
370
+ userId: string;
371
+ } & Record<string, unknown>;
@@ -0,0 +1,18 @@
1
+ import type {
2
+ GenericActionCtx,
3
+ GenericMutationCtx,
4
+ GenericQueryCtx,
5
+ GenericDataModel,
6
+ } from "convex/server";
7
+
8
+ export type QueryCtx = Pick<GenericQueryCtx<GenericDataModel>, "runQuery">;
9
+
10
+ export type MutationCtx = Pick<
11
+ GenericMutationCtx<GenericDataModel>,
12
+ "runQuery" | "runMutation"
13
+ >;
14
+
15
+ export type ActionCtx = Pick<
16
+ GenericActionCtx<GenericDataModel>,
17
+ "runQuery" | "runMutation" | "runAction"
18
+ >;