@dofek/trainingpeaks 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Asher Cohen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # @dofek/trainingpeaks
2
+
3
+ Unofficial TypeScript client for private TrainingPeaks web endpoints. It reads
4
+ athlete workouts, profile data, Performance Management Chart data, personal
5
+ records, calendar notes, and workout analysis.
6
+
7
+ This package is not affiliated with, endorsed by, or supported by
8
+ TrainingPeaks. Its cookie flow and undocumented endpoints may change without
9
+ notice.
10
+
11
+ ## Requirements and installation
12
+
13
+ Requires Node.js 22.14 or newer and its built-in
14
+ [`fetch`](https://nodejs.org/api/globals.html#fetch) implementation.
15
+
16
+ ```sh
17
+ npm install @dofek/trainingpeaks
18
+ ```
19
+
20
+ ## Quick start
21
+
22
+ Log in to `app.trainingpeaks.com` in a browser and copy the value of its
23
+ `Production_tpAuth` cookie. Save this example as `example.mjs`, set
24
+ `TRAININGPEAKS_AUTH_COOKIE` and `TRAININGPEAKS_ATHLETE_ID`, then run
25
+ `node example.mjs`.
26
+
27
+ ```js
28
+ import { TrainingPeaksConnectClient } from "@dofek/trainingpeaks";
29
+
30
+ const savedCookie = process.env.TRAININGPEAKS_AUTH_COOKIE;
31
+ const athleteId = Number(process.env.TRAININGPEAKS_ATHLETE_ID);
32
+ if (!savedCookie || !Number.isInteger(athleteId)) {
33
+ throw new Error("Set TRAININGPEAKS_AUTH_COOKIE and TRAININGPEAKS_ATHLETE_ID");
34
+ }
35
+
36
+ const refreshedCookie =
37
+ await TrainingPeaksConnectClient.refreshCookie(savedCookie);
38
+ const { accessToken, expiresIn } =
39
+ await TrainingPeaksConnectClient.exchangeCookieForToken(refreshedCookie);
40
+ const client = new TrainingPeaksConnectClient(accessToken);
41
+ const workouts = await client.getWorkouts(
42
+ athleteId,
43
+ "2026-07-01",
44
+ "2026-07-07",
45
+ );
46
+
47
+ console.log({ expiresIn, workouts: workouts.length });
48
+ // Replace the saved cookie with refreshedCookie in encrypted storage.
49
+ ```
50
+
51
+ Dates passed to range methods use `YYYY-MM-DD`.
52
+
53
+ ## Public API
54
+
55
+ Authentication:
56
+
57
+ - `TrainingPeaksConnectClient.refreshCookie(cookie, fetch?)` returns the
58
+ replacement `Production_tpAuth` cookie from the observed refresh endpoint.
59
+ - `TrainingPeaksConnectClient.exchangeCookieForToken(cookie, fetch?)` returns
60
+ `accessToken` and `expiresIn` in seconds.
61
+
62
+ Data:
63
+
64
+ - `getUser()`
65
+ - `getWorkouts(athleteId, startDate, endDate)` and
66
+ `getWorkout(athleteId, workoutId)`
67
+ - `getWorkoutFitUrl(athleteId, workoutId)` — constructs the FIT download URL;
68
+ the caller remains responsible for making an authenticated download request.
69
+ - `getPerformanceManagement(athleteId, startDate, endDate, options?)`
70
+ - `getPersonalRecords(athleteId, sport, recordType, startDate?, endDate?)`,
71
+ where `sport` is `"Bike"` or `"Run"`.
72
+ - `getCalendarNotes(athleteId, startDate, endDate)`
73
+ - `getWorkoutAnalysis(workoutId, athleteId)`
74
+
75
+ Supported deep imports:
76
+
77
+ - `@dofek/trainingpeaks/client` — client class.
78
+ - `@dofek/trainingpeaks/parsing` — workout and Performance Management Chart
79
+ parsers plus decimal-hour conversion.
80
+ - `@dofek/trainingpeaks/sports` — sport mapping table and mapper.
81
+ - `@dofek/trainingpeaks/types` — raw token, workout, analysis, profile, record,
82
+ calendar-note, and chart interfaces.
83
+
84
+ ```ts
85
+ import { parseTrainingPeaksWorkout } from "@dofek/trainingpeaks/parsing";
86
+ import type { TrainingPeaksWorkout } from "@dofek/trainingpeaks/types";
87
+ ```
88
+
89
+ ## Authentication and persistence
90
+
91
+ The [current implementation](https://github.com/Asherlc/dofek/blob/main/packages/trainingpeaks-connect/src/client.ts)
92
+ does not accept a username and password or use embedded application
93
+ credentials. It exchanges the browser-created `Production_tpAuth` cookie for a
94
+ bearer access token.
95
+
96
+ Treat both values as password-equivalent secrets. Persist the latest cookie and
97
+ token expiry in encrypted storage. The package does not refresh bearer tokens
98
+ directly: refresh the saved cookie, exchange it for a new token, replace the
99
+ stored cookie, and construct a new client. If cookie refresh fails because the
100
+ browser session is no longer accepted, log in through the TrainingPeaks site
101
+ again and capture a new cookie.
102
+
103
+ ## Request constraints
104
+
105
+ The currently observed workout endpoint accepts at most 90 days per
106
+ `getWorkouts` call, as recorded alongside the
107
+ [implemented method](https://github.com/Asherlc/dofek/blob/main/packages/trainingpeaks-connect/src/client.ts).
108
+ The client does not validate or split longer spans, so callers performing
109
+ historical syncs must divide them into windows of 90 days or less.
110
+
111
+ Each client instance targets a minimum 150 ms interval between its API calls.
112
+ This is local pacing, not a guarantee against upstream throttling; coordinate
113
+ concurrent clients separately.
114
+
115
+ ## Rate limits and errors
116
+
117
+ The shared
118
+ [rate-limit wrapper](https://github.com/Asherlc/dofek/blob/main/packages/provider-http/src/rate-limit.ts)
119
+ throws `ProviderRateLimitError` for `429` and
120
+ `ProviderServiceUnavailableError` for `502`, `503`, and `504`. Both expose
121
+ `providerId`, `statusCode`, `responseBody`, and `retryAfterSeconds`. The latter
122
+ follows the HTTP
123
+ [`Retry-After`](https://www.rfc-editor.org/rfc/rfc9110.html#name-retry-after)
124
+ header when present. Other unsuccessful responses throw a regular `Error`
125
+ containing the response status and body.
126
+
127
+ If your application handles these error classes directly, declare
128
+ `@dofek/provider-http` as a direct dependency:
129
+
130
+ ```sh
131
+ npm install @dofek/provider-http
132
+ ```
133
+
134
+ ```ts
135
+ import {
136
+ ProviderRateLimitError,
137
+ ProviderServiceUnavailableError,
138
+ } from "@dofek/provider-http/rate-limit";
139
+
140
+ try {
141
+ await client.getUser();
142
+ } catch (error) {
143
+ if (
144
+ error instanceof ProviderRateLimitError ||
145
+ error instanceof ProviderServiceUnavailableError
146
+ ) {
147
+ console.error(error.providerId, error.statusCode, error.retryAfterSeconds);
148
+ }
149
+ throw error;
150
+ }
151
+ ```
152
+
153
+ ## Project
154
+
155
+ - [Source](https://github.com/Asherlc/dofek/tree/main/packages/trainingpeaks-connect)
156
+ - [Report an issue](https://github.com/Asherlc/dofek/issues)
157
+ - [Contribute a pull request](https://github.com/Asherlc/dofek/pulls)
158
+ - License: [MIT](./LICENSE)
@@ -0,0 +1,51 @@
1
+ import type { TrainingPeaksCalendarNote, TrainingPeaksPersonalRecord, TrainingPeaksPmcEntry, TrainingPeaksPmcRequest, TrainingPeaksUser, TrainingPeaksWorkout, TrainingPeaksWorkoutAnalysis } from "./types.ts";
2
+ /**
3
+ * Exchange a Production_tpAuth cookie for a Bearer access token.
4
+ *
5
+ * The cookie must be obtained by logging into app.trainingpeaks.com
6
+ * in a browser and copying the `Production_tpAuth` cookie value.
7
+ */
8
+ declare function exchangeCookieForToken(cookie: string, fetchFn?: typeof globalThis.fetch): Promise<{
9
+ accessToken: string;
10
+ expiresIn: number;
11
+ }>;
12
+ /**
13
+ * Refresh the Production_tpAuth cookie.
14
+ * Returns the new cookie value from the Set-Cookie header.
15
+ */
16
+ declare function refreshCookie(cookie: string, fetchFn?: typeof globalThis.fetch): Promise<string>;
17
+ export declare class TrainingPeaksConnectClient {
18
+ #private;
19
+ constructor(accessToken: string, fetchFn?: typeof globalThis.fetch);
20
+ static exchangeCookieForToken: typeof exchangeCookieForToken;
21
+ static refreshCookie: typeof refreshCookie;
22
+ getUser(): Promise<TrainingPeaksUser>;
23
+ /**
24
+ * Get workouts in a date range. Max 90 days per request.
25
+ * Dates in YYYY-MM-DD format.
26
+ */
27
+ getWorkouts(athleteId: number, startDate: string, endDate: string): Promise<TrainingPeaksWorkout[]>;
28
+ /** Get a single workout by ID. */
29
+ getWorkout(athleteId: number, workoutId: number): Promise<TrainingPeaksWorkout>;
30
+ /** Get FIT file download URL for a workout. */
31
+ getWorkoutFitUrl(athleteId: number, workoutId: number): string;
32
+ /**
33
+ * Get CTL/ATL/TSB (fitness/fatigue/form) data for a date range.
34
+ * This is the Performance Management Chart data.
35
+ */
36
+ getPerformanceManagement(athleteId: number, startDate: string, endDate: string, options?: Partial<TrainingPeaksPmcRequest>): Promise<TrainingPeaksPmcEntry[]>;
37
+ /**
38
+ * Get personal records for a sport type.
39
+ * @param sport - "Bike" or "Run"
40
+ * @param recordType - e.g., "power5sec", "power20min", "speed5K"
41
+ */
42
+ getPersonalRecords(athleteId: number, sport: "Bike" | "Run", recordType: string, startDate?: string, endDate?: string): Promise<TrainingPeaksPersonalRecord[]>;
43
+ getCalendarNotes(athleteId: number, startDate: string, endDate: string): Promise<TrainingPeaksCalendarNote[]>;
44
+ /**
45
+ * Get detailed workout analysis with time-series data, zones, and laps.
46
+ * Uses the separate analysis API at api.peakswaresb.com.
47
+ */
48
+ getWorkoutAnalysis(workoutId: number, athleteId: number): Promise<TrainingPeaksWorkoutAnalysis>;
49
+ }
50
+ export {};
51
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAC3B,qBAAqB,EACrB,uBAAuB,EAEvB,iBAAiB,EACjB,oBAAoB,EACpB,4BAA4B,EAC7B,MAAM,YAAY,CAAC;AAkBpB;;;;;GAKG;AACH,iBAAe,sBAAsB,CACnC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAsBrD;AAED;;;GAGG;AACH,iBAAe,aAAa,CAC1B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC,MAAM,CAAC,CAsBjB;AAMD,qBAAa,0BAA0B;;gBAKzB,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB;IAOpF,MAAM,CAAC,sBAAsB,gCAA0B;IACvD,MAAM,CAAC,aAAa,uBAAiB;IAiE/B,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAM3C;;;OAGG;IACG,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAOlC,kCAAkC;IAC5B,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAOrF,+CAA+C;IAC/C,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAM9D;;;OAGG;IACG,wBAAwB,CAC5B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC7C,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAkBnC;;;;OAIG;IACG,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GAAG,KAAK,EACrB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,2BAA2B,EAAE,CAAC;IAcnC,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,yBAAyB,EAAE,CAAC;IASvC;;;OAGG;IACG,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,4BAA4B,CAAC;CAOzC"}
package/dist/client.js ADDED
@@ -0,0 +1,190 @@
1
+ import { createRateLimitAwareFetch } from "@dofek/provider-http/rate-limit";
2
+ // ============================================================
3
+ // TrainingPeaks internal API URLs
4
+ // ============================================================
5
+ const TP_API_BASE = "https://tpapi.trainingpeaks.com";
6
+ const TP_ANALYSIS_BASE = "https://api.peakswaresb.com";
7
+ const TP_HOME_BASE = "https://home.trainingpeaks.com";
8
+ const TP_APP_ORIGIN = "https://app.trainingpeaks.com";
9
+ /** Minimum delay between requests (ms) to avoid rate limiting */
10
+ const REQUEST_DELAY_MS = 150;
11
+ // ============================================================
12
+ // Authentication
13
+ // ============================================================
14
+ /**
15
+ * Exchange a Production_tpAuth cookie for a Bearer access token.
16
+ *
17
+ * The cookie must be obtained by logging into app.trainingpeaks.com
18
+ * in a browser and copying the `Production_tpAuth` cookie value.
19
+ */
20
+ async function exchangeCookieForToken(cookie, fetchFn = globalThis.fetch) {
21
+ const rateLimitFetchFn = createRateLimitAwareFetch(fetchFn, { providerId: "trainingpeaks" });
22
+ const response = await rateLimitFetchFn(`${TP_API_BASE}/users/v3/token`, {
23
+ headers: {
24
+ Cookie: `Production_tpAuth=${cookie}`,
25
+ },
26
+ });
27
+ if (!response.ok) {
28
+ const text = await response.text();
29
+ throw new Error(`TrainingPeaks token exchange failed (${response.status}): ${text}`);
30
+ }
31
+ const data = await response.json();
32
+ if (!data.success) {
33
+ throw new Error("TrainingPeaks token exchange returned success=false");
34
+ }
35
+ return {
36
+ accessToken: data.token.access_token,
37
+ expiresIn: data.token.expires_in,
38
+ };
39
+ }
40
+ /**
41
+ * Refresh the Production_tpAuth cookie.
42
+ * Returns the new cookie value from the Set-Cookie header.
43
+ */
44
+ async function refreshCookie(cookie, fetchFn = globalThis.fetch) {
45
+ const rateLimitFetchFn = createRateLimitAwareFetch(fetchFn, { providerId: "trainingpeaks" });
46
+ const response = await rateLimitFetchFn(`${TP_HOME_BASE}/refresh`, {
47
+ headers: {
48
+ Cookie: `Production_tpAuth=${cookie}`,
49
+ },
50
+ redirect: "manual",
51
+ });
52
+ const setCookies = "getSetCookie" in response.headers && typeof response.headers.getSetCookie === "function"
53
+ ? response.headers.getSetCookie()
54
+ : (response.headers.get("set-cookie")?.split(/,(?=\s*\w+=)/) ?? []);
55
+ for (const header of setCookies) {
56
+ if (header.includes("Production_tpAuth=")) {
57
+ const match = header.match(/Production_tpAuth=([^;]+)/);
58
+ if (match?.[1])
59
+ return match[1];
60
+ }
61
+ }
62
+ throw new Error("TrainingPeaks cookie refresh did not return a new Production_tpAuth cookie");
63
+ }
64
+ // ============================================================
65
+ // TrainingPeaks Connect Client
66
+ // ============================================================
67
+ export class TrainingPeaksConnectClient {
68
+ #accessToken;
69
+ #fetchFn;
70
+ #lastRequestTime = 0;
71
+ constructor(accessToken, fetchFn = globalThis.fetch) {
72
+ this.#accessToken = accessToken;
73
+ this.#fetchFn = createRateLimitAwareFetch(fetchFn, { providerId: "trainingpeaks" });
74
+ }
75
+ // ---- Static auth methods ----
76
+ static exchangeCookieForToken = exchangeCookieForToken;
77
+ static refreshCookie = refreshCookie;
78
+ // ---- Private helpers ----
79
+ async #throttle() {
80
+ const now = Date.now();
81
+ const elapsed = now - this.#lastRequestTime;
82
+ if (elapsed < REQUEST_DELAY_MS) {
83
+ await new Promise((resolve) => setTimeout(resolve, REQUEST_DELAY_MS - elapsed));
84
+ }
85
+ this.#lastRequestTime = Date.now();
86
+ }
87
+ async #get(base, path, params) {
88
+ await this.#throttle();
89
+ const url = new URL(`${base}${path}`);
90
+ if (params) {
91
+ for (const [key, value] of Object.entries(params)) {
92
+ url.searchParams.set(key, value);
93
+ }
94
+ }
95
+ const response = await this.#fetchFn(url.toString(), {
96
+ headers: {
97
+ Authorization: `Bearer ${this.#accessToken}`,
98
+ Accept: "application/json",
99
+ Origin: TP_APP_ORIGIN,
100
+ },
101
+ });
102
+ if (!response.ok) {
103
+ const text = await response.text();
104
+ throw new Error(`TrainingPeaks API error (${response.status}): ${text}`);
105
+ }
106
+ const result = await response.json();
107
+ return result;
108
+ }
109
+ async #post(base, path, body) {
110
+ await this.#throttle();
111
+ const response = await this.#fetchFn(`${base}${path}`, {
112
+ method: "POST",
113
+ headers: {
114
+ Authorization: `Bearer ${this.#accessToken}`,
115
+ "Content-Type": "application/json",
116
+ Accept: "application/json",
117
+ Origin: TP_APP_ORIGIN,
118
+ },
119
+ body: JSON.stringify(body),
120
+ });
121
+ if (!response.ok) {
122
+ const text = await response.text();
123
+ throw new Error(`TrainingPeaks API error (${response.status}): ${text}`);
124
+ }
125
+ const result = await response.json();
126
+ return result;
127
+ }
128
+ // ---- User profile ----
129
+ async getUser() {
130
+ return this.#get(TP_API_BASE, "/users/v3/user");
131
+ }
132
+ // ---- Workouts ----
133
+ /**
134
+ * Get workouts in a date range. Max 90 days per request.
135
+ * Dates in YYYY-MM-DD format.
136
+ */
137
+ async getWorkouts(athleteId, startDate, endDate) {
138
+ return this.#get(TP_API_BASE, `/fitness/v6/athletes/${athleteId}/workouts/${startDate}/${endDate}`);
139
+ }
140
+ /** Get a single workout by ID. */
141
+ async getWorkout(athleteId, workoutId) {
142
+ return this.#get(TP_API_BASE, `/fitness/v6/athletes/${athleteId}/workouts/${workoutId}`);
143
+ }
144
+ /** Get FIT file download URL for a workout. */
145
+ getWorkoutFitUrl(athleteId, workoutId) {
146
+ return `${TP_API_BASE}/fitness/v6/athletes/${athleteId}/workouts/${workoutId}/fordevice/fit`;
147
+ }
148
+ // ---- Performance Management Chart ----
149
+ /**
150
+ * Get CTL/ATL/TSB (fitness/fatigue/form) data for a date range.
151
+ * This is the Performance Management Chart data.
152
+ */
153
+ async getPerformanceManagement(athleteId, startDate, endDate, options = {}) {
154
+ const body = {
155
+ atlConstant: options.atlConstant ?? 7,
156
+ atlStart: options.atlStart ?? 0,
157
+ ctlConstant: options.ctlConstant ?? 42,
158
+ ctlStart: options.ctlStart ?? 0,
159
+ workoutTypes: options.workoutTypes ?? [],
160
+ };
161
+ return this.#post(TP_API_BASE, `/fitness/v1/athletes/${athleteId}/reporting/performancedata/${startDate}/${endDate}`, body);
162
+ }
163
+ // ---- Personal Records ----
164
+ /**
165
+ * Get personal records for a sport type.
166
+ * @param sport - "Bike" or "Run"
167
+ * @param recordType - e.g., "power5sec", "power20min", "speed5K"
168
+ */
169
+ async getPersonalRecords(athleteId, sport, recordType, startDate, endDate) {
170
+ const params = { prType: recordType };
171
+ if (startDate)
172
+ params.startDate = startDate;
173
+ if (endDate)
174
+ params.endDate = endDate;
175
+ return this.#get(TP_API_BASE, `/personalrecord/v2/athletes/${athleteId}/${sport}`, params);
176
+ }
177
+ // ---- Calendar Notes ----
178
+ async getCalendarNotes(athleteId, startDate, endDate) {
179
+ return this.#get(TP_API_BASE, `/fitness/v1/athletes/${athleteId}/calendarNote/${startDate}/${endDate}`);
180
+ }
181
+ // ---- Workout Analysis ----
182
+ /**
183
+ * Get detailed workout analysis with time-series data, zones, and laps.
184
+ * Uses the separate analysis API at api.peakswaresb.com.
185
+ */
186
+ async getWorkoutAnalysis(workoutId, athleteId) {
187
+ return this.#post(TP_ANALYSIS_BASE, "/workout-analysis/v1/analyze", { workoutId, viewingPersonId: athleteId });
188
+ }
189
+ }
190
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { CanonicalActivityType } from "@dofek/training/training";
2
+ import type { TrainingPeaksPmcEntry, TrainingPeaksWorkout } from "./types.ts";
3
+ export interface ParsedWorkout {
4
+ externalId: string;
5
+ activityType: CanonicalActivityType;
6
+ name: string;
7
+ startedAt: Date;
8
+ endedAt: Date;
9
+ completed: boolean;
10
+ distanceMeters?: number;
11
+ durationSeconds?: number;
12
+ averageHeartRate?: number;
13
+ maxHeartRate?: number;
14
+ averagePower?: number;
15
+ normalizedPower?: number;
16
+ cadenceAverage?: number;
17
+ elevationGain?: number;
18
+ trainingStressScore?: number;
19
+ intensityFactor?: number;
20
+ feeling?: number;
21
+ rpe?: number;
22
+ raw: TrainingPeaksWorkout;
23
+ }
24
+ export interface ParsedPerformanceManagement {
25
+ date: string;
26
+ tss: number;
27
+ fitness: number;
28
+ fatigue: number;
29
+ form: number;
30
+ }
31
+ /**
32
+ * Convert TrainingPeaks decimal hours to seconds.
33
+ * TrainingPeaks stores totalTime as decimal hours (e.g., 1.25 = 1h15m).
34
+ */
35
+ export declare function decimalHoursToSeconds(hours: number): number;
36
+ /** Parse a TrainingPeaks workout into our normalized format. */
37
+ export declare function parseTrainingPeaksWorkout(raw: TrainingPeaksWorkout): ParsedWorkout;
38
+ /** Parse a PMC data point into our normalized format. */
39
+ export declare function parseTrainingPeaksPmc(entry: TrainingPeaksPmcEntry): ParsedPerformanceManagement;
40
+ //# sourceMappingURL=parsing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parsing.d.ts","sourceRoot":"","sources":["../src/parsing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAM9E,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,qBAAqB,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,oBAAoB,CAAC;CAC3B;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAMD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,gEAAgE;AAChE,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,oBAAoB,GAAG,aAAa,CA+BlF;AAED,yDAAyD;AACzD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,qBAAqB,GAAG,2BAA2B,CAQ/F"}
@@ -0,0 +1,53 @@
1
+ import { mapTrainingPeaksSport } from "./sports.js";
2
+ // ============================================================
3
+ // Parsing functions
4
+ // ============================================================
5
+ /**
6
+ * Convert TrainingPeaks decimal hours to seconds.
7
+ * TrainingPeaks stores totalTime as decimal hours (e.g., 1.25 = 1h15m).
8
+ */
9
+ export function decimalHoursToSeconds(hours) {
10
+ return Math.round(hours * 3600);
11
+ }
12
+ /** Parse a TrainingPeaks workout into our normalized format. */
13
+ export function parseTrainingPeaksWorkout(raw) {
14
+ // Use startTime if available, fall back to workoutDay
15
+ const startTimeStr = raw.startTime ?? raw.startTimePlanned ?? raw.workoutDay;
16
+ const startedAt = new Date(startTimeStr);
17
+ // Calculate end time from actual or planned duration
18
+ const totalTimeHours = raw.totalTime ?? raw.totalTimePlanned ?? 0;
19
+ const durationSeconds = decimalHoursToSeconds(totalTimeHours);
20
+ const endedAt = new Date(startedAt.getTime() + durationSeconds * 1000);
21
+ return {
22
+ externalId: String(raw.workoutId),
23
+ activityType: mapTrainingPeaksSport(raw.workoutTypeFamilyId),
24
+ name: raw.title,
25
+ startedAt,
26
+ endedAt,
27
+ completed: raw.completed,
28
+ distanceMeters: raw.distance,
29
+ durationSeconds: durationSeconds > 0 ? durationSeconds : undefined,
30
+ averageHeartRate: raw.heartRateAverage,
31
+ maxHeartRate: raw.heartRateMaximum,
32
+ averagePower: raw.powerAverage,
33
+ normalizedPower: raw.normalizedPowerActual,
34
+ cadenceAverage: raw.cadenceAverage,
35
+ elevationGain: raw.elevationGain,
36
+ trainingStressScore: raw.tssActual ?? raw.tssPlanned,
37
+ intensityFactor: raw.if ?? raw.ifPlanned,
38
+ feeling: raw.feeling,
39
+ rpe: raw.rpe,
40
+ raw,
41
+ };
42
+ }
43
+ /** Parse a PMC data point into our normalized format. */
44
+ export function parseTrainingPeaksPmc(entry) {
45
+ return {
46
+ date: entry.workoutDay,
47
+ tss: entry.tssActual,
48
+ fitness: entry.ctl,
49
+ fatigue: entry.atl,
50
+ form: entry.tsb,
51
+ };
52
+ }
53
+ //# sourceMappingURL=parsing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parsing.js","sourceRoot":"","sources":["../src/parsing.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAqCpD,+DAA+D;AAC/D,oBAAoB;AACpB,+DAA+D;AAE/D;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,yBAAyB,CAAC,GAAyB;IACjE,sDAAsD;IACtD,MAAM,YAAY,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,gBAAgB,IAAI,GAAG,CAAC,UAAU,CAAC;IAC7E,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC;IAEzC,qDAAqD;IACrD,MAAM,cAAc,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,gBAAgB,IAAI,CAAC,CAAC;IAClE,MAAM,eAAe,GAAG,qBAAqB,CAAC,cAAc,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,eAAe,GAAG,IAAI,CAAC,CAAC;IAEvE,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;QACjC,YAAY,EAAE,qBAAqB,CAAC,GAAG,CAAC,mBAAmB,CAAC;QAC5D,IAAI,EAAE,GAAG,CAAC,KAAK;QACf,SAAS;QACT,OAAO;QACP,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,cAAc,EAAE,GAAG,CAAC,QAAQ;QAC5B,eAAe,EAAE,eAAe,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS;QAClE,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;QACtC,YAAY,EAAE,GAAG,CAAC,gBAAgB;QAClC,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,eAAe,EAAE,GAAG,CAAC,qBAAqB;QAC1C,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,aAAa,EAAE,GAAG,CAAC,aAAa;QAChC,mBAAmB,EAAE,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,UAAU;QACpD,eAAe,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,SAAS;QACxC,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,GAAG;KACJ,CAAC;AACJ,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,qBAAqB,CAAC,KAA4B;IAChE,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,UAAU;QACtB,GAAG,EAAE,KAAK,CAAC,SAAS;QACpB,OAAO,EAAE,KAAK,CAAC,GAAG;QAClB,OAAO,EAAE,KAAK,CAAC,GAAG;QAClB,IAAI,EAAE,KAAK,CAAC,GAAG;KAChB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { CanonicalActivityType } from "@dofek/training/training";
2
+ /**
3
+ * TrainingPeaks workout type family IDs → normalized sport type.
4
+ */
5
+ export declare const TRAINING_PEAKS_SPORT_MAP: Record<number, CanonicalActivityType>;
6
+ /**
7
+ * Map a TrainingPeaks workoutTypeFamilyId to a normalized sport type.
8
+ */
9
+ export declare function mapTrainingPeaksSport(familyId: number): CanonicalActivityType;
10
+ //# sourceMappingURL=sports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sports.d.ts","sourceRoot":"","sources":["../src/sports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAc1E,CAAC;AAEF;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAE7E"}
package/dist/sports.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * TrainingPeaks workout type family IDs → normalized sport type.
3
+ */
4
+ export const TRAINING_PEAKS_SPORT_MAP = {
5
+ 1: "swimming",
6
+ 2: "cycling",
7
+ 3: "running",
8
+ 4: "walking",
9
+ 5: "rowing",
10
+ 6: "skiing",
11
+ 7: "strength",
12
+ 8: "yoga",
13
+ 9: "hiking",
14
+ 10: "other",
15
+ 11: "triathlon",
16
+ 12: "other",
17
+ 13: "cardio",
18
+ };
19
+ /**
20
+ * Map a TrainingPeaks workoutTypeFamilyId to a normalized sport type.
21
+ */
22
+ export function mapTrainingPeaksSport(familyId) {
23
+ return TRAINING_PEAKS_SPORT_MAP[familyId] ?? "other";
24
+ }
25
+ //# sourceMappingURL=sports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sports.js","sourceRoot":"","sources":["../src/sports.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA0C;IAC7E,CAAC,EAAE,UAAU;IACb,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,QAAQ;IACX,CAAC,EAAE,QAAQ;IACX,CAAC,EAAE,UAAU;IACb,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,QAAQ;IACX,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,WAAW;IACf,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,QAAQ;CACb,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,OAAO,wBAAwB,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC;AACvD,CAAC"}
@@ -0,0 +1,138 @@
1
+ /** Token response from /users/v3/token */
2
+ export interface TrainingPeaksTokenResponse {
3
+ success: boolean;
4
+ token: {
5
+ access_token: string;
6
+ expires_in: number;
7
+ };
8
+ }
9
+ /** User profile from /users/v3/user */
10
+ export interface TrainingPeaksUser {
11
+ user: {
12
+ personId: number;
13
+ firstName: string;
14
+ lastName: string;
15
+ email: string;
16
+ athletes: Array<{
17
+ athleteId: number;
18
+ }>;
19
+ settings: {
20
+ account: {
21
+ isPremium: boolean;
22
+ };
23
+ };
24
+ };
25
+ }
26
+ /** Workout from /fitness/v6/athletes/{id}/workouts */
27
+ export interface TrainingPeaksWorkout {
28
+ workoutId: number;
29
+ athleteId: number;
30
+ workoutDay: string;
31
+ title: string;
32
+ description?: string;
33
+ completed: boolean;
34
+ workoutTypeFamilyId: number;
35
+ workoutTypeValueId: number;
36
+ totalTimePlanned?: number;
37
+ totalTime?: number;
38
+ distancePlanned?: number;
39
+ distance?: number;
40
+ tssPlanned?: number;
41
+ tssActual?: number;
42
+ tssCalculationMethod?: string;
43
+ ifPlanned?: number;
44
+ if?: number;
45
+ powerAverage?: number;
46
+ normalizedPowerActual?: number;
47
+ heartRateAverage?: number;
48
+ heartRateMaximum?: number;
49
+ cadenceAverage?: number;
50
+ elevationGain?: number;
51
+ elevationLoss?: number;
52
+ calories?: number;
53
+ feeling?: number;
54
+ rpe?: number;
55
+ coachComments?: string;
56
+ athleteComments?: string;
57
+ startTime?: string;
58
+ startTimePlanned?: string;
59
+ lastModifiedDate?: string;
60
+ tags?: string[];
61
+ workoutFileFormats?: string[];
62
+ }
63
+ /** Performance Management Chart data point */
64
+ export interface TrainingPeaksPmcEntry {
65
+ workoutDay: string;
66
+ tssActual: number;
67
+ ctl: number;
68
+ atl: number;
69
+ tsb: number;
70
+ }
71
+ /** PMC request body */
72
+ export interface TrainingPeaksPmcRequest {
73
+ atlConstant: number;
74
+ atlStart: number;
75
+ ctlConstant: number;
76
+ ctlStart: number;
77
+ workoutTypes: number[];
78
+ }
79
+ /** Personal record entry */
80
+ export interface TrainingPeaksPersonalRecord {
81
+ workoutId: number;
82
+ workoutDay: string;
83
+ title: string;
84
+ value: number;
85
+ rank: number;
86
+ duration?: number;
87
+ distance?: number;
88
+ }
89
+ /** Calendar note */
90
+ export interface TrainingPeaksCalendarNote {
91
+ id: number;
92
+ athleteId: number;
93
+ date: string;
94
+ title: string;
95
+ description?: string;
96
+ }
97
+ /** Workout analysis response from api.peakswaresb.com */
98
+ export interface TrainingPeaksWorkoutAnalysis {
99
+ totals: {
100
+ duration?: number;
101
+ distance?: number;
102
+ calories?: number;
103
+ tss?: number;
104
+ normalizedPower?: number;
105
+ intensityFactor?: number;
106
+ averagePower?: number;
107
+ averageHeartRate?: number;
108
+ averageCadence?: number;
109
+ elevationGain?: number;
110
+ work?: number;
111
+ };
112
+ channels?: TrainingPeaksDataChannel[];
113
+ laps?: TrainingPeaksLapData[];
114
+ }
115
+ /** Time-series data channel */
116
+ export interface TrainingPeaksDataChannel {
117
+ name: string;
118
+ min: number;
119
+ max: number;
120
+ avg: number;
121
+ zones?: Array<{
122
+ start: number;
123
+ end: number;
124
+ duration: number;
125
+ }>;
126
+ }
127
+ /** Lap data */
128
+ export interface TrainingPeaksLapData {
129
+ lapNumber: number;
130
+ duration: number;
131
+ distance?: number;
132
+ averagePower?: number;
133
+ normalizedPower?: number;
134
+ averageHeartRate?: number;
135
+ averageCadence?: number;
136
+ elevationGain?: number;
137
+ }
138
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAKA,0CAA0C;AAC1C,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,uCAAuC;AACvC,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,KAAK,CAAC;YAAE,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACvC,QAAQ,EAAE;YACR,OAAO,EAAE;gBACP,SAAS,EAAE,OAAO,CAAC;aACpB,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED,sDAAsD;AACtD,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAG3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAG9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IAGZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAG/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,8CAA8C;AAC9C,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,uBAAuB;AACvB,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,4BAA4B;AAC5B,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,oBAAoB;AACpB,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,yDAAyD;AACzD,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE;QACN,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,QAAQ,CAAC,EAAE,wBAAwB,EAAE,CAAC;IACtC,IAAI,CAAC,EAAE,oBAAoB,EAAE,CAAC;CAC/B;AAED,+BAA+B;AAC/B,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;CACJ;AAED,eAAe;AACf,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB"}
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ // ============================================================
2
+ // TrainingPeaks internal API response types
3
+ // Reverse-engineered from app.trainingpeaks.com
4
+ // ============================================================
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,4CAA4C;AAC5C,gDAAgD;AAChD,+DAA+D"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@dofek/trainingpeaks",
3
+ "version": "0.1.0",
4
+ "description": "Unofficial TrainingPeaks internal API client using cookie-based authentication",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Asherlc/dofek.git",
10
+ "directory": "packages/trainingpeaks-connect"
11
+ },
12
+ "homepage": "https://github.com/Asherlc/dofek/tree/main/packages/trainingpeaks-connect#readme",
13
+ "bugs": "https://github.com/Asherlc/dofek/issues",
14
+ "keywords": [
15
+ "trainingpeaks",
16
+ "cycling",
17
+ "running",
18
+ "api-client",
19
+ "unofficial"
20
+ ],
21
+ "engines": {
22
+ "node": ">=22.14.0"
23
+ },
24
+ "sideEffects": false,
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/client.d.ts",
33
+ "import": "./dist/client.js"
34
+ },
35
+ "./*": {
36
+ "types": "./dist/*.d.ts",
37
+ "import": "./dist/*.js"
38
+ }
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "scripts": {
44
+ "test": "vitest run",
45
+ "test:watch": "vitest --watch",
46
+ "build": "tsc",
47
+ "prepack": "pnpm run build",
48
+ "lint": "biome check .",
49
+ "typecheck": "tsc --noEmit"
50
+ },
51
+ "dependencies": {
52
+ "@dofek/provider-http": "0.1.0",
53
+ "@dofek/training": "0.1.0"
54
+ },
55
+ "devDependencies": {
56
+ "typescript": "6.0.3",
57
+ "vitest": "3.2.6"
58
+ },
59
+ "gitHead": "ac95258c753c7e75b57da7ae47426d22b78e8c01"
60
+ }