{"_id":"@alosha/stride","_rev":"11-5104e1ac1484ddf391be9df1407dc6f6","name":"@alosha/stride","dist-tags":{"latest":"2.0.1"},"versions":{"0.1.0":{"name":"@alosha/stride","version":"0.1.0","keywords":["running","gpx","analytics","chartjs","fitness","strava","garmin","pace","training"],"author":{"name":"Eduardo Silvanavarrete","email":"avlis_odraude@hotmail.com"},"license":"MIT","_id":"@alosha/stride@0.1.0","maintainers":[{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"}],"homepage":"https://stride.alosha.dev","bugs":{"url":"https://github.com/avlisodraude/stride/issues"},"bin":{"stride":"dist/cli.js"},"dist":{"shasum":"fa17858dc7e96d28935906aa36dc6cc6c96b9f91","tarball":"https://registry.npmjs.org/@alosha/stride/-/stride-0.1.0.tgz","fileCount":10,"integrity":"sha512-PkHYpqB5ThiTfVM4b6/INXv2pIg25YASPYUio99rlP1XUq4KDnWO18qZxN0UontlNOF67PTCCEyT/ogD+e0yVA==","signatures":[{"sig":"MEYCIQC92nQLOG6Ll1J3j2GAcEt3DiGAHhiGNE7btRACElmiIQIhAKuwTYQVmhghLBMVVO/hVBMIbTeeWIRZFu/zWRLVSHiJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":122931},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"d3836c22889a7ffb675eaae76aff07660243aaef","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup","prepare":"husky","release":"npm run lint && npm run build && npm publish --access public"},"_npmUser":{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"},"repository":{"url":"git+https://github.com/avlisodraude/stride.git","type":"git"},"_npmVersion":"11.16.0","description":"Parse GPX files, compute running metrics, and render Chart.js dashboards — zero config.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"chart.js":"^4.4.3","fast-xml-parser":"^4.4.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.1.0","husky":"^9.0.11","eslint":"^9.5.0","typescript":"^5.4.5","@types/node":"^20.14.0","@commitlint/cli":"^19.3.0","@commitlint/config-conventional":"^19.2.2"},"_npmOperationalInternal":{"tmp":"tmp/stride_0.1.0_1781440094517_0.4049723573016262","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@alosha/stride","version":"0.1.1","keywords":["running","gpx","analytics","chartjs","fitness","strava","garmin","pace","training"],"author":{"name":"Eduardo Silvanavarrete","email":"avlis_odraude@hotmail.com"},"license":"MIT","_id":"@alosha/stride@0.1.1","maintainers":[{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"}],"homepage":"https://stride.alosha.dev","bugs":{"url":"https://github.com/avlisodraude/stride/issues"},"bin":{"stride":"dist/cli.js"},"dist":{"shasum":"293371a57dc9dceb98b434cada4ad3fcceb64f1d","tarball":"https://registry.npmjs.org/@alosha/stride/-/stride-0.1.1.tgz","fileCount":10,"integrity":"sha512-3LE1vqlT8aQoihIRcaTBuuZaycjLm2RiwE9ApYr8E9JzTYNSAGT3cD55XtJ+HzC7DCW/16o44KIjOP0VKOEr4Q==","signatures":[{"sig":"MEUCIQDYu2Gc9FcfEjBJEZVv3C31GVPoXJzYUjWAqlxPPUl74AIgYm8sRcD/MOUOsF3BYjVIqRRWtna7oJuLK8eCjeRNHMg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":122969},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"2661f388fa8980a2f41fcf78916c5294868acd7d","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup","prepare":"husky","release":"npm run lint && npm run build && npm publish --access public"},"_npmUser":{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"},"repository":{"url":"git+https://github.com/avlisodraude/stride.git","type":"git"},"_npmVersion":"11.16.0","description":"Parse GPX files, compute running metrics, and render Chart.js dashboards — zero config.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"chart.js":"^4.4.3","fast-xml-parser":"^4.4.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.1.0","husky":"^9.0.11","eslint":"^9.5.0","typescript":"^5.4.5","@types/node":"^20.14.0","@commitlint/cli":"^19.3.0","@commitlint/config-conventional":"^19.2.2"},"_npmOperationalInternal":{"tmp":"tmp/stride_0.1.1_1781440348471_0.47710806001287387","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@alosha/stride","version":"0.1.2","keywords":["gpx","gpx-parser","running","strava","garmin","activity","heart-rate","heart-rate-zones","pace","splits","chartjs","chart","activity-analysis","fitness","cli"],"author":{"name":"Eduardo Silvanavarrete","email":"avlis_odraude@hotmail.com"},"license":"MIT","_id":"@alosha/stride@0.1.2","maintainers":[{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"}],"homepage":"https://stride.alosha.dev","bugs":{"url":"https://github.com/avlisodraude/stride/issues"},"bin":{"stride":"dist/cli.js"},"dist":{"shasum":"708cddbb357102aae20c1811234cf03d42f01c86","tarball":"https://registry.npmjs.org/@alosha/stride/-/stride-0.1.2.tgz","fileCount":10,"integrity":"sha512-g7fxAuzoed0zvWHITEhWh+FLz+MZ6TEqMuUF+ASrQymnX6L5vok1vlu+THDclRrBIB2y7NL2J0y0dQvPSx8Q7Q==","signatures":[{"sig":"MEYCIQDEm+DSfa49usEgetYjRpTVPDj+jIWQChNR3IGQLBP0WwIhAIRt30liPgbrEFuyfNEyclZdbTrMDW0A4aZ2hyKxlIja","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":123095},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"2661f388fa8980a2f41fcf78916c5294868acd7d","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup","prepare":"husky","release":"npm run lint && npm run build && npm publish --access public"},"_npmUser":{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"},"repository":{"url":"git+https://github.com/avlisodraude/stride.git","type":"git"},"_npmVersion":"11.16.0","description":"Parse GPX files and compute running metrics — pace, HR zones, splits, and Chart.js dashboards. Zero config.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"chart.js":"^4.4.3","fast-xml-parser":"^4.4.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.1.0","husky":"^9.0.11","eslint":"^9.5.0","typescript":"^5.4.5","@types/node":"^20.14.0","@commitlint/cli":"^19.3.0","@commitlint/config-conventional":"^19.2.2"},"_npmOperationalInternal":{"tmp":"tmp/stride_0.1.2_1781442660866_0.7803844658568557","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@alosha/stride","version":"0.1.3","keywords":["gpx","gpx-parser","running","strava","garmin","activity","heart-rate","heart-rate-zones","pace","splits","chartjs","chart","activity-analysis","fitness","cli"],"author":{"name":"Eduardo Silvanavarrete","email":"avlis_odraude@hotmail.com"},"license":"MIT","_id":"@alosha/stride@0.1.3","maintainers":[{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"}],"homepage":"https://stride.alosha.dev","bugs":{"url":"https://github.com/avlisodraude/stride/issues"},"bin":{"stride":"dist/cli.js"},"dist":{"shasum":"01b6bdec638dfdd69777c1120c7d2307bec1abb1","tarball":"https://registry.npmjs.org/@alosha/stride/-/stride-0.1.3.tgz","fileCount":10,"integrity":"sha512-bdTpfoSwKo9gnQsIxMEu/sYYM8FUfHq2QhYMNvRZ/AmRXTZ/ZnehmTQBeg3H0EqPitUVCnU8vpaEe7eNvceI2A==","signatures":[{"sig":"MEYCIQDmELlF+/gh6qavAyWNYJXEQbLzwpMiZYKq7ZU+g8/RrgIhANJUtEs5HvXM9/ehnyp8x1dCOAG4/EfRNduSNQK+wIct","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":123543},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"5579d2c539c1e3e81e6dd428170d17df4bbe9a2c","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup","prepare":"husky","release":"npm run lint && npm run build && npm publish --access public"},"_npmUser":{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"},"repository":{"url":"git+https://github.com/avlisodraude/stride.git","type":"git"},"_npmVersion":"11.16.0","description":"Parse GPX files and compute running metrics — pace, HR zones, splits, and Chart.js dashboards. Zero config.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"chart.js":"^4.4.3","fast-xml-parser":"^4.4.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.1.0","husky":"^9.0.11","eslint":"^9.5.0","typescript":"^5.4.5","@types/node":"^20.14.0","@commitlint/cli":"^19.3.0","@commitlint/config-conventional":"^19.2.2"},"_npmOperationalInternal":{"tmp":"tmp/stride_0.1.3_1781443926676_0.9854231707863219","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@alosha/stride","version":"0.1.4","keywords":["gpx","gpx-parser","running","strava","garmin","activity","heart-rate","heart-rate-zones","pace","splits","chartjs","chart","activity-analysis","fitness","cli"],"author":{"name":"Eduardo Silvanavarrete","email":"avlis_odraude@hotmail.com"},"license":"MIT","_id":"@alosha/stride@0.1.4","maintainers":[{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"}],"homepage":"https://stride.alosha.dev","bugs":{"url":"https://github.com/avlisodraude/stride/issues"},"bin":{"stride":"dist/cli.js"},"dist":{"shasum":"103ff7eb5a86168a915245d28ba75ec8738185aa","tarball":"https://registry.npmjs.org/@alosha/stride/-/stride-0.1.4.tgz","fileCount":10,"integrity":"sha512-ksTacyxTl6qsVQtEhy8GxS4TrDzwF8ex7ZRmNHn4+BkpkNT8xv0lKSaLMWnlji+JHC10Vt4aol/nbySZJEAgjw==","signatures":[{"sig":"MEQCIAqNNMeqKgzfJ8KS0pqzOdxqkYPmJprIa9WPINLxok2gAiB4Qx3DjWdn27HkV9/xiNwnekdQ/HhtnmxYQOSFm46N3w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":123986},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"a224258099a6c4c637c28a03628b4541271652b7","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup","prepare":"husky","release":"npm run build && npm publish --access public"},"_npmUser":{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"},"repository":{"url":"git+https://github.com/avlisodraude/stride.git","type":"git"},"_npmVersion":"11.16.0","description":"Parse GPX files and compute running metrics — pace, HR zones, splits, and Chart.js dashboards. Zero config.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"chart.js":"^4.4.3","fast-xml-parser":"^4.4.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.1.0","husky":"^9.0.11","eslint":"^9.5.0","typescript":"^5.4.5","@types/node":"^20.14.0","@commitlint/cli":"^19.3.0","@commitlint/config-conventional":"^19.2.2"},"_npmOperationalInternal":{"tmp":"tmp/stride_0.1.4_1781449206470_0.8062728703604689","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@alosha/stride","version":"0.2.0","keywords":["gpx","gpx-parser","fit","fit-parser","fit-file","tcx","tcx-parser","running","strava","garmin","activity","heart-rate","heart-rate-zones","pace","splits","chartjs","chart","activity-analysis","fitness","cli"],"author":{"name":"Eduardo Silvanavarrete","email":"avlis_odraude@hotmail.com"},"license":"MIT","_id":"@alosha/stride@0.2.0","maintainers":[{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"}],"homepage":"https://stride.alosha.dev","bugs":{"url":"https://github.com/avlisodraude/stride/issues"},"bin":{"stride":"dist/cli.js"},"dist":{"shasum":"f2b2719a267bd5e488a8a6c9336f5809a9188742","tarball":"https://registry.npmjs.org/@alosha/stride/-/stride-0.2.0.tgz","fileCount":11,"integrity":"sha512-oXWuPg/GBsHFJYKFOsh5ipT3PxPTWtSsN4LyzdwCiOOUmhpA/PUlJsLk86R2sVAzrlEyRABHAc5qx06KBJnxjg==","signatures":[{"sig":"MEUCIFNdNTRqsw98EhG51B82VC3DTrrEZXKnwumubEufceBwAiEAvJ9+nDJ33JcRVJkE9G+sf6tzV2tsZy6fFDsbA1FOTl8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":167510},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"af61fd3aca26c6b50d2eb2487d1635f508a0c491","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup","prepare":"husky","pretest":"npm run build","release":"npm run build && npm publish --access public","sample:fit":"node scripts/make-sample-fit.mjs test/fixtures/sample-run.fit","build:browser":"node scripts/build-browser.mjs"},"_npmUser":{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"},"repository":{"url":"git+https://github.com/avlisodraude/stride.git","type":"git"},"_npmVersion":"11.16.0","description":"Parse GPX, TCX and FIT files and compute running metrics — pace, HR zones, splits, and Chart.js dashboards. Zero config.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"chart.js":"^4.4.3","@garmin/fitsdk":"^21.205.0","fast-xml-parser":"^4.4.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.1.0","husky":"^9.0.11","eslint":"^9.5.0","typescript":"^5.4.5","@types/node":"^20.14.0","@commitlint/cli":"^19.3.0","@commitlint/config-conventional":"^19.2.2"},"_npmOperationalInternal":{"tmp":"tmp/stride_0.2.0_1781541629411_0.4997748433398237","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@alosha/stride","version":"0.2.1","keywords":["gpx","gpx-parser","fit","fit-parser","fit-file","tcx","tcx-parser","running","strava","garmin","activity","heart-rate","heart-rate-zones","pace","splits","chartjs","chart","activity-analysis","fitness","cli"],"author":{"name":"Eduardo Silvanavarrete","email":"avlis_odraude@hotmail.com"},"license":"MIT","_id":"@alosha/stride@0.2.1","maintainers":[{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"}],"homepage":"https://stride.alosha.dev","bugs":{"url":"https://github.com/avlisodraude/stride/issues"},"bin":{"stride":"dist/cli.js"},"dist":{"shasum":"95b68708a13bd5e39309a84ba1b34e044382733c","tarball":"https://registry.npmjs.org/@alosha/stride/-/stride-0.2.1.tgz","fileCount":12,"integrity":"sha512-slqm/vW0uu5+Ki2jgoWZD9fkAs593R0hJWCxibtyGWtYqvqI5uHeN8acdvikKHMSDacOEHAU+Lsy5a5zp0umKg==","signatures":[{"sig":"MEYCIQCRZnm2Bxi/p5BxttmTwvIf9yHmfokOqUzotJzgLAJwsgIhAJ4euzQelUxDYG1pUlkGCe1MxRJcpyVyR1HCXNx4BmtK","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":642459},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"c48e34f50e9e07fc938c8c43c6ecfc837b49a8a1","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup","prepare":"husky","pretest":"npm run build","release":"npm run build && npm publish --access public","sample:fit":"node scripts/make-sample-fit.mjs test/fixtures/sample-run.fit","build:browser":"node scripts/build-browser.mjs"},"_npmUser":{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"},"overrides":{"esbuild":"0.28.1","js-yaml":"4.2.0"},"repository":{"url":"git+https://github.com/avlisodraude/stride.git","type":"git"},"_npmVersion":"11.16.0","description":"Parse GPX, TCX and FIT files and compute running metrics — pace, HR zones, splits, and Chart.js dashboards. Zero config.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"chart.js":"^4.4.3","@garmin/fitsdk":"^21.205.0","fast-xml-parser":"^5.9.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.1.0","husky":"^9.0.11","eslint":"^9.5.0","typescript":"^5.4.5","@types/node":"^20.14.0","@commitlint/cli":"^19.3.0","@commitlint/config-conventional":"^19.2.2"},"_npmOperationalInternal":{"tmp":"tmp/stride_0.2.1_1781608362869_0.8407110221771619","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@alosha/stride","version":"0.2.2","keywords":["gpx","gpx-parser","fit","fit-parser","fit-file","tcx","tcx-parser","running","strava","garmin","activity","heart-rate","heart-rate-zones","pace","splits","chartjs","chart","activity-analysis","fitness","cli"],"author":{"name":"Eduardo Silva Navarrete","email":"avlis_odraude@hotmail.com"},"license":"MIT","_id":"@alosha/stride@0.2.2","maintainers":[{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"}],"homepage":"https://stride.alosha.dev","bugs":{"url":"https://github.com/avlisodraude/stride/issues"},"bin":{"stride":"dist/cli.js"},"dist":{"shasum":"4dc0c87a8f1362c3bb9f48f87e426fbb8c144491","tarball":"https://registry.npmjs.org/@alosha/stride/-/stride-0.2.2.tgz","fileCount":12,"integrity":"sha512-0FSTJwXmdIgfJ4/+XKm8Ggm0r/coRAgQi4TMx0QNwJbVN/0cmcE1VkqL4ipD9hVPiPOOIg/wMOz8z3kfT3JsZA==","signatures":[{"sig":"MEYCIQCyjV8Bq0c8mdonYrYEngsHbrT9x1VDf5S/8zqFcaCqhQIhAKU/LE16CR1v5PqYK8eB/ZgKl0vCDPH+7zCajDkBukCb","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":170691},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"ff29dbc37da7ebeb226faa2d70099387df3714a3","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup","prepare":"husky","pretest":"npm run build","release":"npm run build && npm publish --access public","sample:fit":"node scripts/make-sample-fit.mjs test/fixtures/sample-run.fit","build:browser":"node scripts/build-browser.mjs"},"_npmUser":{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"},"overrides":{"esbuild":"0.28.1","js-yaml":"4.2.0"},"repository":{"url":"git+https://github.com/avlisodraude/stride.git","type":"git"},"_npmVersion":"11.16.0","description":"Parse GPX, TCX and FIT files and compute running metrics — pace, HR zones, splits, and Chart.js dashboards. Zero config.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"chart.js":"^4.4.3","@garmin/fitsdk":"^21.205.0","fast-xml-parser":"^5.9.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.1.0","husky":"^9.0.11","eslint":"^9.5.0","@eslint/js":"^9.5.0","typescript":"^5.4.5","@types/node":"^20.14.0","@commitlint/cli":"^19.3.0","typescript-eslint":"^8.61.1","@commitlint/config-conventional":"^19.2.2"},"_npmOperationalInternal":{"tmp":"tmp/stride_0.2.2_1783582942208_0.7950949984954341","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@alosha/stride","version":"1.0.0","keywords":["gpx","gpx-parser","fit","fit-parser","fit-file","tcx","tcx-parser","running","strava","garmin","activity","heart-rate","heart-rate-zones","pace","splits","chartjs","chart","activity-analysis","fitness","cli"],"author":{"name":"Eduardo Silva Navarrete","email":"avlis_odraude@hotmail.com"},"license":"MIT","_id":"@alosha/stride@1.0.0","maintainers":[{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"}],"homepage":"https://stride.alosha.dev","bugs":{"url":"https://github.com/avlisodraude/stride/issues"},"bin":{"stride":"dist/cli.js"},"dist":{"shasum":"69343a08e85947c9cc0af2b5a231a4aa9b36df95","tarball":"https://registry.npmjs.org/@alosha/stride/-/stride-1.0.0.tgz","fileCount":12,"integrity":"sha512-mix/V8abb1j/oVZAVsKZMtPwvRbNtBEdy5LJSvRDlumRWrdmHTGUdz4qANoTBso8g7BkpF2PJaJL333Fd5JyDQ==","signatures":[{"sig":"MEUCIQDYay+XucZ/GFjgZJZnABUnI3ov1FkeNTz/i92EGq7g5AIgfzHF9VKgjLKEJKZEsPQk2VtpjZXlfUB+G0EsFdWiU54=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":214714},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"42fd8bb9ad7d26971946b109b5984ad7bbf15c66","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup","prepare":"husky","pretest":"npm run build","release":"npm run build && npm publish --access public","sample:fit":"node scripts/make-sample-fit.mjs test/fixtures/sample-run.fit","build:browser":"node scripts/build-browser.mjs"},"_npmUser":{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"},"overrides":{"esbuild":"0.28.1","js-yaml":"4.2.0"},"repository":{"url":"git+https://github.com/avlisodraude/stride.git","type":"git"},"_npmVersion":"11.16.0","description":"Parse GPX, TCX and FIT files and compute running metrics — pace, HR zones, splits, and Chart.js dashboards. Zero config.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"chart.js":"^4.4.3","@garmin/fitsdk":"^21.205.0","fast-xml-parser":"^5.9.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.1.0","husky":"^9.0.11","eslint":"^9.5.0","@eslint/js":"^9.5.0","typescript":"^5.4.5","@types/node":"^20.14.0","@commitlint/cli":"^19.3.0","typescript-eslint":"^8.61.1","@commitlint/config-conventional":"^19.2.2"},"_npmOperationalInternal":{"tmp":"tmp/stride_1.0.0_1783627244852_0.1870731009669524","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@alosha/stride","version":"2.0.0","keywords":["gpx","gpx-parser","fit","fit-parser","fit-file","tcx","tcx-parser","running","strava","garmin","activity","heart-rate","heart-rate-zones","pace","splits","chartjs","chart","activity-analysis","fitness","cli"],"author":{"name":"Eduardo Silva Navarrete","email":"avlis_odraude@hotmail.com"},"license":"MIT","_id":"@alosha/stride@2.0.0","maintainers":[{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"}],"homepage":"https://stride.alosha.dev","bugs":{"url":"https://github.com/avlisodraude/stride/issues"},"bin":{"stride":"dist/cli.js"},"dist":{"shasum":"566f0965800fb54faf74e20b4b37d552fe6120cb","tarball":"https://registry.npmjs.org/@alosha/stride/-/stride-2.0.0.tgz","fileCount":24,"integrity":"sha512-au2tFVKqUS+OJrZ2hcpULkQTjwTunAA+Ui1r/jd2eyfAwDbnRcOuBjCWzwITWO8QUy5KJz+eg6QsaA2vmxzbvg==","signatures":[{"sig":"MEYCIQCQ1t2+X50RaU3hr2KZp3p+o7mKHeRRnTcJREcNSXVpSQIhAP92HVMUa4UIj6uyTIWBt0cEVjp/b6CY668teq99GN22","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":565231},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"browser":{"types":"./dist/index.browser.d.ts","default":"./dist/index.browser.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./charts":{"import":{"types":"./dist/charts.d.ts","default":"./dist/charts.js"},"require":{"types":"./dist/charts.d.cts","default":"./dist/charts.cjs"}},"./package.json":"./package.json"},"gitHead":"c51379fa974e79badc26651867a9d6cf42f00975","scripts":{"dev":"tsup --watch","lint":"eslint src","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup && npm run build:browser","prepare":"husky","pretest":"npm run build","release":"npm run build && npm publish --access public","test:real":"node --experimental-vm-modules node_modules/.bin/jest --testMatch '**/*.manual.test.js' --testPathIgnorePatterns '/node_modules/'","sample:fit":"node scripts/make-sample-fit.mjs test/fixtures/sample-run.fit","sample:climb":"node scripts/make-climb-fit.mjs test/fixtures/climb-run.fit","build:browser":"node scripts/build-browser.mjs"},"_npmUser":{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"},"overrides":{"esbuild":"0.28.1","js-yaml":"4.2.0"},"repository":{"url":"git+https://github.com/avlisodraude/stride.git","type":"git"},"_npmVersion":"11.16.0","description":"Parse GPX, TCX and FIT files and compute running metrics — pace, HR zones, splits, and Chart.js dashboards. Zero config.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{"@garmin/fitsdk":"^21.205.0","fast-xml-parser":"^5.9.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.1.0","husky":"^9.0.11","eslint":"^9.5.0","chart.js":"^4.4.3","@eslint/js":"^9.5.0","typescript":"^5.4.5","@types/node":"^20.14.0","@commitlint/cli":"^19.3.0","typescript-eslint":"^8.61.1","@commitlint/config-conventional":"^19.2.2"},"peerDependencies":{"chart.js":"^4.4.3"},"peerDependenciesMeta":{"chart.js":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/stride_2.0.0_1783698945284_0.8370106348556561","host":"s3://npm-registry-packages-npm-production"}},"2.0.1":{"name":"@alosha/stride","version":"2.0.1","description":"Parse GPX, TCX and FIT files and compute running metrics — pace, HR zones, splits, and Chart.js dashboards. Zero config.","keywords":["gpx","gpx-parser","fit","fit-parser","fit-file","tcx","tcx-parser","running","strava","garmin","activity","heart-rate","heart-rate-zones","pace","splits","chartjs","chart","activity-analysis","fitness","cli"],"homepage":"https://stride.alosha.dev","bugs":{"url":"https://github.com/avlisodraude/stride/issues"},"repository":{"type":"git","url":"git+https://github.com/avlisodraude/stride.git"},"license":"MIT","author":{"name":"Eduardo Silva Navarrete","email":"avlis_odraude@hotmail.com"},"type":"module","engines":{"node":">=18.0.0"},"exports":{".":{"browser":{"types":"./dist/index.browser.d.ts","default":"./dist/index.browser.js"},"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./charts":{"import":{"types":"./dist/charts.d.ts","default":"./dist/charts.js"},"require":{"types":"./dist/charts.d.cts","default":"./dist/charts.cjs"}},"./package.json":"./package.json"},"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","sideEffects":false,"bin":{"stride":"dist/cli.js"},"scripts":{"build":"tsup && npm run build:browser","build:browser":"node scripts/build-browser.mjs","dev":"tsup --watch","pretest":"npm run build","test":"node --experimental-vm-modules node_modules/.bin/jest","sample:fit":"node scripts/make-sample-fit.mjs test/fixtures/sample-run.fit","sample:climb":"node scripts/make-climb-fit.mjs test/fixtures/climb-run.fit","lint":"eslint src","prepare":"husky","release":"npm run build && npm publish --access public","test:real":"node --experimental-vm-modules node_modules/.bin/jest --testMatch '**/*.manual.test.js' --testPathIgnorePatterns '/node_modules/'"},"dependencies":{"@garmin/fitsdk":"^21.205.0","fast-xml-parser":"^5.9.0"},"peerDependencies":{"chart.js":"^4.4.3"},"peerDependenciesMeta":{"chart.js":{"optional":true}},"devDependencies":{"@commitlint/cli":"^19.3.0","@commitlint/config-conventional":"^19.2.2","@eslint/js":"^9.5.0","@types/node":"^20.14.0","chart.js":"^4.4.3","eslint":"^9.5.0","husky":"^9.0.11","jest":"^29.7.0","tsup":"^8.1.0","typescript":"^5.4.5","typescript-eslint":"^8.61.1"},"overrides":{"esbuild":"0.28.1","js-yaml":"4.2.0"},"gitHead":"a27c01652c2ae50bea9a957441e1e34122da37c2","_id":"@alosha/stride@2.0.1","_nodeVersion":"22.22.3","_npmVersion":"11.16.0","dist":{"integrity":"sha512-3Zr2k9dRJrs7RJ9rO2fqVactliePTjS4vBKmM+tOKg48yZmVfs4y74ShK/DU7shNjLhFeiFSwok7HBRKf+DABA==","shasum":"d82a12e3bc7e0f30acd31d75e6765b6575f932d3","tarball":"https://registry.npmjs.org/@alosha/stride/-/stride-2.0.1.tgz","fileCount":24,"unpackedSize":568883,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEaatMSaYN3gzHeyrSqDmQf5X4HFKPgw9c1Xx7qAHYEbAiEA5geXktTHKD80MK226JQdURsVV0p3Ytf8as0xt5v2Mp8="}]},"_npmUser":{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"},"directories":{},"maintainers":[{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/stride_2.0.1_1783707329916_0.07277248832362804"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-14T12:28:14.317Z","modified":"2026-07-10T18:15:30.279Z","0.1.0":"2026-06-14T12:28:14.658Z","0.1.1":"2026-06-14T12:32:28.687Z","0.1.2":"2026-06-14T13:11:01.014Z","0.1.3":"2026-06-14T13:32:06.822Z","0.1.4":"2026-06-14T15:00:06.613Z","0.2.0":"2026-06-15T16:40:29.533Z","0.2.1":"2026-06-16T11:12:42.998Z","0.2.2":"2026-07-09T07:42:22.349Z","1.0.0":"2026-07-09T20:00:45.063Z","2.0.0":"2026-07-10T15:55:45.459Z","2.0.1":"2026-07-10T18:15:30.143Z"},"bugs":{"url":"https://github.com/avlisodraude/stride/issues"},"author":{"name":"Eduardo Silva Navarrete","email":"avlis_odraude@hotmail.com"},"license":"MIT","homepage":"https://stride.alosha.dev","keywords":["gpx","gpx-parser","fit","fit-parser","fit-file","tcx","tcx-parser","running","strava","garmin","activity","heart-rate","heart-rate-zones","pace","splits","chartjs","chart","activity-analysis","fitness","cli"],"repository":{"type":"git","url":"git+https://github.com/avlisodraude/stride.git"},"description":"Parse GPX, TCX and FIT files and compute running metrics — pace, HR zones, splits, and Chart.js dashboards. Zero config.","maintainers":[{"name":"avlisodraude","email":"avlis_odraude@hotmail.com"}],"readme":"# @alosha/stride\n\nParse GPX, TCX and FIT files, compute running metrics, and build Chart.js dashboards — zero config.\n\n[![npm version](https://img.shields.io/npm/v/@alosha/stride)](https://www.npmjs.com/package/@alosha/stride)\n[![npm downloads](https://img.shields.io/npm/dm/@alosha/stride)](https://www.npmjs.com/package/@alosha/stride)\n[![Types included](https://img.shields.io/badge/types-included-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n- **GPX, TCX & FIT in, insights out** — parses GPX and TCX XML plus binary FIT files from Garmin, Strava, Coros, Wahoo and more. Format is auto-detected; the same `analyze()` works for all three.\n- **Every running metric you want** — pace, splits, HR zones (Z1–Z5), elevation, cadence, moving vs. elapsed time — each computed the way Garmin and Strava compute it, and labelled with where it came from (`distanceSource`, `elevationSource`).\n- **Charts with zero boilerplate** — 5 ready-made Chart.js configs (pace, elevation, heart rate, HR zones, splits) behind a separate `@alosha/stride/charts` entry point, so parse/analyze consumers never pull in Chart.js.\n- **CLI and library** — `npx stride analyze run.gpx`, metric or imperial, no config required.\n\nUpgrading from 1.x? Read **[MIGRATING.md](./MIGRATING.md)** — two silent changes, three loud ones, one deprecation.\n\n## Install\n\n```bash\nnpm install @alosha/stride\n```\n\nRequires **Node >= 18** (any current LTS). Parsing and metrics have no peer\ndependencies. `chart.js` is an *optional* peer dependency needed **only** if\nyou use the chart builders, which live behind a separate entry point:\n\n```bash\nnpm install chart.js    # only if you import @alosha/stride/charts\n```\n\nInstalling `@alosha/stride` alone pulls no charting code and emits no peer\nwarning. Note that `@alosha/stride/charts` returns plain Chart.js\nconfiguration objects and never calls Chart.js itself — you need the package\nto **render** a config and, if you use TypeScript, to resolve the\n`ChartConfiguration` type in the builders' signatures.\n\n## Quick start\n\n```ts\nimport { parseFile, analyze } from '@alosha/stride'\n\n// Parse a GPX, TCX or FIT file — the format is detected automatically\nconst activity = await parseFile('./my-run.fit')\n\n// Compute all running metrics\nconst stats = analyze(activity, { maxHR: 185 })\nconsole.log(stats.distanceM, stats.avgPaceSecPerKm, stats.hrZones)\nconsole.log(stats.distanceSource, stats.elevationSource)  // 'device' | 'computed'\n```\n\nTo render charts, add the optional `chart.js` peer and import from the\ncharts entry point:\n\n```ts\nimport { analyze } from '@alosha/stride'\nimport { paceChartConfig, splitsChartConfig } from '@alosha/stride/charts'\nimport { Chart } from 'chart.js/auto'   // npm install chart.js\n\nconst stats = analyze(activity)\nnew Chart(document.querySelector<HTMLCanvasElement>('#pace')!, paceChartConfig(activity, stats))\nnew Chart(document.querySelector<HTMLCanvasElement>('#splits')!, splitsChartConfig(stats))\n```\n\n## Accuracy\n\nEvery metric is computed the way the systems you compare against — Garmin\nConnect, Strava — compute it, with the derivation written down in\n[`docs/metrics-spec.md`](./docs/metrics-spec.md). The four that most\nlibraries get wrong:\n\n- **Device-reported distance over summed haversine.** Summing haversine\n  distances between raw GPS fixes integrates ±3–5 m of per-fix jitter as\n  extra path length, typically +1–3%. When a FIT or TCX file carries the\n  device's own filtered distance stream, stride uses it — and tells you so\n  via `distanceSource`. ([spec §2.3](./docs/metrics-spec.md))\n- **Time-weighted HR zones over sample counts.** Smart-recording watches\n  sample hard efforts more densely, so counting samples systematically\n  inflates time in high zones. Stride attributes each segment's *duration*\n  to a zone, matching what Garmin Connect and TrainingPeaks report.\n  ([spec §1](./docs/metrics-spec.md))\n- **Hysteresis-filtered elevation, with deference to the device.** Raw\n  positive altitude deltas integrate GPS noise as climb; stride only credits\n  a rise once it cumulatively clears a threshold (default 8 m, the\n  GPS-altitude figure), and defers outright to the device's barometric total\n  when a FIT file carries one. ([spec §5](./docs/metrics-spec.md))\n- **A true rolling best-km.** The fastest kilometre almost never starts on a\n  split boundary, so the fastest *bucketed split* systematically understates\n  it. Stride slides a 1000 m window over the cumulative series with\n  interpolated edges, like Strava's Best Efforts. ([spec §2](./docs/metrics-spec.md))\n\n## Why do these numbers disagree?\n\nThree pairs of numbers differ **by design**. Each alone can read as a bug;\ntogether they are the same policy applied consistently: report the best\navailable instrument for each question, and never fudge one number to make\nit agree with another.\n\n**`distanceM: 1980` vs `deviceDistanceM: 1983.3`.** The device counts\ndistance accumulated before its first position fix (and during position-less\nsegments); `distanceM` measures between the first and last *recorded* point.\nThe gap is real distance that has no GPS points to attach to. Both are\nreported; nothing is derived from `deviceDistanceM`.\n\n**`elevationGainM: 78` vs `sum(splits[].elevationGainM): 58`** (when\n`elevationSource === 'device'`). The device's total ascent is an\nactivity-level scalar from its barometric altimeter — it cannot be\nattributed to a distance range. Splits therefore keep using the GPS-altitude\nhysteresis pass, the only elevation signal that can be sliced by distance.\nThe parts answer a different question than the whole, with a different\ninstrument. We do not fudge the splits to make them add up.\n\n**Elevation fell (threshold) *and* rose (device) in the same release.**\nTwo independent 2.0.0 changes: the hysteresis threshold went 3 m → 8 m\nbecause GPS altitude noise is larger than barometric (lowers gain —\n`sample-run.tcx`: 12 → 0), and FIT files carrying `session.totalAscent` now\ndefer to the device (can raise it — `climb-run.fit`: 78 where GPS computes\n58). Check `elevationSource` to see which regime a file is in.\n\n**A known limitation of the hysteresis filter:** a climb's trailing rise\nthat never cumulatively clears the threshold is never credited — crediting\nan in-progress, still-ambiguous rise would re-admit exactly the noise the\nthreshold exists to reject. `gpx-climb.gpx` climbs 30 m from its starting\nelevation and reports 28. ([spec §5.3](./docs/metrics-spec.md))\n\n## What stride does not solve\n\n- **Attributing a device elevation total to splits.** When\n  `elevationSource === 'device'`, per-split gains stay GPS-derived and do\n  not sum to `elevationGainM` (see above). No rescaling is offered.\n- **The trailing unconfirmed climb.** Hysteresis never credits a rise that\n  hasn't cleared the threshold when the track ends: `gpx-climb.gpx` climbs\n  30 m, reports 28. Lowering `elevationThresholdM` narrows this at the cost\n  of admitting noise.\n- **Reconciling `deviceDistanceM` with `distanceM`.** The gap (distance\n  before the first fix) is reported, not repaired — there are no points to\n  attach it to.\n- **GPX device data.** GPX has no standard device distance or elevation\n  total, so GPX is always `distanceSource: 'computed'` and\n  `elevationSource: 'computed'`; its accuracy is bounded by GPS quality.\n- **DEM elevation correction.** Garmin Connect re-derives elevation from a\n  terrain model for non-barometric watches; stride only works with what is\n  in the file.\n- **Device pause events.** `movingTimeSec` comes from a speed threshold\n  (`pauseThresholdMps`), not from the watch's own timer-stop events, which\n  most exports don't carry per-point.\n- **Lactate-threshold (LTHR) zone model.** `zoneModel` supports the two\n  anchors a runner can actually supply — `hrmax` and `reserve` (Karvonen).\n  Anchoring zones to lactate-threshold HR is deliberately not implemented:\n  LTHR must be *measured* by a 30-minute time-trial field test, and the\n  established LTHR conventions use seven zones rather than the five in\n  `HeartRateZones`. A future `zoneModel: { type: 'lthr' }` mapping onto the\n  existing five zones would be a non-breaking addition.\n- **Multisport FIT files are unexercised.** When a FIT file carries more than\n  one `session` message (a triathlon, a brick workout), stride sums\n  `totalDistance`, `totalAscent` and `totalDescent` across them. That\n  summation is implemented but has no test fixture — every fixture in the\n  suite carries exactly one session — so the multi-session path has never\n  executed. Single-session files, which is nearly every run, are covered.\n- **Streaming parse.** `parse()` and `parseFile()` read the whole file into\n  memory. Activity files are kilobytes to a few megabytes, so this is the\n  right trade; it would be the wrong one for a multi-gigabyte archive.\n\n## Supported formats\n\n| Format | Extension | Input types | Typical sources |\n|---|---|---|---|\n| **GPX** | `.gpx` | file path, raw XML string | Strava, Apple Health/Watch routes, Komoot, most apps |\n| **TCX** | `.tcx` | file path, raw XML string | Garmin Connect, Strava, Wahoo, Zwift |\n| **FIT** | `.fit` | file path, `Uint8Array`, `ArrayBuffer` | Garmin, Coros, Wahoo, Suunto, Polar (native device files) |\n\nThe format is auto-detected — you call `parse()`/`parseFile()` and never\nbranch on file type (pass `{ format }` to skip sniffing when you already\nknow it). All three normalise into the same `Activity` shape: GPS track,\nelevation, heart rate, cadence, timestamps, and — where the source provides\nthem — the device's own distance and elevation figures.\n\n## CLI\n\n```bash\nnpx stride analyze my-run.gpx\nnpx stride analyze my-run.tcx            # TCX and FIT work too — auto-detected\nnpx stride analyze my-run.fit\nnpx stride analyze my-run.fit --imperial\n```\n\nFlags: `--imperial` for miles/feet/min-per-mile; `--max-hr 185` and\n`--elevation-threshold 2` expose the corresponding `analyze()` options; and\n`--json` prints the raw `ActivityStats` object — the same documented schema\nthe library returns — for scripting:\n\n```bash\nnpx stride analyze my-run.fit --json | jq '.avgPaceSecPerKm'\nnpx stride analyze my-run.fit --max-hr 185 --json | jq '.hrZones'\n```\n\nOutput (real output for the repo's `test/fixtures/sample-run.tcx`):\n\n```\n🏃 @alosha/stride — test/fixtures/sample-run.tcx\n\n  Distance:      1.98 km\n  Moving time:   10:00\n  Elapsed time:  10:00\n  Avg pace:      5:03/km\n  Best km pace:  5:03/km\n  Elevation ↑:   0m\n  Elevation ↓:   0m\n  Avg HR:        143 bpm\n  Max HR:        163 bpm\n  Avg cadence:   174 spm\n\n  Splits:\n    km  1  5:03/km  HR 131bpm\n    km  2  5:03/km  (0.98 km)  HR 154bpm\n```\n\n## API\n\n### `parse(input, options?)`\n\n```ts\nparse(input: string | Uint8Array | ArrayBuffer, options?: { format?: 'gpx' | 'tcx' | 'fit' }): Activity\n```\n\nSynchronous. Accepts a file path (Node), raw GPX/TCX XML, or FIT bytes, and\nauto-detects the format:\n\n```ts\nimport { parse } from '@alosha/stride'\n\nconst a1 = parse('./run.gpx')                 // file path (GPX / TCX / FIT)\nconst a2 = parse(xmlString)                   // raw GPX or TCX XML\nconst a3 = parse(new Uint8Array(fitBytes))    // FIT bytes (browser / streamed)\nconst a4 = parse(xmlString, { format: 'gpx' }) // skip format sniffing\n```\n\n| Input | Detected as |\n|---|---|\n| String containing `<gpx` | GPX |\n| String containing `<TrainingCenterDatabase` | TCX |\n| File path to a `.FIT` file, or `Uint8Array` / `ArrayBuffer` bytes | FIT |\n\nA short string that is neither a readable path nor recognisable GPX/TCX/FIT\nthrows a clear error (with a truncated preview of the input), not a bare\n`ENOENT`.\n\n### `parseFile(path, options?)` — Node only\n\n```ts\nparseFile(path: string, options?: { format?: 'gpx' | 'tcx' | 'fit' }): Promise<Activity>\n```\n\nAsync, backed by `fs/promises` — reads without blocking the event loop, and\nskips path-vs-content sniffing entirely since the argument is unambiguously\na path. Prefer it whenever you're in Node and reading from disk; use\n`parse()` when you already hold the content (an upload, a string, a fetch\nresponse) or need a synchronous call.\n\n```ts\nimport { parseFile } from '@alosha/stride'\n\nconst activity = await parseFile('./my-run.fit')\nconst gpx = await parseFile('./export.xml', { format: 'gpx' })  // odd extension, known format\n```\n\n`parseFile` is absent from the browser build (see [Browser](#browser)).\n\n### `analyze(activity, options?)`\n\n```ts\nanalyze(activity: Activity, options?: AnalyzeOptions): ActivityStats\n```\n\nComputes every metric in one pass. The 1.x positional form\n`analyze(activity, maxHR, elevationThresholdM)` still works but is\ndeprecated and will be removed in 3.0.0.\n\n```ts\nconst stats = analyze(activity, {\n  maxHR: 185,\n  zoneModel: { type: 'reserve', restingHR: 52 },\n  elevationThresholdM: 2,\n  pauseThresholdMps: 0.5,\n})\n```\n\n#### `AnalyzeOptions`\n\n| Option | Type | Default | What it changes |\n|---|---|---|---|\n| `maxHR` | `number` | `190` | The athlete's max heart rate — the reference all zone percentages are computed against. Affects `hrZones` only. Must be finite and within **60–220 bpm**; outside that range throws instead of silently dividing by zero or inverting the pct formula into a single zone. |\n| `zoneModel` | `HrZoneModel` | `{ type: 'hrmax' }` | Which formula turns a heart rate into a zone percentage. `{ type: 'hrmax' }` uses `hr / maxHR` (the historical behaviour). `{ type: 'reserve', restingHR }` uses the Karvonen formula, `(hr − restingHR) / (maxHR − restingHR)` — the same effort reads a *lower* percentage, so samples shift toward lower zones relative to `'hrmax'`. `restingHR` must be finite, **non-negative**, and **less than `maxHR`**; missing, negative, or out-of-order values throw. Both variants accept an optional `boundaries: [number, number, number, number]` (default `[0.6, 0.7, 0.8, 0.9]`, the 60/70/80/90% bands). Boundaries must be strictly increasing and each strictly between 0 and 1 — anything else throws instead of silently mis-bucketing. |\n| `elevationThresholdM` | `number` | `8` | The hysteresis threshold: a cumulative rise (or fall) must clear this many metres before it is credited as gain (or loss). The default suits GPS-derived altitude; pass `2` for barometric data. Affects `elevationGainM`/`elevationLossM` when `elevationSource` is `'computed'`, and `splits[].elevationGainM` always. |\n| `pauseThresholdMps` | `number` | `0.3` | Speed (m/s) at or below which a segment counts as paused rather than moving. Raising it treats slow shuffling and GPS drift at rest as pauses. Affects `movingTimeSec`, and through it `avgPaceSecPerKm` (= moving time ÷ distance). |\n\n### Chart builders — `@alosha/stride/charts`\n\nAll builders return a plain [Chart.js configuration object](https://www.chartjs.org/docs/latest/configuration/)\n— you instantiate `Chart` yourself, so the library never touches the DOM.\nRequires the optional `chart.js` peer to render (and for the\n`ChartConfiguration` type).\n\n| Function | Chart type | Notes |\n|---|---|---|\n| `paceChartConfig(activity, stats, opts?)` | Line | Trailing partial split labelled distinctly (`km 2 (0.98 km)`) |\n| `elevationChartConfig(activity, stats, opts?)` | Line | x-axis uses the same distance series as `stats.distanceM` |\n| `heartRateChartConfig(activity, stats, opts?)` | Line | x-axis uses the same distance series as `stats.distanceM` |\n| `hrZonesChartConfig(stats)` | Doughnut | Never throws — renders a labelled empty chart when there's no HR data |\n| `splitsChartConfig(stats, opts?)` | Bar | Partial split's bar is faded as well as labelled |\n\n```ts\nimport { Chart } from 'chart.js/auto'\nimport { paceChartConfig, elevationChartConfig, hrZonesChartConfig } from '@alosha/stride/charts'\n\nnew Chart(canvas1, paceChartConfig(activity, stats, { units: 'imperial' }))\nnew Chart(canvas2, elevationChartConfig(activity, stats))\nnew Chart(canvas3, hrZonesChartConfig(stats))\n```\n\n### Formatting helpers\n\n```ts\nimport { formatPace, formatDistance, formatDuration } from '@alosha/stride'\n\nformatPace(302, 'metric')    // \"5:02/km\"\nformatPace(302, 'imperial')  // \"8:06/mi\"\nformatDistance(10240)        // \"10.24 km\"\nformatDuration(3092)         // \"51:32\"\n```\n\n## ActivityStats reference\n\n| Field | Type | Description |\n|---|---|---|\n| `distanceM` | `number` | Total distance in metres, from the first to the last recorded point |\n| `distanceSource` | `'device' \\| 'computed'` | Whether `distanceM` (and the series behind `splits[]` / `bestKmPaceSecPerKm`) came from the file's own device distance stream, or was summed from GPS points |\n| `deviceDistanceM` | `number \\| undefined` | The device's own total distance (TCX `<Lap><DistanceMeters>`, FIT `session.totalDistance`), passed through **unrounded**. Undefined for GPX. May exceed `distanceM` — see [Why do these numbers disagree?](#why-do-these-numbers-disagree) |\n| `elapsedTimeSec` | `number` | Total elapsed time in seconds |\n| `movingTimeSec` | `number` | Moving time in seconds (segments at or below `pauseThresholdMps` excluded) |\n| `avgPaceSecPerKm` | `number` | Average pace in sec/km (moving time ÷ distance) |\n| `bestKmPaceSecPerKm` | `number \\| null` | Fastest 1000 m anywhere in the activity, as a rolling window with interpolated edges — independent of `splits[]`. Null under 1 km total |\n| `elevationGainM` | `number` | Total elevation gain in metres |\n| `elevationLossM` | `number` | Total elevation loss in metres |\n| `elevationSource` | `'device' \\| 'computed'` | `'device'` when a FIT `session.totalAscent`/`totalDescent` was used; `'computed'` for the hysteresis filter (GPX and TCX always). When `'device'`, split gains don't sum to the total — see [Why do these numbers disagree?](#why-do-these-numbers-disagree) |\n| `avgHeartRate` | `number \\| null` | Average HR in bpm |\n| `maxHeartRate` | `number \\| null` | Max HR in bpm |\n| `hrZones` | `HeartRateZones \\| null` | Time in each HR zone, in seconds, time-weighted (not sample-counted) |\n| `avgCadence` | `number \\| null` | Average cadence in steps/min |\n| `splits` | `Split[]` | Per-km splits at exact 1000 m marks, plus a trailing partial split (`distanceM !== 1000`). `sum(splits[].distanceM) === distanceM` |\n\n## Browser\n\nThe package ships a dedicated browser build behind the `exports` map's\n`browser` condition — bundlers (and TypeScript with\n`customConditions: [\"browser\"]`) resolve `dist/index.browser.js` with its own\ntype declarations, and that build never references `fs`.\n\nThe browser build is `parse()`, `analyze()` and the formatting helpers.\n**`parseFile()` is Node-only and absent from the browser build** — in the\nbrowser there are no file paths; read the file yourself and hand the content\nto `parse()`:\n\n```ts\nimport { parse, analyze } from '@alosha/stride'\nimport { paceChartConfig } from '@alosha/stride/charts'\nimport { Chart } from 'chart.js/auto'\n\n// A user drops a .fit / .gpx / .tcx export onto your page.\nasync function renderUpload(file: File, canvas: HTMLCanvasElement) {\n  const bytes = new Uint8Array(await file.arrayBuffer())\n  const activity = parse(bytes)     // format auto-detected: GPX / TCX / FIT\n  const stats = analyze(activity)   // distance, pace, HR zones, splits\n\n  new Chart(canvas, paceChartConfig(activity, stats))\n  return stats\n}\n```\n\n(For GPX/TCX you can equally pass the file's text: `parse(await file.text())`.)\n\n## Examples\n\nThe [`examples/`](./examples) directory holds small, runnable scripts — one\nper feature, each with its real output pasted at the bottom: `parseFile` vs\n`parse`, explicit `format`, both zone models side by side, pause and\nelevation thresholds, `distanceSource`/`elevationSource` branching, and the\ncharts entry point. See [`examples/README.md`](./examples/README.md).\n\n## Support & custom work\n\n`@alosha/stride` is free and MIT-licensed, and always will be. When you need more than the open-source library, there's a paid path backed by the maintainer — not a ticket queue:\n\n- **Priority support** — a direct line to the person who wrote it, with prioritised fixes.\n- **Custom work** — bespoke chart types or running metrics, and help integrating Stride into your app or platform.\n\nGet in touch at [alosha.dev/support](https://alosha.dev/support).\n\n---\n\nDocs & live demo: [stride.alosha.dev](https://stride.alosha.dev) · Built by [Alosha](https://alosha.dev)\n","readmeFilename":"README.md"}