@dlwiest/ts-tonal-client 0.1.1 → 0.2.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/dist/index.d.ts CHANGED
@@ -645,7 +645,8 @@ declare class TonalClient {
645
645
  username: string;
646
646
  password: string;
647
647
  }): Promise<TonalClient>;
648
- getMovements(): Promise<TonalMovement[]>;
648
+ getMovements(useCache?: boolean): Promise<TonalMovement[]>;
649
+ invalidateMovementsCache(): Promise<void>;
649
650
  getUserInfo(): Promise<TonalUserInfo>;
650
651
  getGoals(): Promise<TonalGoal[]>;
651
652
  getTrainingEffectGoals(): Promise<TonalTrainingEffectGoalsResponse>;
package/dist/index.esm.js CHANGED
@@ -1,3 +1,6 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
1
4
  class TonalClientError extends Error {
2
5
  constructor(message, statusCode, originalError) {
3
6
  super(message);
@@ -323,12 +326,85 @@ class WorkoutService {
323
326
  }
324
327
  }
325
328
 
329
+ class CacheManager {
330
+ constructor(cacheDir = '.cache', defaultTTL = 24 * 60 * 60 * 1000) {
331
+ this.cacheDir = cacheDir;
332
+ this.defaultTTL = defaultTTL;
333
+ this.ensureCacheDir();
334
+ }
335
+ ensureCacheDir() {
336
+ if (!fs.existsSync(this.cacheDir)) {
337
+ fs.mkdirSync(this.cacheDir, { recursive: true });
338
+ }
339
+ }
340
+ getCachePath(key) {
341
+ return path.join(this.cacheDir, `${key}.json`);
342
+ }
343
+ async get(key) {
344
+ const cachePath = this.getCachePath(key);
345
+ if (!fs.existsSync(cachePath)) {
346
+ return null;
347
+ }
348
+ try {
349
+ const content = fs.readFileSync(cachePath, 'utf-8');
350
+ const entry = JSON.parse(content);
351
+ const cachedAt = new Date(entry.cachedAt).getTime();
352
+ const now = Date.now();
353
+ const age = now - cachedAt;
354
+ if (age > entry.ttl) {
355
+ // Cache expired
356
+ return null;
357
+ }
358
+ return entry.data;
359
+ }
360
+ catch (error) {
361
+ // If there's an error reading/parsing cache, treat as cache miss
362
+ return null;
363
+ }
364
+ }
365
+ async set(key, data, ttl) {
366
+ const cachePath = this.getCachePath(key);
367
+ const entry = {
368
+ cachedAt: new Date().toISOString(),
369
+ ttl: ttl || this.defaultTTL,
370
+ data,
371
+ };
372
+ fs.writeFileSync(cachePath, JSON.stringify(entry, null, 2), 'utf-8');
373
+ }
374
+ async invalidate(key) {
375
+ const cachePath = this.getCachePath(key);
376
+ if (fs.existsSync(cachePath)) {
377
+ fs.unlinkSync(cachePath);
378
+ }
379
+ }
380
+ async clear() {
381
+ if (fs.existsSync(this.cacheDir)) {
382
+ const files = fs.readdirSync(this.cacheDir);
383
+ for (const file of files) {
384
+ fs.unlinkSync(path.join(this.cacheDir, file));
385
+ }
386
+ }
387
+ }
388
+ }
389
+
326
390
  class MovementService {
327
391
  constructor(httpClient) {
328
392
  this.httpClient = httpClient;
393
+ this.cacheManager = new CacheManager();
394
+ }
395
+ async getMovements(useCache = true) {
396
+ if (useCache) {
397
+ const cached = await this.cacheManager.get('movements');
398
+ if (cached) {
399
+ return cached;
400
+ }
401
+ }
402
+ const movements = await this.httpClient.request('/movements');
403
+ await this.cacheManager.set('movements', movements);
404
+ return movements;
329
405
  }
330
- async getMovements() {
331
- return this.httpClient.request('/movements');
406
+ async invalidateMovementsCache() {
407
+ await this.cacheManager.invalidate('movements');
332
408
  }
333
409
  }
334
410
 
@@ -441,8 +517,11 @@ class TonalClient {
441
517
  return client;
442
518
  }
443
519
  // Movement operations
444
- async getMovements() {
445
- return this.movementService.getMovements();
520
+ async getMovements(useCache = true) {
521
+ return this.movementService.getMovements(useCache);
522
+ }
523
+ async invalidateMovementsCache() {
524
+ return this.movementService.invalidateMovementsCache();
446
525
  }
447
526
  // User operations
448
527
  async getUserInfo() {
package/dist/index.js CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var fs = require('fs');
6
+ var path = require('path');
7
+
5
8
  class TonalClientError extends Error {
6
9
  constructor(message, statusCode, originalError) {
7
10
  super(message);
@@ -327,12 +330,85 @@ class WorkoutService {
327
330
  }
328
331
  }
329
332
 
333
+ class CacheManager {
334
+ constructor(cacheDir = '.cache', defaultTTL = 24 * 60 * 60 * 1000) {
335
+ this.cacheDir = cacheDir;
336
+ this.defaultTTL = defaultTTL;
337
+ this.ensureCacheDir();
338
+ }
339
+ ensureCacheDir() {
340
+ if (!fs.existsSync(this.cacheDir)) {
341
+ fs.mkdirSync(this.cacheDir, { recursive: true });
342
+ }
343
+ }
344
+ getCachePath(key) {
345
+ return path.join(this.cacheDir, `${key}.json`);
346
+ }
347
+ async get(key) {
348
+ const cachePath = this.getCachePath(key);
349
+ if (!fs.existsSync(cachePath)) {
350
+ return null;
351
+ }
352
+ try {
353
+ const content = fs.readFileSync(cachePath, 'utf-8');
354
+ const entry = JSON.parse(content);
355
+ const cachedAt = new Date(entry.cachedAt).getTime();
356
+ const now = Date.now();
357
+ const age = now - cachedAt;
358
+ if (age > entry.ttl) {
359
+ // Cache expired
360
+ return null;
361
+ }
362
+ return entry.data;
363
+ }
364
+ catch (error) {
365
+ // If there's an error reading/parsing cache, treat as cache miss
366
+ return null;
367
+ }
368
+ }
369
+ async set(key, data, ttl) {
370
+ const cachePath = this.getCachePath(key);
371
+ const entry = {
372
+ cachedAt: new Date().toISOString(),
373
+ ttl: ttl || this.defaultTTL,
374
+ data,
375
+ };
376
+ fs.writeFileSync(cachePath, JSON.stringify(entry, null, 2), 'utf-8');
377
+ }
378
+ async invalidate(key) {
379
+ const cachePath = this.getCachePath(key);
380
+ if (fs.existsSync(cachePath)) {
381
+ fs.unlinkSync(cachePath);
382
+ }
383
+ }
384
+ async clear() {
385
+ if (fs.existsSync(this.cacheDir)) {
386
+ const files = fs.readdirSync(this.cacheDir);
387
+ for (const file of files) {
388
+ fs.unlinkSync(path.join(this.cacheDir, file));
389
+ }
390
+ }
391
+ }
392
+ }
393
+
330
394
  class MovementService {
331
395
  constructor(httpClient) {
332
396
  this.httpClient = httpClient;
397
+ this.cacheManager = new CacheManager();
398
+ }
399
+ async getMovements(useCache = true) {
400
+ if (useCache) {
401
+ const cached = await this.cacheManager.get('movements');
402
+ if (cached) {
403
+ return cached;
404
+ }
405
+ }
406
+ const movements = await this.httpClient.request('/movements');
407
+ await this.cacheManager.set('movements', movements);
408
+ return movements;
333
409
  }
334
- async getMovements() {
335
- return this.httpClient.request('/movements');
410
+ async invalidateMovementsCache() {
411
+ await this.cacheManager.invalidate('movements');
336
412
  }
337
413
  }
338
414
 
@@ -445,8 +521,11 @@ class TonalClient {
445
521
  return client;
446
522
  }
447
523
  // Movement operations
448
- async getMovements() {
449
- return this.movementService.getMovements();
524
+ async getMovements(useCache = true) {
525
+ return this.movementService.getMovements(useCache);
526
+ }
527
+ async invalidateMovementsCache() {
528
+ return this.movementService.invalidateMovementsCache();
450
529
  }
451
530
  // User operations
452
531
  async getUserInfo() {
@@ -10,7 +10,8 @@ export declare class TonalClient {
10
10
  username: string;
11
11
  password: string;
12
12
  }): Promise<TonalClient>;
13
- getMovements(): Promise<TonalMovement[]>;
13
+ getMovements(useCache?: boolean): Promise<TonalMovement[]>;
14
+ invalidateMovementsCache(): Promise<void>;
14
15
  getUserInfo(): Promise<TonalUserInfo>;
15
16
  getGoals(): Promise<TonalGoal[]>;
16
17
  getTrainingEffectGoals(): Promise<TonalTrainingEffectGoalsResponse>;
@@ -2,6 +2,8 @@ import { HttpClient } from '../http/http-client';
2
2
  import { TonalMovement } from '../types';
3
3
  export declare class MovementService {
4
4
  private httpClient;
5
+ private cacheManager;
5
6
  constructor(httpClient: HttpClient);
6
- getMovements(): Promise<TonalMovement[]>;
7
+ getMovements(useCache?: boolean): Promise<TonalMovement[]>;
8
+ invalidateMovementsCache(): Promise<void>;
7
9
  }
@@ -0,0 +1,11 @@
1
+ export declare class CacheManager {
2
+ private cacheDir;
3
+ private defaultTTL;
4
+ constructor(cacheDir?: string, defaultTTL?: number);
5
+ private ensureCacheDir;
6
+ private getCachePath;
7
+ get<T>(key: string): Promise<T | null>;
8
+ set<T>(key: string, data: T, ttl?: number): Promise<void>;
9
+ invalidate(key: string): Promise<void>;
10
+ clear(): Promise<void>;
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dlwiest/ts-tonal-client",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "TypeScript client for Tonal API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
package/dist/client.d.ts DELETED
@@ -1,16 +0,0 @@
1
- import { TonalMovement, TonalSharedWorkout, TonalWorkout } from './types';
2
- export declare class TonalClient {
3
- private username;
4
- private password;
5
- private idToken;
6
- private tokenExpiresAt;
7
- private constructor();
8
- static create({ username, password }: {
9
- username: string;
10
- password: string;
11
- }): Promise<TonalClient>;
12
- private refreshToken;
13
- getMovements(): Promise<TonalMovement[]>;
14
- getWorkoutById(id: string): Promise<TonalWorkout>;
15
- getWorkoutByShareUrl(shareUrl: string): Promise<TonalSharedWorkout>;
16
- }
package/dist/types.d.ts DELETED
@@ -1,115 +0,0 @@
1
- export interface OAuthTokenResponse {
2
- access_token: string;
3
- id_token: string;
4
- refresh_token: string;
5
- scope: string;
6
- token_type: string;
7
- expires_in: number;
8
- }
9
- export type MuscleGroup = 'Obliques' | 'Abs' | 'Shoulders' | 'Glutes' | 'Back' | 'Biceps' | 'Quads' | 'Triceps' | 'Chest' | 'Hamstrings' | 'Calves' | 'Forearms';
10
- export interface TonalMovement {
11
- id: string;
12
- createdAt: string;
13
- updatedAt: string;
14
- name: string;
15
- shortName: string;
16
- muscleGroups: MuscleGroup[];
17
- bodyRegion: string;
18
- bodyRegionDisplay: string;
19
- baseOfSupport: string;
20
- pushPull: string;
21
- family: string;
22
- familyDisplay: string;
23
- inFreeLift: boolean;
24
- onMachine: boolean;
25
- countReps: boolean;
26
- isTwoSided: boolean;
27
- isBilateral: boolean;
28
- isAlternating: boolean;
29
- offMachineAccessory: string;
30
- descriptionHow: string;
31
- descriptionWhy: string;
32
- sortOrder: number;
33
- imageAssetId: string;
34
- skillLevel: number;
35
- active: boolean;
36
- featureGroupIds: null | string[];
37
- isGeneric: boolean;
38
- }
39
- export interface TonalWorkout {
40
- id: string;
41
- createdAt: string;
42
- title: string;
43
- shortDescription: string;
44
- description: string;
45
- productionCode: string;
46
- assetId: string;
47
- coachId: string;
48
- sets: WorkoutSet[];
49
- duration: number;
50
- publishState: string;
51
- programId: string | null;
52
- level: string;
53
- groupIds: string[];
54
- targetArea: string;
55
- tags: string[] | null;
56
- bodyRegions: MuscleGroup[];
57
- goalIds: string[] | null;
58
- trainingEffectGoals: string[];
59
- disableModification: boolean;
60
- publishedAt: string;
61
- localPublishedAt: string;
62
- type: string;
63
- userId: string;
64
- style: string;
65
- trainingType: string;
66
- trainingTypeIds: string[] | null;
67
- mobileFriendly: boolean;
68
- live: boolean;
69
- recoveryWeight: boolean;
70
- supportedDevices: string[] | null;
71
- featureGroupIds: string[] | null;
72
- movementIds: string[];
73
- muscleGroupsForExclusion: MuscleGroup[] | null;
74
- playbackType: string;
75
- isImported: boolean;
76
- }
77
- interface WorkoutSet {
78
- id: string;
79
- workoutId: string;
80
- blockStart: boolean;
81
- movementId: string;
82
- prescribedReps: number;
83
- repetition: number;
84
- repetitionTotal: number;
85
- blockNumber: number;
86
- burnout: boolean;
87
- spotter: boolean;
88
- eccentric: boolean;
89
- chains: boolean;
90
- skipSetup: boolean;
91
- skipDemo: boolean;
92
- finalSet: boolean;
93
- calibration: boolean;
94
- practice: boolean;
95
- flex: boolean;
96
- progressive: boolean;
97
- weightPercentage: number;
98
- warmUp: boolean;
99
- durationBasedRepGoal: number;
100
- setGroup: number;
101
- round: number;
102
- description: string;
103
- dropSet: boolean;
104
- omitempty: null | any;
105
- }
106
- export interface TonalSharedWorkout {
107
- id: string;
108
- sharerUserId: string;
109
- parentWorkoutId: string;
110
- workoutSnapshotId: string;
111
- workoutSnapshotHash: string;
112
- deepLinkUrl: string;
113
- workoutSnapshot: TonalWorkout;
114
- }
115
- export {};