@dlwiest/ts-tonal-client 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +28 -0
  2. package/dist/{index.js → index.cjs} +140 -72
  3. package/dist/index.d.ts +13 -7
  4. package/dist/index.esm.js +140 -72
  5. package/package.json +5 -5
  6. package/dist/examples/create-workout.d.ts +0 -1
  7. package/dist/examples/debug-daily-lifts.d.ts +0 -1
  8. package/dist/examples/delete-workout.d.ts +0 -1
  9. package/dist/examples/edit-workout.d.ts +0 -1
  10. package/dist/examples/estimate-workout.d.ts +0 -1
  11. package/dist/examples/get-achievement-stats.d.ts +0 -2
  12. package/dist/examples/get-achievements.d.ts +0 -2
  13. package/dist/examples/get-activity-summaries.d.ts +0 -2
  14. package/dist/examples/get-current-streak.d.ts +0 -1
  15. package/dist/examples/get-daily-lifts.d.ts +0 -1
  16. package/dist/examples/get-daily-metrics.d.ts +0 -1
  17. package/dist/examples/get-goal-metrics.d.ts +0 -1
  18. package/dist/examples/get-goals.d.ts +0 -1
  19. package/dist/examples/get-home-calendar.d.ts +0 -2
  20. package/dist/examples/get-metric-scores.d.ts +0 -1
  21. package/dist/examples/get-movements.d.ts +0 -1
  22. package/dist/examples/get-muscle-readiness.d.ts +0 -1
  23. package/dist/examples/get-program-by-id.d.ts +0 -1
  24. package/dist/examples/get-target-scores.d.ts +0 -1
  25. package/dist/examples/get-training-effect-goals.d.ts +0 -1
  26. package/dist/examples/get-training-types.d.ts +0 -1
  27. package/dist/examples/get-user-info.d.ts +0 -1
  28. package/dist/examples/get-user-settings.d.ts +0 -1
  29. package/dist/examples/get-user-statistics.d.ts +0 -2
  30. package/dist/examples/get-user-workouts.d.ts +0 -1
  31. package/dist/examples/get-workout-by-id.d.ts +0 -1
  32. package/dist/examples/get-workout-by-name.d.ts +0 -1
  33. package/dist/examples/get-workout-by-share-url.d.ts +0 -1
  34. package/dist/examples/raw-daily-lifts-test.d.ts +0 -1
  35. package/dist/src/auth/auth-manager.d.ts +0 -15
  36. package/dist/src/client.d.ts +0 -40
  37. package/dist/src/http/http-client.d.ts +0 -12
  38. package/dist/src/index.d.ts +0 -4
  39. package/dist/src/services/movement-service.d.ts +0 -9
  40. package/dist/src/services/user-service.d.ts +0 -51
  41. package/dist/src/services/workout-service.d.ts +0 -14
  42. package/dist/src/types/auth.d.ts +0 -18
  43. package/dist/src/types/common.d.ts +0 -1
  44. package/dist/src/types/index.d.ts +0 -6
  45. package/dist/src/types/movements.d.ts +0 -30
  46. package/dist/src/types/programs.d.ts +0 -46
  47. package/dist/src/types/users.d.ts +0 -418
  48. package/dist/src/types/workouts.d.ts +0 -120
  49. package/dist/src/types.d.ts +0 -125
  50. package/dist/src/utils/cache-manager.d.ts +0 -11
package/dist/index.esm.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import fs from 'fs';
2
+ import os from 'os';
2
3
  import path from 'path';
3
4
 
4
5
  class TonalClientError extends Error {
@@ -15,9 +16,11 @@ class AuthManager {
15
16
  this.idToken = '';
16
17
  this.refreshToken = '';
17
18
  this.tokenExpiresAt = 0;
18
- this.isRefreshing = false;
19
+ this.authPromise = null;
20
+ this.refreshPromise = null;
19
21
  this.authUrl = 'https://tonal.auth0.com/oauth/token';
20
22
  this.clientId = 'ERCyexW-xoVG_Yy3RDe-eV4xsOnRHP6L';
23
+ this.authTimeout = 30000;
21
24
  this.username = username;
22
25
  this.password = password;
23
26
  }
@@ -25,6 +28,18 @@ class AuthManager {
25
28
  if (this.isTokenValid()) {
26
29
  return this.idToken;
27
30
  }
31
+ const activeAuth = this.authPromise ?? this.authenticateWithPassword();
32
+ this.authPromise = activeAuth;
33
+ try {
34
+ return await activeAuth;
35
+ }
36
+ finally {
37
+ if (this.authPromise === activeAuth) {
38
+ this.authPromise = null;
39
+ }
40
+ }
41
+ }
42
+ async authenticateWithPassword() {
28
43
  const response = await fetch(this.authUrl, {
29
44
  method: 'POST',
30
45
  headers: {
@@ -37,6 +52,7 @@ class AuthManager {
37
52
  grant_type: 'password',
38
53
  scope: 'offline_access',
39
54
  }),
55
+ signal: AbortSignal.timeout(this.authTimeout),
40
56
  });
41
57
  if (!response.ok) {
42
58
  const errorText = await response.text();
@@ -51,69 +67,69 @@ class AuthManager {
51
67
  }
52
68
  const tokenData = await response.json();
53
69
  this.idToken = tokenData.id_token;
54
- this.refreshToken = tokenData.refresh_token;
70
+ this.refreshToken = tokenData.refresh_token ?? '';
55
71
  this.tokenExpiresAt = Date.now() + (tokenData.expires_in * 1000);
56
72
  return this.idToken;
57
73
  }
74
+ invalidateToken() {
75
+ this.tokenExpiresAt = 0;
76
+ }
58
77
  async getValidToken() {
59
78
  if (this.isTokenValid()) {
60
79
  return this.idToken;
61
80
  }
62
- if (this.refreshToken) {
63
- if (!this.isRefreshing) {
64
- await this.refreshTokens();
65
- }
66
- else {
67
- // Wait for ongoing refresh to complete
68
- while (this.isRefreshing) {
69
- await new Promise(resolve => setTimeout(resolve, 100));
70
- }
71
- }
72
- if (this.isTokenValid()) {
73
- return this.idToken;
81
+ if (!this.refreshToken) {
82
+ return this.authenticate();
83
+ }
84
+ const activeRefresh = this.refreshPromise ?? this.refreshTokens();
85
+ this.refreshPromise = activeRefresh;
86
+ try {
87
+ await activeRefresh;
88
+ }
89
+ catch {
90
+ return this.authenticate();
91
+ }
92
+ finally {
93
+ if (this.refreshPromise === activeRefresh) {
94
+ this.refreshPromise = null;
74
95
  }
75
96
  }
76
- throw new TonalClientError('Token expired and refresh failed. Call authenticate() first.');
97
+ if (this.isTokenValid()) {
98
+ return this.idToken;
99
+ }
100
+ return this.authenticate();
77
101
  }
78
102
  isTokenValid() {
79
103
  return !!this.idToken && Date.now() < this.tokenExpiresAt - 60000; // 1 minute buffer
80
104
  }
81
105
  async refreshTokens() {
82
- if (this.isRefreshing) {
83
- return; // Prevent concurrent refresh attempts
84
- }
85
- this.isRefreshing = true;
86
- try {
87
- const response = await fetch(this.authUrl, {
88
- method: 'POST',
89
- headers: {
90
- 'Content-Type': 'application/json',
91
- },
92
- body: JSON.stringify({
93
- client_id: this.clientId,
94
- grant_type: 'refresh_token',
95
- refresh_token: this.refreshToken,
96
- }),
97
- });
98
- if (!response.ok) {
99
- const errorText = await response.text();
100
- let errorData;
101
- try {
102
- errorData = JSON.parse(errorText);
103
- }
104
- catch {
105
- errorData = { error: errorText };
106
- }
107
- throw new TonalClientError(errorData.error_description || errorData.error || 'Token refresh failed', response.status, errorData);
106
+ const response = await fetch(this.authUrl, {
107
+ method: 'POST',
108
+ headers: {
109
+ 'Content-Type': 'application/json',
110
+ },
111
+ body: JSON.stringify({
112
+ client_id: this.clientId,
113
+ grant_type: 'refresh_token',
114
+ refresh_token: this.refreshToken,
115
+ }),
116
+ signal: AbortSignal.timeout(this.authTimeout),
117
+ });
118
+ if (!response.ok) {
119
+ const errorText = await response.text();
120
+ let errorData;
121
+ try {
122
+ errorData = JSON.parse(errorText);
108
123
  }
109
- const tokenData = await response.json();
110
- this.idToken = tokenData.id_token;
111
- this.refreshToken = tokenData.refresh_token;
112
- this.tokenExpiresAt = Date.now() + (tokenData.expires_in * 1000);
113
- }
114
- finally {
115
- this.isRefreshing = false;
124
+ catch {
125
+ errorData = { error: errorText };
126
+ }
127
+ throw new TonalClientError(errorData.error_description || errorData.error || 'Token refresh failed', response.status, errorData);
116
128
  }
129
+ const tokenData = await response.json();
130
+ this.idToken = tokenData.id_token;
131
+ this.refreshToken = tokenData.refresh_token ?? this.refreshToken;
132
+ this.tokenExpiresAt = Date.now() + (tokenData.expires_in * 1000);
117
133
  }
118
134
  }
119
135
 
@@ -129,10 +145,11 @@ class HttpClient {
129
145
  return this.makeRequestWithRetry(url, options, expectsBody);
130
146
  }
131
147
  async makeRequest(url, options = {}, expectsBody = true) {
132
- const controller = new AbortController();
133
- const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
148
+ let timeoutId;
134
149
  try {
135
150
  const token = await this.authManager.getValidToken();
151
+ const controller = new AbortController();
152
+ timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
136
153
  const response = await fetch(url, {
137
154
  ...options,
138
155
  headers: {
@@ -143,6 +160,7 @@ class HttpClient {
143
160
  signal: controller.signal,
144
161
  });
145
162
  clearTimeout(timeoutId);
163
+ timeoutId = undefined;
146
164
  if (!response.ok) {
147
165
  const errorText = await response.text();
148
166
  let errorData;
@@ -169,28 +187,35 @@ class HttpClient {
169
187
  if (error instanceof Error && error.name === 'AbortError') {
170
188
  throw new TonalClientError('Request timeout', undefined, error);
171
189
  }
172
- throw new TonalClientError('Request failed', undefined, error);
190
+ throw new TonalClientError(error instanceof Error ? error.message : 'Request failed', undefined, error);
173
191
  }
174
192
  }
175
193
  async makeRequestWithRetry(url, options = {}, expectsBody = true) {
176
194
  let lastError;
195
+ const canRetry = (options.method ?? 'GET').toUpperCase() === 'GET';
177
196
  for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
178
197
  try {
179
198
  return await this.makeRequest(url, options, expectsBody);
180
199
  }
181
200
  catch (error) {
182
201
  lastError = error instanceof TonalClientError ? error : new TonalClientError('Unknown error', undefined, error);
183
- // If it's an auth error on first attempt, try to refresh token and retry once
184
- if (attempt === 1 && lastError.statusCode && (lastError.statusCode === 401 || lastError.statusCode === 403)) {
202
+ // If it's an auth error on first attempt, invalidate the token and retry once
203
+ if (attempt === 1 && (lastError.statusCode === 401 || lastError.statusCode === 403)) {
185
204
  try {
186
- await this.authManager.getValidToken(); // This will refresh if needed
187
- continue; // Retry the request with the new token
205
+ this.authManager.invalidateToken();
206
+ await this.authManager.getValidToken();
207
+ continue;
188
208
  }
189
- catch (refreshError) {
209
+ catch {
190
210
  // If refresh fails, continue with normal retry logic
191
211
  }
192
212
  }
193
- if (attempt === this.maxRetries || (lastError.statusCode && lastError.statusCode < 500)) {
213
+ const isAbort = lastError.originalError instanceof Error &&
214
+ lastError.originalError.name === 'AbortError';
215
+ if (attempt === this.maxRetries ||
216
+ !canRetry ||
217
+ (lastError.statusCode !== undefined && lastError.statusCode < 500) ||
218
+ (lastError.statusCode === undefined && !isAbort)) {
194
219
  throw lastError;
195
220
  }
196
221
  const delay = Math.pow(2, attempt - 1) * 1000;
@@ -219,6 +244,9 @@ class WorkoutService {
219
244
  }
220
245
  async getDailyLifts(userInfo, timeZone) {
221
246
  const device = userInfo.recentMobileDevice;
247
+ if (!device) {
248
+ throw new TonalClientError('Recent mobile device information is unavailable; daily lifts require a mobile device');
249
+ }
222
250
  const userAgent = device.platform === 'ios'
223
251
  ? `Tonal/3004226 CFNetwork/3860.100.1 Darwin/${device.osVersion}`
224
252
  : `Tonal/${device.appVersion}`;
@@ -255,8 +283,8 @@ class WorkoutService {
255
283
  if (!shareUrl?.trim()) {
256
284
  throw new TonalClientError('Share URL is required');
257
285
  }
258
- const urlPattern = /https:\/\/share\.tonal\.com\/workout\/([a-f0-9-]+)/;
259
- const match = shareUrl.match(urlPattern);
286
+ const urlPattern = /^https:\/\/share\.tonal\.com\/workout\/([a-fA-F0-9-]+)(?:[?#]\S*)?$/;
287
+ const match = shareUrl.trim().match(urlPattern);
260
288
  if (!match) {
261
289
  throw new TonalClientError('Invalid share URL format. Expected: https://share.tonal.com/workout/{id}');
262
290
  }
@@ -301,9 +329,16 @@ class WorkoutService {
301
329
  if (!workoutData.sets?.length) {
302
330
  throw new TonalClientError('At least one set is required');
303
331
  }
332
+ // Tonal's PUT behavior is unverified. Omitting an absent shortDescription is equivalent
333
+ // under full-replace semantics and avoids an unintended clear under merge semantics;
334
+ // an explicit empty string intentionally clears it. `!= null` also covers null, which
335
+ // sibling response fields are known to return despite their non-optional types.
304
336
  const requestBody = {
305
337
  id: workoutData.id,
306
338
  title: workoutData.title,
339
+ ...(workoutData.shortDescription != null
340
+ ? { shortDescription: workoutData.shortDescription }
341
+ : {}),
307
342
  description: workoutData.description || '',
308
343
  coachId: workoutData.coachId || '00000000-0000-0000-0000-000000000000',
309
344
  sets: workoutData.sets,
@@ -327,10 +362,11 @@ class WorkoutService {
327
362
  }
328
363
 
329
364
  class CacheManager {
330
- constructor(cacheDir = '.cache', defaultTTL = 24 * 60 * 60 * 1000) {
365
+ constructor(cacheDir = process.env.XDG_CACHE_HOME
366
+ ? path.join(process.env.XDG_CACHE_HOME, 'ts-tonal-client')
367
+ : path.join(os.homedir(), '.cache', 'ts-tonal-client'), defaultTTL = 24 * 60 * 60 * 1000) {
331
368
  this.cacheDir = cacheDir;
332
369
  this.defaultTTL = defaultTTL;
333
- this.ensureCacheDir();
334
370
  }
335
371
  ensureCacheDir() {
336
372
  if (!fs.existsSync(this.cacheDir)) {
@@ -351,8 +387,7 @@ class CacheManager {
351
387
  const cachedAt = new Date(entry.cachedAt).getTime();
352
388
  const now = Date.now();
353
389
  const age = now - cachedAt;
354
- if (age > entry.ttl) {
355
- // Cache expired
390
+ if (!Number.isFinite(cachedAt) || typeof entry.ttl !== 'number' || age > entry.ttl) {
356
391
  return null;
357
392
  }
358
393
  return entry.data;
@@ -364,12 +399,15 @@ class CacheManager {
364
399
  }
365
400
  async set(key, data, ttl) {
366
401
  const cachePath = this.getCachePath(key);
402
+ this.ensureCacheDir();
367
403
  const entry = {
368
404
  cachedAt: new Date().toISOString(),
369
405
  ttl: ttl || this.defaultTTL,
370
406
  data,
371
407
  };
372
- fs.writeFileSync(cachePath, JSON.stringify(entry, null, 2), 'utf-8');
408
+ const tempPath = `${cachePath}.tmp`;
409
+ fs.writeFileSync(tempPath, JSON.stringify(entry, null, 2), 'utf-8');
410
+ fs.renameSync(tempPath, cachePath);
373
411
  }
374
412
  async invalidate(key) {
375
413
  const cachePath = this.getCachePath(key);
@@ -379,7 +417,7 @@ class CacheManager {
379
417
  }
380
418
  async clear() {
381
419
  if (fs.existsSync(this.cacheDir)) {
382
- const files = fs.readdirSync(this.cacheDir);
420
+ const files = fs.readdirSync(this.cacheDir).filter(file => file.endsWith('.json') || file.endsWith('.json.tmp'));
383
421
  for (const file of files) {
384
422
  fs.unlinkSync(path.join(this.cacheDir, file));
385
423
  }
@@ -388,9 +426,9 @@ class CacheManager {
388
426
  }
389
427
 
390
428
  class MovementService {
391
- constructor(httpClient) {
429
+ constructor(httpClient, cacheDir) {
392
430
  this.httpClient = httpClient;
393
- this.cacheManager = new CacheManager();
431
+ this.cacheManager = new CacheManager(cacheDir);
394
432
  }
395
433
  async getMovements(useCache = true) {
396
434
  if (useCache) {
@@ -400,7 +438,12 @@ class MovementService {
400
438
  }
401
439
  }
402
440
  const movements = await this.httpClient.request('/movements');
403
- await this.cacheManager.set('movements', movements);
441
+ try {
442
+ await this.cacheManager.set('movements', movements);
443
+ }
444
+ catch {
445
+ // Cache writes are best-effort and must not hide a successful API response.
446
+ }
404
447
  return movements;
405
448
  }
406
449
  async invalidateMovementsCache() {
@@ -504,15 +547,15 @@ class UserService {
504
547
  }
505
548
 
506
549
  class TonalClient {
507
- constructor(username, password) {
550
+ constructor(username, password, cacheDir) {
508
551
  this.authManager = new AuthManager(username, password);
509
552
  this.httpClient = new HttpClient(this.authManager);
510
553
  this.workoutService = new WorkoutService(this.httpClient);
511
- this.movementService = new MovementService(this.httpClient);
554
+ this.movementService = new MovementService(this.httpClient, cacheDir);
512
555
  this.userService = new UserService(this.httpClient);
513
556
  }
514
557
  static async create(credentials) {
515
- const client = new TonalClient(credentials.username, credentials.password);
558
+ const client = new TonalClient(credentials.username, credentials.password, credentials.cacheDir);
516
559
  await client.authManager.authenticate();
517
560
  return client;
518
561
  }
@@ -525,7 +568,32 @@ class TonalClient {
525
568
  }
526
569
  // User operations
527
570
  async getUserInfo() {
528
- return this.userService.getUserInfo();
571
+ if (this.userInfo) {
572
+ return this.userInfo;
573
+ }
574
+ let request = this.userInfoPromise;
575
+ if (!request) {
576
+ request = this.userService.getUserInfo();
577
+ this.userInfoPromise = request;
578
+ }
579
+ try {
580
+ const userInfo = await request;
581
+ if (this.userInfoPromise === request) {
582
+ this.userInfo = userInfo;
583
+ this.userInfoPromise = undefined;
584
+ }
585
+ return userInfo;
586
+ }
587
+ catch (error) {
588
+ if (this.userInfoPromise === request) {
589
+ this.userInfoPromise = undefined;
590
+ }
591
+ throw error;
592
+ }
593
+ }
594
+ invalidateUserInfo() {
595
+ this.userInfo = undefined;
596
+ this.userInfoPromise = undefined;
529
597
  }
530
598
  async getGoals() {
531
599
  return this.userService.getGoals();
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@dlwiest/ts-tonal-client",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "TypeScript client for Tonal API",
5
- "main": "dist/index.js",
5
+ "main": "dist/index.cjs",
6
6
  "module": "dist/index.esm.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "type": "module",
@@ -10,14 +10,14 @@
10
10
  ".": {
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.esm.js",
13
- "require": "./dist/index.js"
13
+ "require": "./dist/index.cjs"
14
14
  }
15
15
  },
16
16
  "files": [
17
17
  "dist"
18
18
  ],
19
19
  "scripts": {
20
- "build": "rollup -c --bundleConfigAsCjs",
20
+ "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && rollup -c --bundleConfigAsCjs",
21
21
  "typecheck": "tsc --noEmit",
22
22
  "dev": "tsc --watch --noEmit",
23
23
  "test": "jest",
@@ -64,7 +64,7 @@
64
64
  "client"
65
65
  ],
66
66
  "author": "dlwiest",
67
- "license": "ISC",
67
+ "license": "MIT",
68
68
  "bugs": {
69
69
  "url": "https://github.com/dlwiest/ts-tonal-client/issues"
70
70
  },
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env tsx
2
- import 'dotenv/config';
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env tsx
2
- import 'dotenv/config';
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env tsx
2
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env tsx
2
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env tsx
2
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1 +0,0 @@
1
- import 'dotenv/config';
@@ -1,15 +0,0 @@
1
- export declare class AuthManager {
2
- private username;
3
- private password;
4
- private idToken;
5
- private refreshToken;
6
- private tokenExpiresAt;
7
- private isRefreshing;
8
- private readonly authUrl;
9
- private readonly clientId;
10
- constructor(username: string, password: string);
11
- authenticate(): Promise<string>;
12
- getValidToken(): Promise<string>;
13
- private isTokenValid;
14
- private refreshTokens;
15
- }
@@ -1,40 +0,0 @@
1
- import { TonalMovement, TonalSharedWorkout, TonalWorkout, TonalUserInfo, TonalGoal, TonalTrainingEffectGoalsResponse, TonalTrainingType, TonalGoalMetric, TonalUserSettings, TonalDailyMetrics, TonalCurrentStreak, TonalActivitySummary, TonalUserStatistics, TonalAchievementStats, TonalEarnedAchievement, TonalHomeCalendar, TonalWorkoutEstimateSet, TonalWorkoutEstimateResponse, TonalWorkoutCreateRequest, TonalWorkoutUpdateRequest, TonalMuscleReadiness, TonalProgram, TonalTargetScoresResponse, TonalMetricScoresResponse } from './types';
2
- export declare class TonalClient {
3
- private authManager;
4
- private httpClient;
5
- private workoutService;
6
- private movementService;
7
- private userService;
8
- private constructor();
9
- static create(credentials: {
10
- username: string;
11
- password: string;
12
- }): Promise<TonalClient>;
13
- getMovements(useCache?: boolean): Promise<TonalMovement[]>;
14
- invalidateMovementsCache(): Promise<void>;
15
- getUserInfo(): Promise<TonalUserInfo>;
16
- getGoals(): Promise<TonalGoal[]>;
17
- getTrainingEffectGoals(): Promise<TonalTrainingEffectGoalsResponse>;
18
- getTrainingTypes(): Promise<TonalTrainingType[]>;
19
- getGoalMetrics(): Promise<TonalGoalMetric[]>;
20
- getUserSettings(): Promise<TonalUserSettings>;
21
- getDailyMetrics(days?: number): Promise<TonalDailyMetrics[]>;
22
- getCurrentStreak(): Promise<TonalCurrentStreak>;
23
- getActivitySummaries(): Promise<TonalActivitySummary[]>;
24
- getUserStatistics(): Promise<TonalUserStatistics>;
25
- getAchievementStats(): Promise<TonalAchievementStats>;
26
- getAchievements(): Promise<TonalEarnedAchievement[]>;
27
- getHomeCalendar(): Promise<TonalHomeCalendar>;
28
- getMuscleReadiness(): Promise<TonalMuscleReadiness>;
29
- getProgramById(programId: string): Promise<TonalProgram>;
30
- getTargetScores(): Promise<TonalTargetScoresResponse>;
31
- getMetricScores(startWeek?: number): Promise<TonalMetricScoresResponse>;
32
- getUserWorkouts(offset?: number, limit?: number): Promise<TonalWorkout[]>;
33
- getDailyLifts(timeZone?: string): Promise<TonalWorkout[]>;
34
- getWorkoutById(workoutId: string): Promise<TonalWorkout>;
35
- getWorkoutByShareUrl(shareUrl: string): Promise<TonalSharedWorkout>;
36
- estimateWorkoutDuration(sets: TonalWorkoutEstimateSet[]): Promise<TonalWorkoutEstimateResponse>;
37
- createWorkout(workoutData: TonalWorkoutCreateRequest): Promise<TonalWorkout>;
38
- updateWorkout(workoutData: TonalWorkoutUpdateRequest): Promise<TonalWorkout>;
39
- deleteWorkout(workoutId: string): Promise<void>;
40
- }
@@ -1,12 +0,0 @@
1
- import { AuthManager } from '../auth/auth-manager';
2
- export declare class HttpClient {
3
- private authManager;
4
- private readonly baseUrl;
5
- private readonly requestTimeout;
6
- private readonly maxRetries;
7
- constructor(authManager: AuthManager);
8
- request<T>(endpoint: string, options?: RequestInit, expectsBody?: boolean): Promise<T>;
9
- private makeRequest;
10
- private makeRequestWithRetry;
11
- private sleep;
12
- }
@@ -1,4 +0,0 @@
1
- import { TonalClient } from './client';
2
- export * from './types';
3
- export { TonalClient };
4
- export default TonalClient;
@@ -1,9 +0,0 @@
1
- import { HttpClient } from '../http/http-client';
2
- import { TonalMovement } from '../types';
3
- export declare class MovementService {
4
- private httpClient;
5
- private cacheManager;
6
- constructor(httpClient: HttpClient);
7
- getMovements(useCache?: boolean): Promise<TonalMovement[]>;
8
- invalidateMovementsCache(): Promise<void>;
9
- }