@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 +2 -1
- package/dist/index.esm.js +83 -4
- package/dist/index.js +83 -4
- package/dist/src/client.d.ts +2 -1
- package/dist/src/services/movement-service.d.ts +3 -1
- package/dist/src/utils/cache-manager.d.ts +11 -0
- package/package.json +1 -1
- package/dist/client.d.ts +0 -16
- package/dist/types.d.ts +0 -115
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
|
|
331
|
-
|
|
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
|
|
335
|
-
|
|
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() {
|
package/dist/src/client.d.ts
CHANGED
|
@@ -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
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 {};
|