{"_id":"@clipin/convex-wearables","_rev":"24-a67b0084ebf592a0dd095b4e6e83898e","name":"@clipin/convex-wearables","dist-tags":{"legacy":"0.7.1","latest":"1.0.0"},"versions":{"0.0.1":{"name":"@clipin/convex-wearables","version":"0.0.1","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.0.1","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"810a96e00ade3b0f8da4078f3049e0e123d3b806","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.0.1.tgz","fileCount":143,"integrity":"sha512-98S8pucxFKxT/2ivsRkflAaf1NCviiMsc/Y8Vb1jAYma5RKf+YCayR1qvg93PMV+eOpyJs+xOzgIy67lC+eQig==","signatures":[{"sig":"MEUCIQCGM1DmMF4vVLSnNQSqzblJ5+Kj3mWRsZ/ScKl+RnK9wgIgWL2PIcLoxmMorgbDvpmdY8ok9yLx5SIa6dA16mkJCoY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":731832},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"}},"gitHead":"9247456de98c3c6a9ca81fd8398aadbaf6f4e027","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.9.0","description":"Convex component for wearable device integrations — sync health data from Garmin, Strava, Whoop, Polar, Suunto, Apple HealthKit, Samsung Health, and Google Health Connect.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.0.1_1774019937494_0.540293908734806","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@clipin/convex-wearables","version":"0.0.2","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.0.2","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"9c78c4a35285de28e4f3e499197b65f35c8bfd0a","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.0.2.tgz","fileCount":143,"integrity":"sha512-02fQ610nCSD3ZEfX/T1hmZpieAYakgEl4+D0xY6QGRCIssygwtIIzm8y7aDtt42/0y8FKy9JkxPB/UmMao63nw==","signatures":[{"sig":"MEQCICgZp2KWgxf6YSBhD2EMcGSsS1PUXp8mqPhjHbGSbHb9AiANMkBV4oV4LDwHa9dtf3aGimLfIBQ9B/0BP4e4Bj6YEA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":735648},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"}},"gitHead":"dff0c72a2c24263e5799614020ed24f3c4e315e5","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.9.0","description":"Convex component for wearable device integrations — sync health data from Garmin, Strava, Whoop, Polar, Suunto, Apple HealthKit, Samsung Health, and Google Health Connect.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.0.2_1774189503962_0.613894093243244","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"@clipin/convex-wearables","version":"0.0.3","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.0.3","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"8b4fb35d59db4c2120a1475fed987b910b6cbb3b","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.0.3.tgz","fileCount":205,"integrity":"sha512-2DBH9uUXoc4uXp3NsGaI6fLpVP6f0dBJm6MCbEGKvFtmK6XGTsQG6aMTG7n2A1CLB+zAq1qd9Lond+Cjm+sVnw==","signatures":[{"sig":"MEQCICisX7OrmZJ7Z/YpXp3J5uH3s20ysvQ4wJjCFdFgaSprAiAEVyQllNsBkA/jjVVhGGCdIyJaw4vMLZ7qB1KLAyCSXw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1189313},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"f2ecd5383fcb9b4bd6a8c5cc7d18a6bdd1455daf","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.9.0","description":"Convex component for wearable device integrations — sync health data from Garmin, Strava, Whoop, Polar, Suunto, Apple HealthKit, Samsung Health, and Google Health Connect.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.0.3_1774210664649_0.12582337098688834","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@clipin/convex-wearables","version":"0.1.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.1.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"d9dead5b1a8bec6169aed6a2a2ca031659d8d92b","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.1.0.tgz","fileCount":210,"integrity":"sha512-Y67XEm7MlJK7fKpjzhwINOEQZm3zOeKgDNLxGk/B/AHYYKhed1XgO3stQSRwzexKZyOfQU7niqpm2LWyHXEzsQ==","signatures":[{"sig":"MEQCIA9F7uT16paErUIyQwObtgsMGixeqjbnLNXF5bINN35AAiBbNg5wXe7HlDTf0RySZZpzu1r/+1d658ss3B6d2B9ofA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1412984},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"3ebd5820f6dbace35b816bfe8a970e465d32eec2","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.9.0","description":"Convex component for wearable device integrations — sync health data from Garmin, Strava, Whoop, Polar, Suunto, Apple HealthKit, Samsung Health, and Google Health Connect.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.1.0_1774370718341_0.9602965476845557","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@clipin/convex-wearables","version":"0.1.1","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.1.1","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"eacbd91561b322bff44de70dd89a9827480bc006","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.1.1.tgz","fileCount":210,"integrity":"sha512-Ib5cTSibqP+qp9OzYR8uQYJG5iBDTnynpi9gqEl2jp1h6cnYBp36emMYuZELkOS3603tXM+oBkWNgLQtfofI7Q==","signatures":[{"sig":"MEUCIQCR01ddoEFuxIlpj7ttAz9WKYGv9uFF6E8gBaUxEEdgjgIgOps+Mdow6radG4C4cX095vnoNhVTzCKpeJZlWhunyhQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1415692},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"0c827d31f171d69f5d6fefe59ef82aad32320812","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.9.0","description":"Convex component for wearable device integrations — sync health data from Garmin, Strava, Whoop, Polar, Suunto, Apple HealthKit, Samsung Health, and Google Health Connect.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.1.1_1777064981212_0.9666118064083704","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@clipin/convex-wearables","version":"0.1.2","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.1.2","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"b913a57f34b8cd55493b05b56a773c5035b6a033","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.1.2.tgz","fileCount":210,"integrity":"sha512-1WtacOQa5oR+m0zaODlsrn05LOzdejfpSijUPhbL3jjT2qVCJ1pawD7FcWpVY0O1NlJnNyKarnY6VAGbiP2RUg==","signatures":[{"sig":"MEUCIQCY5jBPiH7eXuoBDRTzOJCT13yokCzqEF6uF3mUHjkveAIgDoaHtlLqXPwwD/fH+O6hRs1ZSqmCuYfV1NisDT6JmQo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1417688},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"fa86acea9ca89ba69f33d50ed758a678c9ed682d","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.9.0","description":"Convex component for wearable device integrations — sync health data from Garmin, Strava, Whoop, Polar, Suunto, Apple HealthKit, Samsung Health, and Google Health Connect.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.1.2_1777103134651_0.9960703740893313","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@clipin/convex-wearables","version":"0.1.3","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.1.3","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"5d96afd2a5a70886c028aa91e004d8b3fd028553","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.1.3.tgz","fileCount":210,"integrity":"sha512-5M6Xjl7UkY/p3ve0WH1n0pXDf6HRLrvpnsP+G9n+pNvH13oV3AVnLWwye1Uv+sedrnoYJ13+jK1sXO2YStVUzA==","signatures":[{"sig":"MEQCIBicMUnojeHSov0dv/tkgGC+XcRG4Yo7L2AD/Q0Q8lyxAiABl68H2xCWZhvX/tbpg+BRiMClBzFJ8JI0CXG/ZIYplQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1435333},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"b5d9511e765d8ead60e3088be59f659d27a47147","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.9.0","description":"Convex component for wearable device integrations — sync health data from Garmin, Strava, Whoop, Polar, Suunto, Apple HealthKit, Samsung Health, and Google Health Connect.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.1.3_1777289285598_0.5217628200108899","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@clipin/convex-wearables","version":"0.2.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.2.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"4436a61e5be37d6209cb7dca4d8e293ff3a5cf4f","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.2.0.tgz","fileCount":210,"integrity":"sha512-ds/e4eA0zpLgOvgVBlR0O1S8Idmn2ZsLlkBkuNT9+Z6wz39IZad/Y/uMy2BRo0+0tSJVNwUO/ozxysMA2y6mdA==","signatures":[{"sig":"MEUCIQCAlQIonqbNrEotUI7/ToXveEGBVR+erewp6XpXpWedhgIgHhtQ8TUI9XPBfCAz4/kMXvpYBj/SWv0AoUM99CvTWAk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1452985},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"c89a1a647e5862aa94b7cf7c753da42bd953445c","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for wearable device integrations — sync health data from Garmin, Strava, Whoop, Polar, Suunto, Apple HealthKit, Samsung Health, and Google Health Connect.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.2.0_1779221791441_0.37094759857908","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@clipin/convex-wearables","version":"0.2.1","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.2.1","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"ce4d6f55c9a84031fc8363c90da59706232faec4","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.2.1.tgz","fileCount":210,"integrity":"sha512-UNdHvbBuAGhuftGNWhNrrBSzwxj5rZhdhuyt0IkJB7OQQ91JmCjJ8WynYpsQDiBgtbO9q3u1t9YR6BddwSq81A==","signatures":[{"sig":"MEUCIERs59JKg0it4YcPlcoR2KfQsELtttvzXsQqtmZeaMeDAiEAqW2pXXTaVN6cGx6daD8oGW/lrsOJsECzjV1rQU9dms4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1489329},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"7a87cca5ad881cabc685eb7ca7bf061e8aaf8507","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for wearable device integrations — sync health data from Garmin, Strava, Whoop, Polar, Suunto, Apple HealthKit, Samsung Health, and Google Health Connect.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.2.1_1779296632908_0.08831710648340163","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@clipin/convex-wearables","version":"0.3.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.3.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"d6ddb74b39f9f6c8f2ad5e36ce567d36dc75935b","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.3.0.tgz","fileCount":210,"integrity":"sha512-BgOGNd9y/+kiCyrGpjB16npz5Zmc1whQpGO2G9mfVjxtFVPfvvxcOBuQVZ/t14RWZwibXzMgNP6bZXWox9Y84w==","signatures":[{"sig":"MEQCIGPlOCebabbh5SZ9dOzZ0jVezM5v1qwAW11odRW04ETkAiBvIentR4/6zzPVTK4a/IGrVeJRXhmEkIfGZo7CgmNq9g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1514483},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"4f387e5c6701c332e3d9220bcd09d3916e50df25","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for wearable device integrations — sync health data from Garmin, Strava, Whoop, Polar, Suunto, Apple HealthKit, Samsung Health, and Google Health Connect.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.3.0_1779487151770_0.4634644004645825","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@clipin/convex-wearables","version":"0.4.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.4.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"d107281a1fc0a7be000cb143ef3ca1025b814e08","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.4.0.tgz","fileCount":215,"integrity":"sha512-l8VHEJu4x5zakhZIIfvb943KFGUoZiZKf7uEwlB/NzvtikjG3qvWwMmMKlojJbROYqnN5E0fnzxL4gkLWBHGKw==","signatures":[{"sig":"MEUCIGxW2F5oxE+5lNz6j3uG6pzAS/PSIhBxC86acKKrsfzmAiEAhWFRO9n721QKjHm07inDvtv6wcF5PYEL1Xd+EdUY7tE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1541169},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"3b2fc9a6a5e25e1f1d008b60d69d4984663f92bb","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for wearable device integrations — sync health data from Garmin, Strava, Whoop, Polar, Suunto, Apple HealthKit, Samsung Health, and Google Health Connect.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.4.0_1779553503795_0.5007861215439884","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@clipin/convex-wearables","version":"0.5.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.5.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"1957b9992cb934450c57b1e3e6aeebfeca658513","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.5.0.tgz","fileCount":226,"integrity":"sha512-LyXe3m1Pd0Q+sITb8tzaf3W4we+HertoPB4PuoveDMBGdWYl36Gu+2XFaHlxlYzlqgeIE6aVxYD/h/9HrA6m2Q==","signatures":[{"sig":"MEYCIQC92Bq4bUs2FPJn9eDYVC+cIXeGD/AiuVEVpnfUQuAeVgIhAOYkqxA6u679YDwDM1M0buD13b2WbSW4A7r08gnj2oCA","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1664585},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"6b10e46a9b50215de0ab7b2655703bd342d8872f","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.5.0_1784374261668_0.6916226726964947","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@clipin/convex-wearables","version":"0.6.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.6.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"2225ee7c598d6e0ddeee49a58d9ba039847bdac8","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.6.0.tgz","fileCount":226,"integrity":"sha512-d/2YbeX/VzRGrt76ijQL64DVV/5IYUv1D/yvKgg3hB0Q0DhxUEjFPceAsz0vHSOaFfWKwubR/39OcYs4vcZL2Q==","signatures":[{"sig":"MEUCIAxeum6qARrA1Uf93pBLLkjb0PtqF8NR//kD6dYd1mKgAiEA+8goSjvyfUKUrkzgHLpGWdMz7C56R1pwBUYMPdsnd7Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1673214},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"dd631477ee52bae4fa8bf201867a3b6d5d51e213","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.6.0_1784376143054_0.9914494035464851","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@clipin/convex-wearables","version":"0.7.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.7.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"5406cc98ab7f340833e4a560b93039b422610707","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.7.0.tgz","fileCount":228,"integrity":"sha512-QDiHkhwR4DIr7tlJqDbxl38gP8SKV/8MfhATwIecakpD9oM+aIlSmukmDyub/UiUcFQ3r+pPdkqIpxtX/adDog==","signatures":[{"sig":"MEUCIQCyjqyScSJy4vyohFGWSvp5+sypSpeFeZMIHCpGFCY9FgIgdYfmBhqGccMFqmBi3mQ4otkKdBRuYL+M0dnwbfmrojM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1697639},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"4016d07d519aa6330135a8ab8f4a46fe255f5b79","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.7.0_1784388950643_0.386979343653719","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"@clipin/convex-wearables","version":"0.8.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.8.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"a441f22123976eb852f93a859adc2bd52c49bd0f","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.8.0.tgz","fileCount":228,"integrity":"sha512-23iQBkIMIo4fU3uBocJp5ysYW5XKRmg8RHjAv6e5mxxa0jDK9ehn32/Z595poudOI42IFu+Czh2sqEUhHQwzfA==","signatures":[{"sig":"MEUCIASV4NPhVseizA04f4f7gdNbalaXEg0fr01ufW/PU++qAiEAqdU/xS+jM24Ba1JIt8bc8zyDG1rMBzfHTQXwyVnXMU8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1858428},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"8da89a2491c69e5f0a1199aa6c44b90f1d66772a","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.8.0_1785598431263_0.6581612913579216","host":"s3://npm-registry-packages-npm-production"}},"0.9.0":{"name":"@clipin/convex-wearables","version":"0.9.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.9.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"2937002ffd87a71bfb24b84f59c85c7cd95b9a44","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.9.0.tgz","fileCount":234,"integrity":"sha512-fIdK9OE7CK7B7uymkqISkzY33v5NhdKJmeJ8AnKx/CyhgnGYUpiQHSJkBG5oNd05mBsvp1f2yfVHB0RlOQjiTg==","signatures":[{"sig":"MEYCIQCwuJ7H54ckVYrw3R0lT+0Duq6XPtEzCQ24I3mUz0s9kgIhAJj6lDP7Hnr8N5Noja0l9CaBvLFiqDo8nXlb2MUolaxE","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1947820},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"249830d24440e4a93651e87aec3061f4e4d98309","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.9.0_1785601398724_0.8746400281890649","host":"s3://npm-registry-packages-npm-production"}},"0.10.0":{"name":"@clipin/convex-wearables","version":"0.10.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.10.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"d157d375b80404449c3aba3f4b0c193636ae8c3c","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.10.0.tgz","fileCount":251,"integrity":"sha512-CSXGbBpUUqUBge0J7SzJN04MEfTDT19TeoOFJIKI7DGYdLyBPGaKrQq4bMGUh3M1DbiAv0OPmwcHk2KAsHn6Sw==","signatures":[{"sig":"MEYCIQCL5hEOoXW817syCHtNviWSFPCv84WKfHcF7bTGqTjZ5gIhAP/iXaYW5zDhqfnKiQyL9IAq/r/coJZ7zpYUH1j3w3+h","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2099198},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"dd91084bfa481a84277faabd3106fba86f63e93f","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","fit-file-parser":"^4.1.0","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.10.0_1785694252946_0.9978206984762861","host":"s3://npm-registry-packages-npm-production"}},"0.11.0":{"name":"@clipin/convex-wearables","version":"0.11.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.11.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"c075b8d5ed86a372a1db60d40fa0d41f1aab7184","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.11.0.tgz","fileCount":257,"integrity":"sha512-VahEYiMGh1ITlv82SsA0YPm6pJ0vLztyKRztXGdF2bhwAqMoPEYRwElCm4SHBm27Svnw7jxXCzKJQFUB2Yn/+Q==","signatures":[{"sig":"MEUCIQCP67UoAc8dd55+/8PaU5jHI4CNMb62ozfyMTnW4EEuRQIgK6CsA6ojD+shHh7F17FfYqxU0q8gPqWnvjkYYrnoRA0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2380338},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"1d739292ac2c5116928f34f6f45d999805279ed1","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","fit-file-parser":"^4.1.0","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.11.0_1785699839263_0.6446886240353482","host":"s3://npm-registry-packages-npm-production"}},"0.12.0":{"name":"@clipin/convex-wearables","version":"0.12.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.12.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"553b3f4886924d05964a5fcf34cd769c2a06f0f9","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.12.0.tgz","fileCount":268,"integrity":"sha512-3cWOa3p+S5zgR1fYf8D05Anh2uw1uQnIhGCzLCHBIGoLDq86gH8a6sN1Gijqn/I0SnmKB8amO5eAm4A/mol7AA==","signatures":[{"sig":"MEUCIQDzYoJl+K1KeP3RxEak9/VFVn+ckLqsdOctskoUGgc2lAIgF3aG9j+bIKKGnk+ceYioJxUnQ3j80PKTCJHyZqp5Rxg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2827902},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"543e7569967671a7fb759da22daf1d262e3929ed","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","fit-file-parser":"^4.1.0","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.12.0_1786286052791_0.15419587466480533","host":"s3://npm-registry-packages-npm-production"}},"0.13.0":{"name":"@clipin/convex-wearables","version":"0.13.0","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.13.0","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"1ddc9b01cec047d5bf32653c3ed44fe54b6a80af","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.13.0.tgz","fileCount":268,"integrity":"sha512-pFnVH0fENb9Kn7VaWLAlvfVLecpDbSB9dgcN5VlacscwcnaVyt9luBvPWNmM25qxVIbfPrpZudJWQpfUjH0Cmg==","signatures":[{"sig":"MEYCIQDkjKn0sHo/lJXrxt8hYdPd4IMxewP3lUlxJLDWvcvQnQIhAMUXYTzSS2lVO9Jmhk8zHFnsZxqVqkxW7auVSPahtSSg","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2889928},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"c4b0d314aa1754ae96f6142b95565039b02e2ce7","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.14.1","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","fit-file-parser":"^4.1.0","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.13.0_1786296625011_0.9192386635727445","host":"s3://npm-registry-packages-npm-production"}},"0.13.1":{"name":"@clipin/convex-wearables","version":"0.13.1","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.13.1","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"693423d0207435a71689672b6f22118626f05367","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.13.1.tgz","fileCount":269,"integrity":"sha512-S5Pa+pkn+w8sJq2tynZCDwLoYT2fFc8ATIR5VhfrT6PuEyvDGNxH4i5hinXA66jGrNVBFr3dQCCsTtwcdyxJ9A==","signatures":[{"sig":"MEYCIQDKrkOC2oEvo/y+JQqtKZEYdnKBjUdofejwnIVIMXjTdgIhAKiiiZqwtobfXPs0r5iE1D3gClGSusQ7oq/6FNKgaZoC","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2891974},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"3022de7c1a17eaafe93bef2b64ade8f32b0b5392","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git","directory":"packages/convex-wearables"},"_npmVersion":"11.14.1","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"_nodeVersion":"24.7.0","dependencies":{"convex-helpers":">=0.1.99","fit-file-parser":"^4.1.0","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.13.1_1786307957700_0.9894496514096298","host":"s3://npm-registry-packages-npm-production"}},"0.7.1":{"name":"@clipin/convex-wearables","version":"0.7.1","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.7.1","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"3492cf7f6263b6d00889d41f6e42d68e33f7da65","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.7.1.tgz","fileCount":228,"integrity":"sha512-qVMrHsSUp06hSD0gJZVUhzHnXR2Ev131lKxdip6/8PXXOwj+wdpICC4jRtBE97CSCZMrLhDnh21eAPdJ0zBxGQ==","signatures":[{"sig":"MEUCICtZrERs+JQ6HZZnjpiwfZvFqv6asFbO9zgpxDS2CgFAAiEA4XHVylb82zMxarwCSF2oW+kN2OidRc57W8K52hes4Nk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQC1Un8wCkgTL6QuFWiyeW/12aJ7dGMHucF8a5Ifh9Ox0wIhAMWhimgrtAns2aL8VWKhwJEim04BsfQWoaAU2p4ed3ai","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1703580},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git"},"_npmVersion":"11.19.0","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"_nodeVersion":"26.7.0","dependencies":{"convex-helpers":">=0.1.99","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.7.1_1790260284824_0.11489978129839629","host":"s3://npm-registry-packages-npm-production"}},"0.13.2":{"name":"@clipin/convex-wearables","version":"0.13.2","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"license":"Apache-2.0","_id":"@clipin/convex-wearables@0.13.2","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"homepage":"https://github.com/clipinfit/convex-wearables#readme","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"088bef5f1eb72d23a24e10b8a976c9cca3f3f01b","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-0.13.2.tgz","fileCount":269,"integrity":"sha512-bY5qlb0S5qDgLBK7IN3y7iwLvEBV929z2nWzKCnpUrScO7i0IlZzx4jKsJJfoHEys5aQ1UII5opKZUbsE8FOXw==","signatures":[{"sig":"MEUCIQCT0V5/ug0wkXmYqiPf2dWY6jWxqEKkWTrT6OKa8C5pDQIgNEryn7t7F7HjA/Y3uCy8StQcPPzBinJxzVcr+sS1iDo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIEktdh9atoMdG52/Rj4uLWLu/rJMsiu2d2sNt8H7ASJ2AiEA5NJJSUDmU9pmnHhVParmFp2t1KWS9dInf0S68oWmgMs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2897883},"main":"./dist/client/index.js","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"d68d58515fb46c7c1770056c2fe5c4816b742076","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git","directory":"packages/convex-wearables"},"_npmVersion":"11.19.0","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"_nodeVersion":"26.7.0","dependencies":{"convex-helpers":">=0.1.99","fit-file-parser":"^4.1.0","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-wearables_0.13.2_1790260311873_0.1435422476881476","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"_id":"@clipin/convex-wearables@1.0.0","bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"dist":{"shasum":"57ec2d83e9eec7b1d9e1ddb7550604982c5c15cd","tarball":"https://registry.npmjs.org/@clipin/convex-wearables/-/convex-wearables-1.0.0.tgz","fileCount":285,"integrity":"sha512-hbO7iEGZ2Qty43WfC4bGS3PEvWS6MNsYoCVj0Gdsigvsoc/YyVzE7+UwYaRb1Us5imf4dZ1rUP1sHUaA4K8Giw==","signatures":[{"sig":"MEYCIQDB+DZREqjcqFHuH0OR3oGRIt1WCcg+ubL3XppcRac16QIhAPiy0AVp7dtGjRt481xpErHx5Tm/QUx1D3NuhLU9mcFf","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIE5Eb+1ts+wJTyntzsxLN6jQgT6Z+Co+K6nU3LoY26v7AiBbEgfpg4Xl1a00jUleuRPSts3xxTV3moqCbp7tGQUZzQ=="}],"unpackedSize":2936349},"main":"./dist/client/index.js","name":"@clipin/convex-wearables","type":"module","types":"./dist/client/index.d.ts","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./types":{"types":"./dist/client/types.d.ts","default":"./dist/client/types.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./outgoing-webhooks/node":{"types":"./dist/host/outgoingWebhookNode.d.ts","default":"./dist/host/outgoingWebhookNode.js"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"},"./outgoing-webhooks/protocol":{"types":"./dist/client/outgoingWebhookProtocol.d.ts","default":"./dist/client/outgoingWebhookProtocol.js"}},"gitHead":"e55cef7e167810b0734630c8d4854b81a12fdf3e","license":"Apache-2.0","scripts":{"dev":"tsc -p tsconfig.build.json --watch","lint":"biome check .","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"biome format --write .","prepack":"npm run build","release":"tsx scripts/release.ts","lint:fix":"biome check --write .","prebuild":"node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"version":"1.0.0","_npmUser":{"name":"denis","email":"dciccale@gmail.com"},"homepage":"https://github.com/clipinfit/convex-wearables#readme","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git","directory":"packages/convex-wearables"},"_npmVersion":"11.19.0","description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","directories":{},"maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"_nodeVersion":"26.7.0","dependencies":{"convex-helpers":">=0.1.99","fit-file-parser":"^4.1.0","@convex-dev/workflow":"^0.3.6"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","convex":"^1.17.0","vitest":"^3.0.0","typescript":"~5.6.0","@types/node":"^22.0.0","convex-test":"^0.0.35","@biomejs/biome":"2.4.7","@edge-runtime/vm":"^5.0.0"},"peerDependencies":{"convex":">=1.17.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/convex-wearables_1.0.0_1790317153150_0.8456460168384377"}}},"time":{"created":"2026-03-20T15:18:57.411Z","modified":"2026-09-25T06:19:13.445Z","0.0.1":"2026-03-20T15:18:57.650Z","0.0.2":"2026-03-22T14:25:04.144Z","0.0.3":"2026-03-22T20:17:44.859Z","0.1.0":"2026-03-24T16:45:18.578Z","0.1.1":"2026-04-24T21:09:41.425Z","0.1.2":"2026-04-25T07:45:34.896Z","0.1.3":"2026-04-27T11:28:05.750Z","0.2.0":"2026-05-19T20:16:31.676Z","0.2.1":"2026-05-20T17:03:53.087Z","0.3.0":"2026-05-22T21:59:11.973Z","0.4.0":"2026-05-23T16:25:03.958Z","0.5.0":"2026-07-18T11:31:01.849Z","0.6.0":"2026-07-18T12:02:23.281Z","0.7.0":"2026-07-18T15:35:50.829Z","0.8.0":"2026-08-01T15:33:51.468Z","0.9.0":"2026-08-01T16:23:18.882Z","0.10.0":"2026-08-02T18:10:53.189Z","0.11.0":"2026-08-02T19:43:59.434Z","0.12.0":"2026-08-09T14:34:12.950Z","0.13.0":"2026-08-09T17:30:25.909Z","0.13.1":"2026-08-09T20:39:17.911Z","0.7.1":"2026-09-24T14:31:24.926Z","0.13.2":"2026-09-24T14:31:51.974Z","1.0.0":"2026-09-25T06:19:13.261Z"},"bugs":{"url":"https://github.com/clipinfit/convex-wearables/issues"},"license":"Apache-2.0","homepage":"https://github.com/clipinfit/convex-wearables#readme","keywords":["convex","convex-component","wearables","health","garmin","strava","whoop","polar","suunto","apple-health","samsung-health","google-health-connect","fitness","iot"],"repository":{"url":"git+https://github.com/clipinfit/convex-wearables.git","type":"git","directory":"packages/convex-wearables"},"description":"Convex component for normalized wearable integrations and deterministic synthetic health data.","maintainers":[{"name":"denis","email":"dciccale@gmail.com"}],"readme":"# @clipin/convex-wearables\n\n[![Convex Component](https://www.convex.dev/components/badge/clipin/convex-wearables)](https://www.convex.dev/components/clipin/convex-wearables)\n\nA [Convex component](https://docs.convex.dev/components) for wearable device integrations. Sync health data from **Garmin, Strava, Whoop, Polar, Suunto, Apple HealthKit, Samsung Health, and Google Health Connect**, or generate it with the built-in **Synthetic provider**, in your Convex app.\n\nBuilt as a drop-in module: install the component, pass your provider credentials, and start querying workouts, sleep sessions, heart rate, and 88 pre-defined health metrics — all in TypeScript, no backend glue code required.\n\n## Inspiration\n\nConvex Wearables began with a simple idea: build something in the spirit of\n[Open Wearables](https://github.com/the-momentum/open-wearables), but natively\nfor Convex. The projects are independent. We value its broad, provider-neutral\napproach and aim to offer a comparably capable integration surface while making\nour own architectural decisions around Convex components, transactions,\nworkflows, reactive queries, and storage. Convex Wearables also adds\nConvex-native capabilities such as tiered time-series retention, durable\nlifecycle operations, and deterministic synthetic data.\n\n## Features\n\n- **OAuth 2.0 flows** with PKCE support — authorize users, exchange tokens, auto-refresh\n- **Automatic sync** — cron-triggered or on-demand data fetching from provider APIs\n- **Normalized data model** — workouts, sleep, time-series metrics, and daily summaries in a unified schema\n- **40+ workout types** mapped to a unified taxonomy (running, cycling, swimming, yoga, etc.)\n- **88 pre-defined series types** — heart rate, HRV, SpO2, steps, weight, body temperature, and more\n- **Cursor-based pagination** — efficient data access within Convex's scan limits\n- **Configurable time-series storage policy** — keep full raw data, retain recent raw + historical rollups, or store rollups only\n- **Deduplication** — events and data points are deduped by external ID and source+timestamp\n- **Precomputed daily summaries** — activity, sleep, recovery, and body composition aggregates\n- **Durable data lifecycle** — explicit disconnect, provider deregistration, and resumable provider/user deletion workflows\n- **Webhook + SDK push support** — durable WHOOP v2, Polar, Suunto, and Garmin inbound webhooks plus normalized mobile SDK ingestion\n- **Synthetic provider** — deterministic, explicitly enabled wearable fixtures using the same normalized model\n- **Full TypeScript** — end-to-end type safety from provider API to client query\n\n## Installation\n\n```bash\nnpm install @clipin/convex-wearables convex\n```\n\n`convex` is a peer dependency and should be `>= 1.17.0`.\n\n## Quick Start\n\n### 1. Install the component in your Convex app\n\n```ts\n// convex/convex.config.ts\nimport { defineApp } from \"convex/server\";\nimport wearables from \"@clipin/convex-wearables/convex.config\";\n\nconst app = defineApp();\napp.use(wearables);\n\nexport default app;\n```\n\n### 2. Create the client\n\n```ts\n// convex/wearables.ts\nimport { WearablesClient, type ProviderName } from \"@clipin/convex-wearables\";\nimport { components } from \"./_generated/api\";\n\nexport const wearables = new WearablesClient(components.wearables, {\n  providers: {\n    strava: {\n      clientId: process.env.STRAVA_CLIENT_ID!,\n      clientSecret: process.env.STRAVA_CLIENT_SECRET!,\n    },\n    garmin: {\n      clientId: process.env.GARMIN_CLIENT_ID!,\n      clientSecret: process.env.GARMIN_CLIENT_SECRET!,\n    },\n    // Add more providers as needed\n  },\n});\n```\n\n### 3. Use in your queries and mutations\n\n```ts\n// convex/workouts.ts\nimport { query, mutation } from \"./_generated/server\";\nimport { v } from \"convex/values\";\nimport { wearables } from \"./wearables\";\n\n// Get a user's recent workouts\nexport const listWorkouts = query({\n  args: {\n    userId: v.string(),\n    cursor: v.optional(v.string()),\n  },\n  handler: async (ctx, args) => {\n    return await wearables.getEvents(ctx, {\n      userId: args.userId,\n      category: \"workout\",\n      limit: 20,\n      cursor: args.cursor,\n    });\n  },\n});\n\n// Get heart rate time-series for the last 24 hours\nexport const getHeartRate = query({\n  args: { userId: v.string() },\n  handler: async (ctx, args) => {\n    const now = Date.now();\n    return await wearables.getTimeSeries(ctx, {\n      userId: args.userId,\n      seriesType: \"heart_rate\",\n      startDate: now - 24 * 60 * 60 * 1000,\n      endDate: now,\n    });\n  },\n});\n\n// Get daily activity summaries for a date range\nexport const getWeeklySummary = query({\n  args: { userId: v.string() },\n  handler: async (ctx, args) => {\n    return await wearables.getDailySummaries(ctx, {\n      userId: args.userId,\n      provider: \"garmin\",\n      category: \"activity\",\n      startDate: \"2026-03-09\",\n      endDate: \"2026-03-15\",\n    });\n  },\n});\n\n// Disconnect a provider\nexport const disconnectProvider = mutation({\n  args: {\n    userId: v.string(),\n    provider: v.string(),\n  },\n  handler: async (ctx, args) => {\n    await wearables.disconnect(ctx, {\n      userId: args.userId,\n      provider: args.provider as ProviderName,\n    });\n  },\n});\n```\n\n### 4. Configure time-series storage policy (optional)\n\nBy default, the component behaves exactly as before: all raw time-series points are stored indefinitely.\n\nIf you want to reduce row growth for dense series like Garmin heart rate, persist a tier-based policy once from your app:\n\n```ts\n// convex/adminWearables.ts\nimport { mutation } from \"./_generated/server\";\nimport { wearables } from \"./wearables\";\n\nexport const configureWearablesStorage = mutation({\n  args: {},\n  handler: async (ctx) => {\n    await wearables.replaceTimeSeriesPolicyConfiguration(ctx, {\n      defaultRules: [\n        // Global fallback: keep sparse metrics fully raw forever.\n        {\n          tiers: [{ kind: \"raw\", fromAge: \"0m\", toAge: null }],\n        },\n        // Garmin heart rate: raw for 24h, 30-minute rollups until day 7, then 3-hour rollups.\n        {\n          provider: \"garmin\",\n          seriesType: \"heart_rate\",\n          tiers: [\n            { kind: \"raw\", fromAge: \"0m\", toAge: \"24h\" },\n            { kind: \"rollup\", fromAge: \"24h\", toAge: \"7d\", bucket: \"30m\" },\n            { kind: \"rollup\", fromAge: \"7d\", toAge: null, bucket: \"3h\" },\n          ],\n        },\n        // Lower-value dense signals can skip raw storage entirely.\n        {\n          provider: \"garmin\",\n          seriesType: \"oxygen_saturation\",\n          tiers: [\n            {\n              kind: \"rollup\",\n              fromAge: \"0m\",\n              toAge: null,\n              bucket: \"5m\",\n              aggregations: [\"avg\", \"min\", \"max\", \"last\", \"count\"],\n            },\n          ],\n        },\n      ],\n      presets: [\n        {\n          key: \"pro\",\n          rules: [\n            {\n              provider: \"garmin\",\n              seriesType: \"heart_rate\",\n              tiers: [\n                { kind: \"raw\", fromAge: \"0m\", toAge: \"7d\" },\n                { kind: \"rollup\", fromAge: \"7d\", toAge: null, bucket: \"1h\" },\n              ],\n            },\n          ],\n        },\n      ],\n      maintenance: {\n        enabled: true,\n        interval: \"1h\",\n      },\n    });\n\n    await wearables.setUserTimeSeriesPolicyPreset(ctx, {\n      userId: \"pro-user-123\",\n      presetKey: \"pro\",\n    });\n  },\n});\n```\n\nThose rules are stored inside the component and then used automatically by manual syncs, cron syncs, Garmin webhooks, and SDK push ingestion.\n\n## API Reference\n\n### `WearablesClient`\n\nThe main API surface. Instantiate once with your component reference and provider credentials.\n\n```ts\nconst wearables = new WearablesClient(components.wearables, config);\n```\n\n#### Connection Management\n\n| Method | Description |\n|--------|-------------|\n| `getConnections(ctx, { userId })` | Get all connections for a user (tokens stripped) |\n| `getConnection(ctx, { userId, provider })` | Get a specific provider connection |\n| `getSyncStatus(ctx, { userId })` | Get sync status across all providers |\n| `disconnect(ctx, { userId, provider })` | Disconnect a provider (clears tokens, sets inactive) |\n\n#### Events (Workouts & Sleep)\n\n| Method | Description |\n|--------|-------------|\n| `getEvents(ctx, { userId, category, startDate?, endDate?, limit?, cursor? })` | Paginated events query |\n| `getEventsWithSources(ctx, { userId, category, provider?, dataSourceId?, ... })` | Paginated events plus provider/writer/device provenance |\n| `getEvent(ctx, { eventId })` | Get a single event by ID |\n| `getWorkoutEnrichment(ctx, { eventId })` | Get normalized laps, splits, lengths, sets, and zones |\n| `upsertWorkoutEnrichment(ctx, input)` | Replace normalized enrichment from a custom parser/provider |\n\nThe `category` parameter is `\"workout\"` or `\"sleep\"`. Results are ordered by start time (newest first). Pagination uses cursor-based tokens returned in `nextCursor`.\n\n#### Time Series\n\n| Method | Description |\n|--------|-------------|\n| `getTimeSeries(ctx, { userId, seriesType, startDate, endDate, limit? })` | Get time-series data points |\n| `getTimeSeriesWithSources(ctx, { userId, seriesType, startDate, endDate, provider?, dataSourceId?, limit?, order? })` | Get policy-aware points plus provider/writer/device provenance |\n| `getLatestDataPoint(ctx, { userId, seriesType })` | Get the most recent value for a metric |\n| `getAvailableSeriesTypes(ctx, { userId })` | List which metric types have data |\n| `getTimeSeriesPolicyConfiguration(ctx)` | Read the persisted default rules, presets, and maintenance settings |\n| `getUserTimeSeriesPolicyPreset(ctx, { userId })` | Read a user's assigned preset, if any |\n| `getEffectiveTimeSeriesPolicy(ctx, { userId, provider, seriesType })` | Resolve the effective policy after preset assignment and default fallback |\n\nSee [Series Types](#series-types) for all 88 supported metrics.\n\nWhen a query returns rollup-backed points, each point can also include:\n\n- `resolution` — `\"raw\"` or `\"rollup\"`\n- `bucketMinutes`\n- `avg`, `min`, `max`, `last`, `count`\n\n#### Daily Summaries\n\n| Method | Description |\n|--------|-------------|\n| `getDailySummaries(ctx, { userId, provider?, category, startDate, endDate })` | Get daily aggregates, optionally scoped to one provider |\n\nCategories: `\"activity\"`, `\"sleep\"`, `\"recovery\"`, `\"body\"`.\n\nDaily summary rows are provider-aware. Multi-provider apps should pass\n`provider` or apply their own canonical source precedence before showing totals;\nomitting `provider` returns the storage rows across providers for the requested\ncategory and date range.\n\n#### Data Sources\n\n| Method | Description |\n|--------|-------------|\n| `getDataSources(ctx, { userId })` | List every provider/writer/device stream for a user |\n| `getProviderDataSources(ctx, { userId, provider })` | List streams for one provider family |\n| `getOrCreateDataSource(ctx, { userId, provider, deviceModel?, source? })` | Get or create a data source |\n\nSource-aware reads return a normalized envelope. Each event or point contains a\nstable `dataSourceId`, while the matching metadata is returned once in the\n`dataSources` sidecar:\n\n```ts\nconst result = await wearables.getTimeSeriesWithSources(ctx, {\n  userId,\n  seriesType: \"heart_rate\",\n  startDate,\n  endDate,\n});\n\nconst sourcesById = new Map(result.dataSources.map((source) => [source._id, source]));\n\nfor (const point of result.points) {\n  const source = sourcesById.get(point.dataSourceId);\n  console.log({\n    value: point.value,\n    provider: source?.provider, // integration family, e.g. \"google\"\n    writer: source?.originalSourceName ?? source?.source, // e.g. \"Fitbit\"\n    device: source?.deviceModel,\n  });\n}\n```\n\nPass `provider` to isolate one provider family, or `dataSourceId` to isolate one\nexact writer/device stream. These methods preserve independent streams; they do\nnot choose a preferred provider or suppress a Garmin record mirrored through\nStrava, HealthKit, or Health Connect. Canonicalization remains consumer-owned.\nNo schema migration is required when adopting the source-aware APIs.\n\n#### Sync Control\n\n| Method | Description |\n|--------|-------------|\n| `createSyncJob(ctx, { userId, provider? })` | Create a sync job record |\n| `getSyncJobs(ctx, { userId, limit? })` | Get recent sync jobs |\n| `syncAllActive(ctx, { syncWindowHours? })` | Trigger a sync across all active connections |\n\n#### Synthetic provider\n\n| Method | Description |\n|--------|-------------|\n| `isSyntheticProviderEnabled()` | Check whether userland enabled the integration |\n| `seedSyntheticData(ctx, args)` | Generate a connected and synced `synthetic` provider data set |\n| `getSyntheticDataStatus(ctx, { userId })` | Inspect its date range and normalized row counts |\n| `clearSyntheticData(ctx, { userId })` | Idempotently remove the user's synthetic integration |\n\nEnable the provider explicitly when constructing the client. Keep this setting\noff in production userland:\n\n```ts\nconst wearables = new WearablesClient(components.wearables, {\n  providers: {\n    synthetic: { enabled: process.env.ENABLE_SYNTHETIC_WEARABLES === \"true\" },\n  },\n});\n```\n\nCall it from an authenticated host mutation. The host chooses the user and owns\nany admin/development authorization policy:\n\n```ts\nexport const seedWearables = mutation({\n  args: {\n    userId: v.string(),\n    startDate: v.string(),\n    endDate: v.string(),\n  },\n  handler: async (ctx, args) => {\n    // Apply the host app's admin authorization here.\n    return await wearables.seedSyntheticData(ctx, {\n      ...args,\n      timezone: \"Europe/Madrid\",\n      profile: \"mixed\",\n      asOf: Date.now(),\n      replaceExisting: true,\n    });\n  },\n});\n```\n\nThe explicit range may cover up to 31 days and cannot end after the `asOf` day.\nGenerated events and time-series points never extend past `asOf`, which defaults\nto generation time. Prior calendar days stay stable when a range grows.\n`replaceExisting` atomically replaces that user's previous synthetic\nintegration. Use `profile: \"sedentary\"` for a deterministic partial-score UI\nstate. Use `profile: \"showcase\"` for a seeded physically active user: each\nMonday-to-Sunday block contains four perfect target days, two days in the\n80–90 range, and one day below 70 when scored against 3,500 steps, 350 active\ncalories, and seven hours of sleep. Garmin and other real connections coexist because generated rows use\n`provider: \"synthetic\"`, with a normal connection and a `SynthDevice` data\nsource. The provider advertises generated-data capability but no OAuth, pull,\nwebhook, or backfill capability, so existing sync routing ignores it naturally.\n\nThe component namespace is also available directly as\n`components.wearables.synthetic`. No HTTP endpoint is registered. Direct\ncomponent use bypasses the client's userland enablement check, so expose it only\nthrough an authenticated host function with the same policy.\n\n#### OAuth\n\n| Method | Description |\n|--------|-------------|\n| `generateAuthUrl(ctx, { userId, provider, redirectUri })` | Build an OAuth URL using configured provider credentials |\n| `handleCallback(ctx, { provider, state, code })` | Exchange a callback code and persist the resulting connection |\n\n#### Lifecycle\n\n| Method | Description |\n|--------|-------------|\n| `deleteAllUserData(ctx, { userId })` | Delete all data for a user (GDPR) |\n\n#### Configuration\n\n| Method | Description |\n|--------|-------------|\n| `getProviderCredentials(provider)` | Get credentials for a provider |\n| `getConfiguredProviders()` | List all configured providers |\n| `getProviderCapabilities(provider)` | Get static sync/delivery capabilities for one provider |\n| `getProviderCapabilityInfo(provider)` | Get capabilities plus derived UI-friendly flags |\n| `getAllProviderCapabilityInfo()` | Get capability info for every supported provider |\n| `replaceTimeSeriesPolicyConfiguration(ctx, { defaultRules, presets?, maintenance? })` | Replace the persisted time-series policy configuration |\n| `setUserTimeSeriesPolicyPreset(ctx, { userId, presetKey })` | Assign or clear a user-specific preset |\n\n### Provider Capabilities\n\nProvider capabilities are static metadata that describe how each provider delivers data.\nThey are additive client-side helpers; they do not change sync behavior or require a\nschema migration.\n\n```ts\nimport {\n  getProviderCapabilityInfo,\n  supportsBackfill,\n  supportsManualSync,\n} from \"@clipin/convex-wearables\";\n\nconst garmin = getProviderCapabilityInfo(\"garmin\");\nconsole.log(garmin.defaultLiveSyncMode); // \"webhook\"\nconsole.log(supportsManualSync(\"garmin\")); // false\nconsole.log(supportsBackfill(\"garmin\")); // true\n```\n\nUse these helpers in host apps to decide whether to show cloud sync, mobile SDK\nsync, webhook status, or historical backfill controls without hardcoding provider\nnames in app UI.\n\n## Time-Series Storage Policy\n\nThe storage policy is optional.\n\n- If you do nothing, the built-in fallback is one raw tier from age `0` to `null`, which means raw points are stored indefinitely.\n- Policies are persisted in Convex and applied centrally in the shared time-series write path.\n- The same rules apply to workflow syncs, Garmin webhooks, and SDK push ingestion.\n- Default rules are deployment-wide.\n- Presets are optional and can be assigned per user.\n\n### Precedence\n\nDefault rules are matched in this order:\n\n1. exact `provider + seriesType`\n2. `seriesType` across all providers\n3. `provider` across all series\n4. global policy\n5. built-in fallback: raw forever\n\nIf a user has a preset assignment, matching starts inside that preset first and falls back to the default rules only when the preset does not define a rule for that provider/series pair.\n\n### Tier model\n\nEach rule is a list of contiguous tiers ordered by age:\n\n- `fromAge: \"0m\"` means \"newest data\"\n- age grows as data gets older\n- `toAge: null` means \"keep this tier forever\"\n- if no tier matches a point's age, that data is deleted\n\nTwo tier kinds are supported:\n\n- `raw`: keep original data points\n- `rollup`: keep bucketed aggregates such as `avg`, `min`, `max`, `last`, and `count`\n\nDurations accept a numeric millisecond value or a compact string such as `30m`, `24h`, `7d`, or `2w`.\n\n### Configuration shape\n\nEach default rule or preset rule supports:\n\n- `provider?`\n- `seriesType?`\n- `tiers`\n\nEach tier supports:\n\n- raw tier:\n  - `kind: \"raw\"`\n  - `fromAge`\n  - `toAge`\n- rollup tier:\n  - `kind: \"rollup\"`\n  - `fromAge`\n  - `toAge`\n  - `bucket`\n  - `aggregations?`\n\nRollup aggregations default to `[\"avg\", \"min\", \"max\", \"last\", \"count\"]`.\n\n### Configuration reference\n\nUse `replaceTimeSeriesPolicyConfiguration(ctx, { defaultRules, presets?, maintenance? })` to persist the policy model, and `setUserTimeSeriesPolicyPreset(ctx, { userId, presetKey })` to assign a preset to a specific user.\n\n#### Top-level configuration\n\n| Field | Type | Required | Description |\n|---|---|---|---|\n| `defaultRules` | `TimeSeriesPolicyRuleInput[]` | Yes | Deployment-wide fallback rules used for all users unless a preset match overrides them. |\n| `presets` | `TimeSeriesPolicyPresetInput[]` | No | Named policy sets that can be assigned per user. |\n| `maintenance.enabled` | `boolean` | No | Enables the internal maintenance loop. Defaults to `true`. |\n| `maintenance.interval` | `string \\| number` | No | How often the maintenance loop should run. Accepts compact duration strings like `1h` or a millisecond number. |\n\n#### Rule fields\n\n| Field | Type | Required | Description |\n|---|---|---|---|\n| `provider` | `ProviderName` | No | Limits the rule to one provider. Omit for \"all providers\". |\n| `seriesType` | `SeriesType \\| string` | No | Limits the rule to one metric. Omit for \"all series types\". |\n| `tiers` | `TimeSeriesTierInput[]` | Yes | Ordered list of age-based storage tiers. Must be contiguous from newest to oldest. |\n\nRules may be:\n\n- global: no `provider`, no `seriesType`\n- provider-only: `provider` only\n- series-only: `seriesType` only\n- exact: both `provider` and `seriesType`\n\n#### `provider` values\n\nValid `provider` values are:\n\n- `garmin`\n- `suunto`\n- `polar`\n- `whoop`\n- `strava`\n- `apple`\n- `samsung`\n- `google`\n\nUse `provider` only when you want different retention for the same metric depending on where it came from.\n\n#### `seriesType` values\n\n`seriesType` should be one of the supported metric keys from [`SERIES_TYPES`](#series-types).\n\nExamples:\n\n- `heart_rate`\n- `resting_heart_rate`\n- `oxygen_saturation`\n- `steps`\n- `weight`\n- `respiratory_rate`\n- `garmin_stress_level`\n\nFor full safety in your app code, use the exported `SeriesType` type or `SERIES_TYPES` constant:\n\n```ts\nimport { SERIES_TYPES, type SeriesType } from \"@clipin/convex-wearables\";\n\nconst metric: SeriesType = \"heart_rate\";\nconst validKey = SERIES_TYPES.heart_rate;\n```\n\nIf you omit `seriesType`, the rule applies to every supported metric for that scope.\n\n#### Tier fields\n\n| Field | Type | Required | Applies to | Description |\n|---|---|---|---|---|\n| `kind` | `\"raw\" \\| \"rollup\"` | Yes | all tiers | Whether the tier stores original rows or bucketed aggregates. |\n| `fromAge` | `string \\| number` | Yes | all tiers | Lower age boundary for the tier. `0m` means newest data. |\n| `toAge` | `string \\| number \\| null` | Yes | all tiers | Upper age boundary. `null` means open-ended / forever. |\n| `bucket` | `string \\| number` | Yes | rollup only | Bucket size for rollups. Must normalize to a whole number of minutes. |\n| `aggregations` | `(\"avg\" \\| \"min\" \\| \"max\" \\| \"last\" \\| \"count\")[]` | No | rollup only | Which summary values to keep for rollup rows. Defaults to all five. |\n\n#### Duration values\n\nAll duration fields accept either:\n\n- a compact string: `500ms`, `30s`, `15m`, `24h`, `7d`, `2w`\n- a non-negative number interpreted as milliseconds\n\nExamples:\n\n- `fromAge: \"0m\"`\n- `toAge: \"24h\"`\n- `bucket: \"30m\"`\n- `interval: \"1h\"`\n\n#### Tier validation rules\n\nThe implementation currently enforces:\n\n- the first tier must start at age `0`\n- tiers must be contiguous with no gaps and no overlap\n- open-ended tiers (`toAge: null`) must be the final tier\n- only one raw tier is allowed in a single rule\n- rollup buckets must be positive whole-minute durations\n\nIf data becomes older than the last tier, maintenance deletes it.\n\n#### Aggregation values and meaning\n\n| Aggregation | Meaning |\n|---|---|\n| `avg` | Arithmetic mean of all samples in the bucket |\n| `min` | Lowest value seen in the bucket |\n| `max` | Highest value seen in the bucket |\n| `last` | Most recent value seen in the bucket |\n| `count` | Number of samples combined into the bucket |\n\nWhen querying rollup-backed data:\n\n- the response always includes `value`\n- `value` is selected from the configured aggregations using this priority: `avg`, then `last`, then `max`, then `min`, then `count`\n- the rollup point also exposes `avg`, `min`, `max`, `last`, and `count` fields when available in storage\n\nSo if you configure:\n\n```ts\naggregations: [\"last\"]\n```\n\nthen `value` for that rollup point will be the bucket's `last` value.\n\n#### Presets and per-user behavior\n\nEach preset has:\n\n| Field | Type | Required | Description |\n|---|---|---|---|\n| `key` | `string` | Yes | Stable preset identifier such as `free`, `pro`, or `enterprise`. |\n| `rules` | `TimeSeriesPolicyRuleInput[]` | Yes | Rules evaluated before the default rules for users assigned to that preset. |\n\nUser assignment behavior:\n\n- assign a preset with `setUserTimeSeriesPolicyPreset({ userId, presetKey: \"pro\" })`\n- clear a preset with `setUserTimeSeriesPolicyPreset({ userId, presetKey: null })`\n- if a user's preset has no matching rule for a provider/series pair, evaluation falls back to `defaultRules`\n\nThis means per-user support is implemented as preset assignment, not as arbitrary custom rule blobs per user.\n\n#### Practical examples\n\nKeep raw data forever:\n\n```ts\n{\n  tiers: [{ kind: \"raw\", fromAge: \"0m\", toAge: null }],\n}\n```\n\nKeep raw 24h, then 30-minute rollups for 7 days, then delete:\n\n```ts\n{\n  provider: \"garmin\",\n  seriesType: \"heart_rate\",\n  tiers: [\n    { kind: \"raw\", fromAge: \"0m\", toAge: \"24h\" },\n    { kind: \"rollup\", fromAge: \"24h\", toAge: \"7d\", bucket: \"30m\" },\n  ],\n}\n```\n\nKeep no raw data, only 5-minute rollups forever:\n\n```ts\n{\n  provider: \"garmin\",\n  seriesType: \"oxygen_saturation\",\n  tiers: [\n    {\n      kind: \"rollup\",\n      fromAge: \"0m\",\n      toAge: null,\n      bucket: \"5m\",\n      aggregations: [\"avg\", \"last\", \"count\"],\n    },\n  ],\n}\n```\n\n### Maintenance\n\nScheduled maintenance is enabled by default.\n\n- It compacts raw rows into rollups when they age out of a raw tier.\n- It compacts finer rollups into coarser rollups when older tiers use larger buckets.\n- It deletes data that falls outside the last configured tier.\n- It is configured globally through `maintenance.enabled` and `maintenance.interval`.\n- Set `maintenance.enabled: false` if you want to disable the background maintenance loop.\n\nThe maintenance loop is internal to the component. You do not need to add your own cron job.\nMaintenance jobs from older versions drain after an upgrade. The component keeps\none active schedule and ignores older duplicate jobs.\n\n### Example queries\n\n```ts\n// convex/wearablesAdmin.ts\nimport { query } from \"./_generated/server\";\nimport { wearables } from \"./wearables\";\n\nexport const getStoragePolicies = query({\n  args: {},\n  handler: async (ctx) => {\n    const configuration = await wearables.getTimeSeriesPolicyConfiguration(ctx);\n    const assignedPreset = await wearables.getUserTimeSeriesPolicyPreset(ctx, {\n      userId: \"user-123\",\n    });\n    const effectiveHeartRatePolicy = await wearables.getEffectiveTimeSeriesPolicy(ctx, {\n      userId: \"user-123\",\n      provider: \"garmin\",\n      seriesType: \"heart_rate\",\n    });\n\n    return { configuration, assignedPreset, effectiveHeartRatePolicy };\n  },\n});\n```\n\n### Read behavior\n\n- Reads prefer the tier configured for the requested age range.\n- If maintenance has not caught up yet, reads fall back to any available raw or rollup data for that range instead of returning empty history.\n- `value` on a rollup-backed point is chosen from the configured aggregation preference, using this priority: `avg`, then `last`, then `max`, then `min`, then `count`.\n- Rollup-backed points also include `avg`, `min`, `max`, `last`, and `count` in the response.\n\n### Write and retention behavior\n\n- New points are written straight into the tier that matches their age at ingest time.\n- Historical backfills do not need to land in raw storage first.\n- Maintenance keeps already-stored data aligned with the current policy over time.\n- Disconnecting an integration stops future ingestion, but stored data still ages out according to the policy unless you explicitly purge it from your app.\n\n## Data Model\n\n### Tables\n\n| Table | Description | Key Indexes |\n|-------|-------------|-------------|\n| `connections` | OAuth tokens + provider link per user | `by_user`, `by_user_provider`, `by_status` |\n| `dataSources` | User + provider + device combinations | `by_user_provider`, `by_user_provider_device`, `by_connection` |\n| `dataPoints` | Time-series health metrics | `by_source_type_time`, `by_source_time`, `by_type_time` |\n| `timeSeriesRollups` | Bucketed historical time-series rollups | `by_source_type_bucket`, `by_source_type_bucket_size`, `by_source_bucket`, `by_type_bucket` |\n| `events` | Workouts and sleep sessions | `by_user_category_time`, `by_external_id`, `by_source_start_end` |\n| `workoutSegments` | Normalized laps, splits, lengths, and strength sets | `by_event_kind_index`, `by_user_provider` |\n| `workoutZones` | Normalized heart-rate and power time-in-zone | `by_event_kind_zone`, `by_user_provider` |\n| `garminActivityFileJobs` | Ephemeral Garmin FIT processing inbox | `by_connection_status`, `by_activity`, `by_event_external_id`, `by_expiry` |\n| `dailySummaries` | Provider-aware daily aggregates | `by_user_provider_category_date`, `by_user_provider_date`, `by_user_category_date`, `by_user_date` |\n| `syncJobs` | Sync workflow tracking | `by_user`, `by_user_provider`, `by_user_status`, `by_status` |\n| `oauthStates` | Temporary OAuth PKCE state | `by_state` |\n| `timeSeriesPolicyRules` | Persisted default rules and preset rules | `by_set`, `by_set_scope` |\n| `timeSeriesPolicyAssignments` | Per-user preset assignment | `by_user`, `by_preset` |\n| `timeSeriesPolicySettings` | Global maintenance settings | `by_key` |\n| `timeSeriesSeriesState` | Per-source series maintenance cursor | `by_source_series`, `by_next_maintenance`, `by_user` |\n| `backfillJobs` | Long-running historical data imports | `by_connection`, `by_status` |\n\n### Deduplication\n\nEvents are deduplicated at two levels:\n\n1. **By `externalId`** — provider-assigned IDs like `strava-12345` prevent duplicate imports\n2. **By `dataSourceId` + `startDatetime` + `endDatetime`** — catches duplicates even without external IDs\n\nThis is same-provider replay protection, not cross-provider reconciliation. A\nGarmin workout and a copy observed through Strava have different provider\nidentities and may both be stored. Host applications own canonical-source and\ncross-provider duplicate policy; see\n[Strava integration boundaries](https://github.com/clipinfit/convex-wearables/blob/main/docs/strava-integration-boundaries.md).\n\nData points are deduplicated by `dataSourceId` + `seriesType` + `recordedAt`.\n\nRollups are upserted by `dataSourceId` + `seriesType` + `bucketMs` + `bucketStart`.\n\n## OAuth Flow\n\nThe component handles the full OAuth 2.0 authorization code flow:\n\n```\n┌──────────┐    1. generateAuthUrl     ┌─────────────────┐\n│  Your App │ ───────────────────────▶  │  Component       │\n│           │ ◀──────────────────────── │  (stores state)  │\n│           │    ← authorization URL    │                  │\n└─────┬─────┘                           └─────────────────┘\n      │ 2. redirect user to provider\n      ▼\n┌──────────┐    3. user authorizes     ┌─────────────────┐\n│ Provider  │ ───────────────────────▶  │  Your App        │\n│ (Strava)  │    ← redirect with code  │  /callback       │\n└──────────┘                           └─────┬───────────┘\n                                             │ 4. handleCallback\n                                             ▼\n                                       ┌─────────────────┐\n                                       │  Component       │\n                                       │  - exchange code │\n                                       │  - store tokens  │\n                                       │  - create conn   │\n                                       └─────────────────┘\n```\n\n### Actions\n\n| Action | Description |\n|--------|-------------|\n| `generateAuthUrl` | Build OAuth URL, store state with PKCE |\n| `handleCallback` | Exchange code, fetch user info, create connection |\n| `ensureValidToken` | Internal token refresh helper used by sync actions |\n\n## Sync Workflow\n\nThe sync workflow runs as a Convex action:\n\n1. **Token validation** — refreshes expired tokens automatically, revokes connections whose refresh token is definitively rejected, and leaves transient provider failures retryable\n2. **Data fetch** — calls provider API with pagination (e.g., 200 activities per page from Strava)\n3. **Batch storage** — writes events in batches of 50 to stay within Convex's 1-second mutation timeout\n4. **Status tracking** — creates sync job records with status, timestamps, and error details\n\nPull providers can re-fetch a trailing window so late provider revisions are not\nmissed. Configure a global overlap or provider-specific values when constructing\nthe client:\n\n```ts\nconst wearables = new WearablesClient(components.wearables, {\n  providers: {\n    strava: { clientId: \"...\", clientSecret: \"...\" },\n    whoop: { clientId: \"...\", clientSecret: \"...\" },\n  },\n  pullSyncLookbackHours: {\n    strava: 6,\n    whoop: 24,\n  },\n});\n```\n\nLookback is disabled by default to preserve existing provider API usage. It is\nsubtracted from `lastSyncedAt` and capped by `syncWindowHours`. An explicit\n`startDate` always wins. Individual calls can override the configured value with\n`lookbackHours`.\n\n### Cron-based sync\n\nSet up a Convex cron to sync all active connections periodically:\n\n```ts\n// convex/crons.ts\nimport { cronJobs } from \"convex/server\";\nimport { components } from \"./_generated/api\";\n\nconst crons = cronJobs();\n\ncrons.interval(\n  \"sync all wearables\",\n  { minutes: 15 },\n  components.wearables.syncWorkflow.syncAllActive,\n  {\n    clientCredentials: {\n      strava: {\n        clientId: process.env.STRAVA_CLIENT_ID!,\n        clientSecret: process.env.STRAVA_CLIENT_SECRET!,\n      },\n    },\n    syncWindowHours: 24,\n  },\n);\n\nexport default crons;\n```\n\n## Webhook Support\n\n### Garmin Webhooks\n\nRegister Garmin routes directly from the package:\n\n```ts\n// convex/http.ts\nimport { httpRouter } from \"convex/server\";\nimport { registerRoutes } from \"@clipin/convex-wearables\";\nimport { components } from \"./_generated/api\";\n\nconst http = httpRouter();\n\nregisterRoutes(http, components.wearables, {\n  garmin: {\n    clientId: process.env.GARMIN_CLIENT_ID,\n    clientSecret: process.env.GARMIN_CLIENT_SECRET,\n    oauthCallbackPath: \"/oauth/garmin/callback\",\n    successRedirectUrl: process.env.NEXT_PUBLIC_APP_URL,\n    webhookPath: \"/webhooks/garmin/push\",\n    healthPath: \"/webhooks/garmin/health\",\n    activityFiles: {\n      enabled: true,\n      // Optional; defaults to 20 MiB and Garmin API hosts only.\n      maxBytes: 20 * 1024 * 1024,\n    },\n  },\n});\n\nexport default http;\n```\n\nThe Garmin route helper:\n\n- handles the Garmin OAuth callback redirect\n- validates the `garmin-client-id` header\n- logs payload summaries and processing errors\n- forwards the payload to `components.wearables.garminWebhooks.processPushPayload`\n- exposes an optional health-check route\n- optionally queues Garmin FIT Activity Files for asynchronous workout enrichment\n\nActivity File processing is disabled by default. When enabled, callback URLs\nmust use HTTPS and an allowed Garmin host, redirects are rejected, downloads\nare size- and time-bounded, and callback URLs are scrubbed after use or expiry.\nRaw FIT bytes are parsed in memory and are never retained. Summary workouts\nremain available even if enrichment fails.\n\nGarmin `activityDetails` samples are always normalized into the existing\ntime-series store. FIT samples for a series already present in\n`activityDetails` are skipped to avoid same-workout duplication. Read deep\ndetail with `wearables.getWorkoutEnrichment(ctx, { eventId })`; read samples\nwith the normal `getTimeSeries` API and configure their lifecycle through the\nexisting time-series storage policies.\n\nIf you customize `oauthCallbackPath`, the redirect URI used when calling\n`oauthActions.generateAuthUrl` must match that same callback path.\n\n### WHOOP v2, Polar, and Suunto live webhooks\n\nVersion `0.11.0` adds opt-in, receipt-first inbound callbacks for WHOOP v2,\nPolar AccessLink, and Suunto. They complement scheduled pull sync; they do not\nreplace reconciliation. Mount only the providers you intend to enable:\n\n```ts\n// convex/http.ts\nimport { httpRouter } from \"convex/server\";\nimport { registerRoutes } from \"@clipin/convex-wearables\";\nimport { components } from \"./_generated/api\";\n\nconst http = httpRouter();\n\nregisterRoutes(http, components.wearables, {\n  garmin: false, // omit this only if Garmin routes are not needed\n  providerWebhooks: {\n    whoop: { path: \"/wearables/webhooks/whoop/v2\" },\n    polar: { path: \"/wearables/webhooks/polar\" },\n    suunto: { path: \"/wearables/webhooks/suunto\" },\n  },\n});\n\nexport default http;\n```\n\nNo live-provider route is mounted unless `providerWebhooks` and that provider\nkey are both present. The request path reads at most 512,000 bytes by default,\nverifies the signature over the exact body, validates the notification, and\ntransactionally stores/deduplicates a receipt while starting a dedicated\nWorkflow. Targeted provider fetches, inline sample normalization, deletes, and\nretries run after acknowledgement with an isolated five-action concurrency\nbudget and four-attempt exponential retry policy.\n\nConfigure WHOOP's callback URL in its developer dashboard. WHOOP uses the\nprovider `clientSecret` already configured for OAuth and accepts only v2 UUID\nresource notifications. Record safe local status explicitly:\n\n```ts\nawait wearables.configureProviderWebhook(ctx, {\n  provider: \"whoop\",\n  targetUrl: `${convexSiteUrl}/wearables/webhooks/whoop/v2`,\n  eventTypes: [\n    \"workout.updated\",\n    \"workout.deleted\",\n    \"sleep.updated\",\n    \"sleep.deleted\",\n    \"recovery.updated\",\n    \"recovery.deleted\",\n  ],\n});\n```\n\nConfigure Suunto's callback and notification secret in API Zone, then store the\nsame secret through an operator-authorized wrapper:\n\n```ts\nawait wearables.configureProviderWebhook(ctx, {\n  provider: \"suunto\",\n  targetUrl: `${convexSiteUrl}/wearables/webhooks/suunto`,\n  webhookSecret: process.env.SUUNTO_WEBHOOK_SECRET,\n  eventTypes: [\n    \"WORKOUT_CREATED\",\n    \"SUUNTO_247_SLEEP_CREATED\",\n    \"SUUNTO_247_ACTIVITY_CREATED\",\n    \"SUUNTO_247_RECOVERY_CREATED\",\n  ],\n});\n```\n\nPolar registration is component-managed because Polar permits one callback per\nAPI client and returns its signing secret only once:\n\n```ts\nawait wearables.createPolarWebhook(ctx, {\n  targetUrl: `${convexSiteUrl}/wearables/webhooks/polar`,\n  eventTypes: [\"EXERCISE\"],\n});\n```\n\nThis release intentionally subscribes only to Polar `EXERCISE`, the event with\na complete targeted normalization path. Use `updatePolarWebhook`,\n`activatePolarWebhook`, `deactivatePolarWebhook`, `deletePolarWebhook`, and\n`reconcilePolarWebhookRegistration` from operator-authorized actions. If\nreconciliation reports `signing_secret_missing_recreate_required`, delete the\nremote registration and recreate it; Polar cannot reveal the old secret.\n\nReceipt and registration APIs are deliberately authorization-agnostic. Wrap\nthem with your host's operator/tenant authorization before exposing them:\n\n```ts\nawait wearables.getProviderWebhookStatus(ctx, { provider: \"whoop\" });\nawait wearables.listProviderWebhookReceipts(ctx, {\n  provider: \"whoop\",\n  status: \"failed\",\n  limit: 20,\n});\nawait wearables.retryProviderWebhookReceipt(ctx, { receiptId });\nawait wearables.cancelProviderWebhookReceipt(ctx, { receiptId });\n```\n\nPublic status never returns raw payloads or signing secrets. Successful and\nignored payloads are redacted immediately; failed payloads are retained only\nwithin the seven-day receipt window. Unknown connections get a bounded\n15-minute OAuth race window. Provider/user deletion cancels and removes\nresolved receipts, and the normal ingestion fence prevents late writes.\n\nKeep scheduled pulls enabled for WHOOP, Polar, and Suunto. Webhooks are\nat-least-once signals: duplicates are harmless, but providers can still omit or\nreorder them. Incoming provider webhooks are unrelated to the planned outgoing\nconsumer webhook feature.\n\n### Durable outgoing events and self-service webhooks\n\nVersion `0.12.0` adds an opt-in transactional event stream for host callbacks\nand signed external subscriptions. This is distinct from provider webhooks:\nprovider callbacks ingest wearable data, while outgoing webhooks notify systems\nafter normalized component data commits.\n\nNothing is captured or delivered until a host enables it. First persist the\nhost-authorized tenant membership used by transactional writes:\n\n```ts\nawait wearables.setWebhookUserTenant(ctx, {\n  userId: authenticatedUserId,\n  tenantId: authorizedTenantId,\n});\n\nawait wearables.configureOutgoingWebhooks(ctx, {\n  captureEnabled: true,\n  externalDeliveryEnabled: false,\n});\n```\n\nFor a typed internal callback, create a Convex function handle for a mutation\nor action accepting `WearablesEventEnvelope`, then pass that handle to\n`configureOutgoingWebhooks` with its function kind:\n\n```ts\nawait wearables.configureOutgoingWebhooks(ctx, {\n  captureEnabled: true,\n  internalCallbackHandle: callbackHandle,\n  internalCallbackKind: \"mutation\", // or \"action\"\n});\n```\n\nCallback errors are isolated from ingestion and retried four times with\nexponential backoff in the outgoing-webhook Workflow pool. External endpoint\nfan-out is committed before the callback runs, so exhausting callback retries\ncannot suppress external delivery. `onDataSynced` remains supported.\n\nExternal delivery additionally requires a deployment secret:\n\n```bash\nopenssl rand -base64 32\nnpx convex env set CONVEX_WEARABLES_WEBHOOK_ENCRYPTION_KEY '<base64-key>'\n```\n\nEnable delivery only after that key is present. Endpoint and configuration\nmethods do not authenticate callers; expose them through host functions that\nverify tenant administration or exact user ownership:\n\n```ts\nawait wearables.configureOutgoingWebhooks(ctx, {\n  captureEnabled: true,\n  externalDeliveryEnabled: true,\n});\n\nconst created = await wearables.createWebhookEndpoint(ctx, {\n  tenantId: authorizedTenantId,\n  scope: \"user\",\n  userId: authenticatedUserId,\n  url: \"https://receiver.example/webhooks/wearables\",\n  eventTypes: [\"workout.*\", \"sleep.*\"],\n  payloadMode: \"reference\",\n});\n// Display created.signingSecret once; ordinary queries never return it.\n\nawait wearables.verifyWebhookEndpoint(ctx, {\n  tenantId: authorizedTenantId,\n  endpointId: created.endpointId,\n});\n```\n\nGroup selectors expand to the exact catalog at save time, so future sensitive\nevents do not silently enter existing subscriptions. Reference payloads are\nthe default. Snapshot mode requires both global and per-endpoint opt-in and\nstill excludes routes/GPS, menstrual data, raw sleep stages, credentials,\nprovider payloads, and files. Fan-out stores the endpoint-specific canonical\nbody, so retries keep identical bytes after later endpoint configuration\nchanges. Snapshot bodies are not retained before the global opt-in is enabled.\n\nEvery delivery uses the immutable canonical event body and these headers:\n\n```text\nwearables-id: <stable event id>\nwearables-timestamp: <unix seconds>\nwearables-signature: v1,<base64 hmac-sha256>\nwearables-attempt: <1-based attempt>\nwearables-event-type: <event type>\n```\n\nVerify `HMAC-SHA256(secret, \"<id>.<timestamp>.<rawBody>\")` against the exact\nbytes using constant-time comparison and reject timestamps outside five\nminutes. Delivery is at least once and unordered; receivers must persist the\nevent ID before non-idempotent work.\n\nExternal POSTs are HTTPS-only, redirect-free, limited to port 443, and use a\npublic DNS result pinned into the TLS connection. Local, private, link-local,\nreserved, multicast, credentialed, and fragment URLs fail closed. Requests\ntime out after 15 seconds. Failures follow the eight-attempt schedule of\nimmediate, 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and\n10 hours. HTTP 410 and `webhook-delivery: abort-message` are terminal.\n\nEvery delivery claim receives a unique two-minute lease token and schedules a\ndurable watchdog in the same mutation. The network action renews that lease\nwhen it actually starts, preventing Workpool queue time from consuming the\nrequest window. Normal completion makes both watchdogs no-ops. If an action is\ninterrupted before recording its result, the watchdog\nrecords a bounded `worker_interrupted` attempt and durably schedules the next\nWorkflow according to the normal retry policy. A late result from the abandoned\nworker cannot update the reissued delivery because its lease token no longer\nmatches. The receiver may still observe the stable event more than once, so\nreceiver-side event-ID deduplication remains required.\n\nUse the endpoint/event/delivery/attempt list methods for safe status, and\n`retryWebhookDelivery`, `recoverFailedWebhookDeliveries`, or\n`replayMissingWebhookEvents` for explicit recovery. Bulk recovery and replay\nreturn a durable operation ID whose progress is available through\n`getWebhookRecoveryOperation`. Payloads are retained up to 30 days, successful\nattempts seven days, and failed attempts 30 days. Provider/user deletion\ncancels and removes matching queued health payloads; tenant-wide endpoint\nconfiguration survives deletion of one user.\n\nTo rotate the deployment master key, temporarily set the old key as\n`CONVEX_WEARABLES_WEBHOOK_PREVIOUS_ENCRYPTION_KEY`, install the new current\nkey, call `rewrapWebhookEndpointSecret` for every endpoint, verify delivery,\nthen remove the previous key. Losing all configured decryption keys requires\nreceiver-secret rotation.\n\n### Strava Webhooks\n\nThe component provides HTTP handlers for Strava's [webhook events API](https://developers.strava.com/docs/webhooks/):\n\n| Endpoint | Handler | Purpose |\n|----------|---------|---------|\n| `GET /webhooks/strava` | `stravaWebhookVerify` | Subscription verification (hub.challenge) |\n| `POST /webhooks/strava` | `stravaWebhookEvent` | Receive activity create/update/delete events |\n\nMount these in your Convex HTTP router:\n\n```ts\n// convex/http.ts\nimport { httpRouter } from \"convex/server\";\nimport { stravaWebhookVerify, stravaWebhookEvent } from \"@clipin/convex-wearables\";\n\nconst http = httpRouter();\n\nhttp.route({\n  path: \"/webhooks/strava\",\n  method: \"GET\",\n  handler: stravaWebhookVerify,\n});\n\nhttp.route({\n  path: \"/webhooks/strava\",\n  method: \"POST\",\n  handler: stravaWebhookEvent,\n});\n\nexport default http;\n```\n\nSet `STRAVA_WEBHOOK_VERIFY_TOKEN` in the host Convex deployment to the same\nsecret used when creating the Strava webhook subscription. Verification fails\nclosed with HTTP 403 when the secret is missing or does not match; the token is\nnever logged.\n\n### SDK Push (Apple Health / Google Health Connect)\n\nFor on-device providers, register the normalized SDK sync route explicitly:\n\n```ts\n// convex/http.ts\nimport { httpRouter } from \"convex/server\";\nimport { getSdkSyncUrl, registerRoutes } from \"@clipin/convex-wearables\";\nimport { components } from \"./_generated/api\";\n\nconst http = httpRouter();\n\nconst routeConfig = {\n  sdk: {\n    syncPath: \"/sdk/sync\",\n    authToken: process.env.WEARABLES_SDK_AUTH_TOKEN,\n  },\n};\n\nregisterRoutes(http, components.wearables, routeConfig);\n\nconst sdkSyncUrl = getSdkSyncUrl(process.env.CONVEX_SITE_URL!, routeConfig);\n\nexport default http;\n```\n\nThe same configuration also registers `/sdk/sync/v2`, a resilient opt-in route\nthat accepts a versioned envelope:\n\n```ts\nconst result = await fetch(`${process.env.CONVEX_SITE_URL}/sdk/sync/v2`, {\n  method: \"POST\",\n  headers: {\n    \"content-type\": \"application/json\",\n    authorization: `Bearer ${sdkToken}`,\n  },\n  body: JSON.stringify({\n    userId: \"user_123\",\n    provider: \"google\",\n    requestId: \"health-connect-sync-2026-08-01T08:00:00Z\",\n    mode: \"partial\",\n    payload: {\n      dataPoints: normalizedPoints,\n      events: normalizedEvents,\n      summaries: normalizedSummaries,\n    },\n  }),\n});\n\nconst report = await result.json();\n```\n\nV2 stores valid rows when isolated rows are malformed and reports bounded,\nprivacy-safe rejection codes. Set `mode: \"strict\"` to preserve request-level\nall-or-nothing validation. The original `/sdk/sync` route is unchanged. Set\n`syncV2Path: false` to disable v2 or provide a custom path.\n\nThen POST a pre-normalized payload from your mobile app:\n\n```json\n{\n  \"userId\": \"user_123\",\n  \"provider\": \"google\",\n  \"sourceMetadata\": {\n    \"deviceModel\": \"Pixel Watch 3\",\n    \"source\": \"health-connect\"\n  },\n  \"events\": [],\n  \"dataPoints\": [\n    {\n      \"seriesType\": \"heart_rate\",\n      \"recordedAt\": 1773817200000,\n      \"value\": 58\n    }\n  ],\n  \"summaries\": []\n}\n```\n\nThe backend stores the payload using the same `connections`, `dataSources`, `events`, `dataPoints`, and `dailySummaries` tables as the cloud providers.\nDaily summaries are keyed by user, provider, category, and date, so Apple Health,\nGoogle Health Connect, Garmin, and other providers can coexist without\noverwriting each other's aggregate rows.\n\nThe SDK payload also accepts `device` and `dailySummaries` as compatibility aliases, and normalizes common Health Connect metric names like `hrv_rmssd`.\n\n## Supported Providers\n\n| Provider | Integration mode | Current support | Status |\n|----------|------------------|-----------------|--------|\n| Strava | OAuth pull sync + webhook-triggered resync | Workouts, connection lifecycle, sync jobs | Implemented |\n| Garmin | OAuth connection + push webhooks + durable backfill | Workouts, sleep, time-series, summaries | Implemented |\n| Apple Health | Normalized SDK push | Workouts, sleep, time-series, summaries from your mobile app | Implemented via SDK |\n| Samsung Health | Normalized SDK push | Workouts, sleep, time-series, summaries from your mobile app | Implemented via SDK |\n| Google Health Connect | Normalized SDK push | Workouts, sleep, time-series, summaries from your mobile app | Implemented via SDK |\n| Whoop | OAuth pull + signed v2 targeted-fetch webhooks | Workouts, sleep, recovery, body data | Implemented |\n| Polar | OAuth pull + signed targeted-fetch webhooks | Workouts; live `EXERCISE` | Implemented |\n| Suunto | OAuth pull + signed inline/targeted webhooks | Workouts, sleep, recovery, activity data | Implemented |\n| Synthetic | Deterministic local generation | Workouts, sleep, time-series, summaries through `SynthDevice` | Implemented; explicit opt-in |\n\nSDK-push providers rely on your app to send normalized payloads. The component stores and queries that data, but it does not yet fetch Apple Health, Samsung Health, or Google Health Connect data directly from vendor APIs.\n\n### Adding a Provider\n\nImplement the `ProviderDefinition` interface and register it in the provider registry:\n\n```ts\n// src/component/providers/garmin.ts\nimport type { ProviderDefinition } from \"./registry\";\n\nexport const garminProvider: ProviderDefinition = {\n  oauthConfig(clientId, clientSecret) {\n    return {\n      endpoints: {\n        authorizeUrl: \"https://connect.garmin.com/oauthConfirm\",\n        tokenUrl: \"https://connectapi.garmin.com/oauth-service/oauth/token\",\n        apiBaseUrl: \"https://apis.garmin.com\",\n      },\n      clientId,\n      clientSecret,\n      defaultScope: \"\",\n      usePkce: false,\n      authMethod: \"body\",\n    };\n  },\n  async fetchWorkouts(accessToken, startDate, endDate) {\n    // Fetch and normalize activities...\n    return [];\n  },\n  async getUserInfo(accessToken) {\n    // Fetch user profile...\n    return { providerUserId: null, username: null };\n  },\n};\n```\n\n### Workout Type Taxonomy\n\nThe component normalizes provider-specific activity types to a unified taxonomy:\n\n| Unified Type | Strava Types |\n|---|---|\n| `running` | Run, VirtualRun |\n| `trail_running` | TrailRun |\n| `cycling` | Ride, GravelRide |\n| `mountain_biking` | MountainBikeRide |\n| `indoor_cycling` | VirtualRide |\n| `swimming` | Swim |\n| `hiking` | Hike |\n| `walking` | Walk |\n| `strength_training` | WeightTraining |\n| `yoga` | Yoga |\n| `alpine_skiing` | AlpineSki |\n| `rowing` | Rowing |\n| `kayaking` | Kayaking |\n| `surfing` | Surfing |\n| `rock_climbing` | RockClimbing |\n| `golf` | Golf |\n| `pickleball` | Pickleball |\n| `tennis` | Tennis |\n| `soccer` | Soccer |\n| ... | (40+ types total) |\n\n## Series Types\n\nAll 88 pre-defined metric types are available via the `SERIES_TYPES` constant:\n\nUse these keys as `seriesType` values in time-series policy rules.\n\n```ts\nimport { SERIES_TYPES } from \"@clipin/convex-wearables/types\";\n\nconsole.log(SERIES_TYPES.heart_rate);\n// { id: 1, unit: \"bpm\" }\n```\n\n<details>\n<summary>Full list of series types</summary>\n\n**Heart & Cardiovascular**: `heart_rate`, `resting_heart_rate`, `heart_rate_variability_sdnn`, `heart_rate_variability_rmssd`, `heart_rate_recovery_one_minute`, `walking_heart_rate_average`, `recovery_score`\n\n**Blood & Respiratory**: `oxygen_saturation`, `blood_glucose`, `blood_pressure_systolic`, `blood_pressure_diastolic`, `respiratory_rate`, `sleeping_breathing_disturbances`, `blood_alcohol_content`, `peripheral_perfusion_index`, `forced_vital_capacity`, `forced_expiratory_volume_1`, `peak_expiratory_flow_rate`\n\n**Body Composition**: `height`, `weight`, `body_fat_percentage`, `body_mass_index`, `lean_body_mass`, `body_temperature`, `skin_temperature`, `waist_circumference`, `body_fat_mass`, `skeletal_muscle_mass`\n\n**Fitness**: `vo2_max`, `six_minute_walk_test_distance`\n\n**Activity — Basic**: `steps`, `energy`, `basal_energy`, `stand_time`, `exercise_time`, `physical_effort`, `flights_climbed`, `average_met`\n\n**Activity — Distance**: `distance_walking_running`, `distance_cycling`, `distance_swimming`, `distance_downhill_snow_sports`, `distance_other`\n\n**Activity — Walking/Running/Swimming**: `walking_step_length`, `walking_speed`, `running_power`, `running_speed`, `running_stride_length`, `swimming_stroke_count`, `underwater_depth`, and more\n\n**Environmental**: `environmental_audio_exposure`, `headphone_audio_exposure`, `time_in_daylight`, `water_temperature`, `uv_exposure`, `weather_temperature`, `weather_humidity`\n\n**Garmin-specific**: `garmin_stress_level`, `garmin_skin_temperature`, `garmin_fitness_age`, `garmin_body_battery`\n\n</details>\n\n## Testing\n\nThe package test suite covers component internals, provider adapters, webhook\ningestion, strict and resilient SDK push ingestion, workflow orchestration, and\nclient helpers.\n\n```bash\n# Run all tests\nnpm test\n\n# Run tests in watch mode\nnpm run test:watch\n\n# Run a specific test file\nnpx vitest run src/component/events.test.ts\n```\n\nCoverage includes:\n\n- `convex-test` suites for schema/index behavior, deduplication, data isolation, and sync job lifecycle\n- webhook and ingestion flows for Garmin push payloads and normalized mobile SDK payloads\n- provider adapter normalization for Strava plus additional provider config coverage\n- workflow orchestration and client helpers such as route registration and SDK sync URL generation\n\n## Platform Considerations\n\nThis component is designed around Convex's platform constraints:\n\n| Constraint | Limit | How we handle it |\n|---|---|---|\n| Mutation timeout | 1 second | Batch writes (50 events per mutation) |\n| Document scan limit | 32K per query | Cursor-based pagination, precomputed daily summaries |\n| Action timeout | 10 minutes | Paginated provider API calls, sync-per-connection |\n| Document size | 1 MiB | Flat event schema, sleep stages as embedded array |\n\nFor high-volume time-series data (e.g., per-second heart rate), consider using [`@convex-dev/aggregate`](https://github.com/get-convex/aggregate) for O(log n) sum/count/avg queries alongside this component.\n\n## Project Structure\n\n```\nconvex-wearables/\n├── src/\n│   ├── client/\n│   │   ├── index.ts          # WearablesClient and HTTP route helper exports\n│   │   └── types.ts          # Shared types and SERIES_TYPES\n│   └── component/\n│       ├── schema.ts         # Convex schema\n│       ├── connections.ts    # Connection lifecycle queries and mutations\n│       ├── events.ts         # Workout and sleep storage/query APIs\n│       ├── dataPoints.ts     # Time-series storage/query APIs\n│       ├── dataSources.ts    # Provider/device source tracking\n│       ├── summaries.ts      # Daily aggregates\n│       ├── syncJobs.ts       # Sync job tracking\n│       ├── syncWorkflow.ts   # Durable per-connection sync orchestration\n│       ├── garminWebhooks.ts # Garmin push ingestion\n│       ├── sdkPush.ts        # Normalized mobile SDK ingestion\n│       ├── garminBackfill.ts # Garmin historical backfill workflow\n│       ├── httpHandlers.ts   # Standalone HTTP action handlers\n│       ├── oauthActions.ts   # OAuth URL generation and callback handling\n│       ├── providerSettings.ts # Stored provider credentials\n│       ├── lifecycle.ts      # GDPR user data deletion\n│       ├── convex.config.ts  # Component config\n│       ├── providers/\n│       │   ├── types.ts      # Provider interfaces\n│       │   ├── oauth.ts      # Shared OAuth utilities\n│       │   ├── garmin.ts     # Garmin adapter and normalization\n│       │   ├── strava.ts     # Strava adapter and normalization\n│       │   └── registry.ts   # Provider registry\n│       └── *.test.ts         # Component and adapter tests\n├── package.json\n├── tsconfig.json\n└── README.md\n```\n\n## License\n\nApache-2.0\n","readmeFilename":"README.md"}