@openlifelog/sdk 1.0.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/EXAMPLES.md +624 -0
- package/README.md +824 -0
- package/client.ts +190 -0
- package/config.ts +96 -0
- package/constants/metrics.ts +116 -0
- package/dist/index.d.mts +1101 -0
- package/dist/index.d.ts +1101 -0
- package/dist/index.js +2023 -0
- package/dist/index.mjs +1969 -0
- package/index.ts +49 -0
- package/package.json +53 -0
- package/resources/ai.ts +26 -0
- package/resources/auth.ts +98 -0
- package/resources/exercises.ts +112 -0
- package/resources/food-logs.ts +132 -0
- package/resources/foods.ts +185 -0
- package/resources/goals.ts +155 -0
- package/resources/metrics.ts +115 -0
- package/resources/programs.ts +123 -0
- package/resources/sessions.ts +142 -0
- package/resources/users.ts +132 -0
- package/resources/workouts.ts +147 -0
- package/tsconfig.json +27 -0
- package/types/ai.ts +55 -0
- package/types/common.ts +177 -0
- package/types/exercise.ts +75 -0
- package/types/food.ts +208 -0
- package/types/goal.ts +169 -0
- package/types/index.ts +17 -0
- package/types/metric.ts +108 -0
- package/types/program.ts +120 -0
- package/types/session.ts +196 -0
- package/types/user.ts +79 -0
- package/types/workout.ts +97 -0
- package/utils/errors.ts +159 -0
- package/utils/http.ts +313 -0
- package/utils/index.ts +8 -0
- package/utils/pagination.ts +106 -0
- package/utils/units.ts +279 -0
package/README.md
ADDED
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
# OpenLifeLog TypeScript SDK
|
|
2
|
+
|
|
3
|
+
> A comprehensive, type-safe TypeScript SDK for the OpenLifeLog API - the complete fitness and nutrition tracking platform.
|
|
4
|
+
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **🎯 Type-Safe** - Full TypeScript support with comprehensive type definitions
|
|
11
|
+
- **🔄 Auto-Pagination** - Automatic pagination for large data sets
|
|
12
|
+
- **📏 Unit Conversion** - Automatic conversion between metric and imperial units
|
|
13
|
+
- **🔁 Auto-Retry** - Automatic retries with exponential backoff for failed requests
|
|
14
|
+
- **❌ Error Handling** - Rich error classes with detailed error information
|
|
15
|
+
- **🚀 Modern API** - Intuitive, resource-based API design
|
|
16
|
+
- **📦 Tree-Shakeable** - Modular design supports tree-shaking
|
|
17
|
+
- **⚡ Fast** - Optimized for performance with HTTP/2 support
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @openlifelog/sdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
yarn add @openlifelog/sdk
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm add @openlifelog/sdk
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### Option 1: With JWT Token (Recommended for apps with existing auth)
|
|
36
|
+
|
|
37
|
+
If your app already handles authentication and you have a JWT token:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
41
|
+
|
|
42
|
+
// Initialize with JWT token
|
|
43
|
+
const client = new OpenLifeLog({
|
|
44
|
+
apiKey: 'your-jwt-token-here',
|
|
45
|
+
baseUrl: 'http://localhost:8080',
|
|
46
|
+
measurementSystem: 'metric', // or 'imperial'
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Ready to use immediately - no login needed!
|
|
50
|
+
const user = await client.users.me();
|
|
51
|
+
await client.logFood({ ... });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Option 2: With SDK Authentication
|
|
55
|
+
|
|
56
|
+
If you want the SDK to handle authentication:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
60
|
+
|
|
61
|
+
// Initialize without token
|
|
62
|
+
const client = new OpenLifeLog({
|
|
63
|
+
baseUrl: 'http://localhost:8080',
|
|
64
|
+
measurementSystem: 'metric',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Login to get token
|
|
68
|
+
const { token, user } = await client.auth.login({
|
|
69
|
+
email: 'john@example.com',
|
|
70
|
+
password: 'securePassword123',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Token is automatically set, ready to use!
|
|
74
|
+
await client.logFood({ ... });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Option 3: Set Token Later
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
81
|
+
|
|
82
|
+
// Initialize without token
|
|
83
|
+
const client = new OpenLifeLog({
|
|
84
|
+
baseUrl: 'http://localhost:8080',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Set token later (e.g., after your own auth flow)
|
|
88
|
+
client.setApiKey('jwt-token-from-your-auth-system');
|
|
89
|
+
|
|
90
|
+
// Now ready to use
|
|
91
|
+
const user = await client.users.me();
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Core Concepts
|
|
95
|
+
|
|
96
|
+
### Authentication
|
|
97
|
+
|
|
98
|
+
#### Using External Authentication
|
|
99
|
+
|
|
100
|
+
If your application already has an authentication system (e.g., Auth0, Clerk, Firebase Auth, custom JWT), you can simply pass the JWT token when initializing the SDK:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// Example: Using with Auth0
|
|
104
|
+
import { useAuth0 } from '@auth0/auth0-react';
|
|
105
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
106
|
+
|
|
107
|
+
function MyComponent() {
|
|
108
|
+
const { getAccessTokenSilently } = useAuth0();
|
|
109
|
+
|
|
110
|
+
const getClient = async () => {
|
|
111
|
+
const token = await getAccessTokenSilently();
|
|
112
|
+
return new OpenLifeLog({
|
|
113
|
+
apiKey: token,
|
|
114
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL,
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Use the client...
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// Example: Using with Next.js session
|
|
124
|
+
import { getSession } from 'next-auth/react';
|
|
125
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
126
|
+
|
|
127
|
+
export async function getServerSideProps(context) {
|
|
128
|
+
const session = await getSession(context);
|
|
129
|
+
|
|
130
|
+
const client = new OpenLifeLog({
|
|
131
|
+
apiKey: session.accessToken,
|
|
132
|
+
baseUrl: process.env.API_URL,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const user = await client.users.me();
|
|
136
|
+
// ...
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### Using SDK Authentication
|
|
141
|
+
|
|
142
|
+
If you want the SDK to handle authentication directly:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// Sign up a new user
|
|
146
|
+
const { token, user } = await client.auth.signup({
|
|
147
|
+
name: 'John Doe',
|
|
148
|
+
email: 'john@example.com',
|
|
149
|
+
password: 'securePassword123',
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Login
|
|
153
|
+
const { token, user } = await client.auth.login({
|
|
154
|
+
email: 'john@example.com',
|
|
155
|
+
password: 'securePassword123',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// The token is automatically set after login/signup
|
|
159
|
+
|
|
160
|
+
// Request password reset
|
|
161
|
+
await client.auth.requestPasswordReset({
|
|
162
|
+
email: 'john@example.com',
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Logout (clears token)
|
|
166
|
+
client.auth.logout();
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### Dynamic Token Updates
|
|
170
|
+
|
|
171
|
+
You can update the token at any time, useful for token refresh scenarios:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// Initial setup
|
|
175
|
+
const client = new OpenLifeLog({
|
|
176
|
+
apiKey: 'initial-token',
|
|
177
|
+
baseUrl: 'http://localhost:8080',
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Later, when token refreshes
|
|
181
|
+
client.setApiKey('new-refreshed-token');
|
|
182
|
+
|
|
183
|
+
// Continue using the client with new token
|
|
184
|
+
await client.users.me();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### User Management
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// Get current user
|
|
191
|
+
const user = await client.users.me();
|
|
192
|
+
|
|
193
|
+
// Update profile
|
|
194
|
+
await client.users.update({
|
|
195
|
+
name: 'Jane Doe',
|
|
196
|
+
isOnboarded: true,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Get preferences
|
|
200
|
+
const prefs = await client.users.getPreferences();
|
|
201
|
+
console.log(prefs.measurementSystem); // 'metric' or 'imperial'
|
|
202
|
+
|
|
203
|
+
// Update preferences
|
|
204
|
+
await client.users.updatePreferences({
|
|
205
|
+
measurementSystem: 'imperial',
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Food & Nutrition Tracking
|
|
210
|
+
|
|
211
|
+
#### Search and Browse Foods
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Search for foods
|
|
215
|
+
const { data: foods } = await client.foods.search({
|
|
216
|
+
q: 'chicken breast',
|
|
217
|
+
limit: 10,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// List all foods with pagination
|
|
221
|
+
const { data, pageInfo } = await client.foods.list({
|
|
222
|
+
limit: 20,
|
|
223
|
+
search: 'protein',
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Get specific food details
|
|
227
|
+
const food = await client.foods.get('food-id');
|
|
228
|
+
console.log(food.nutrients.protein); // Protein in grams
|
|
229
|
+
|
|
230
|
+
// Add to favorites
|
|
231
|
+
await client.foods.addToFavorites(food.id);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### Log Food
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// Simple way - using the convenience method
|
|
238
|
+
const log = await client.logFood({
|
|
239
|
+
foodId: 'food-uuid',
|
|
240
|
+
name: 'Chicken Breast',
|
|
241
|
+
servingSizeName: 'breast (200g)',
|
|
242
|
+
quantity: 1.5,
|
|
243
|
+
unitGrams: 200,
|
|
244
|
+
mealType: 'lunch',
|
|
245
|
+
notes: 'Grilled with olive oil',
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Or use the resource directly
|
|
249
|
+
const log = await client.foodLogs.create({
|
|
250
|
+
foodId: 'food-uuid',
|
|
251
|
+
name: 'Chicken Breast',
|
|
252
|
+
servingSizeName: 'breast (200g)',
|
|
253
|
+
quantity: 1.5,
|
|
254
|
+
unitGrams: 200,
|
|
255
|
+
mealType: 'lunch',
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Get Nutrition Summary
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// Get today's nutrition
|
|
263
|
+
const summary = await client.getTodayNutrition();
|
|
264
|
+
console.log(`Calories: ${summary.totalCalories}`);
|
|
265
|
+
console.log(`Protein: ${summary.totalProtein}g`);
|
|
266
|
+
|
|
267
|
+
// Get specific date
|
|
268
|
+
const summary = await client.foodLogs.getDailySummary({
|
|
269
|
+
date: '2024-01-15',
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Filter by meal type
|
|
273
|
+
const lunchSummary = await client.foodLogs.getDailySummary({
|
|
274
|
+
date: '2024-01-15',
|
|
275
|
+
mealType: 'lunch',
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Exercise & Workout Management
|
|
280
|
+
|
|
281
|
+
#### Exercises
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// List exercises
|
|
285
|
+
const { data: exercises } = await client.exercises.list({
|
|
286
|
+
category: 'strength',
|
|
287
|
+
muscleGroup: 'chest',
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Search exercises
|
|
291
|
+
const { data } = await client.exercises.search({
|
|
292
|
+
q: 'bench press',
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Create custom exercise
|
|
296
|
+
const exercise = await client.exercises.create({
|
|
297
|
+
name: 'Custom Push-up Variation',
|
|
298
|
+
category: 'strength',
|
|
299
|
+
primaryMuscles: ['chest', 'triceps'],
|
|
300
|
+
secondaryMuscles: ['front_delts'],
|
|
301
|
+
equipment: ['bodyweight'],
|
|
302
|
+
capabilities: {
|
|
303
|
+
supportsWeight: false,
|
|
304
|
+
supportsBodyweightOnly: true,
|
|
305
|
+
supportsAssistance: false,
|
|
306
|
+
supportsDistance: false,
|
|
307
|
+
supportsDuration: true,
|
|
308
|
+
supportsTempo: true,
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### Workout Templates
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Create a workout template
|
|
317
|
+
const workout = await client.workouts.create({
|
|
318
|
+
name: 'Upper Body A',
|
|
319
|
+
description: 'Push-focused upper body workout',
|
|
320
|
+
type: 'strength',
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Add exercises to workout
|
|
324
|
+
await client.workouts.addExercises(workout.id, {
|
|
325
|
+
exercises: [
|
|
326
|
+
{
|
|
327
|
+
exerciseId: 'bench-press-id',
|
|
328
|
+
orderIndex: 1,
|
|
329
|
+
workoutFormat: 'straight_sets',
|
|
330
|
+
setsData: [
|
|
331
|
+
{ order: 1, reps: 5, weight: 100, restSeconds: 180 },
|
|
332
|
+
{ order: 2, reps: 5, weight: 100, restSeconds: 180 },
|
|
333
|
+
{ order: 3, reps: 5, weight: 100, restSeconds: 180 },
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Clone a workout
|
|
340
|
+
const clonedWorkout = await client.workouts.clone(workout.id);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### Workout Sessions (Performance Tracking)
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
// Start a workout session
|
|
347
|
+
const session = await client.sessions.start({
|
|
348
|
+
workoutId: 'workout-id',
|
|
349
|
+
userBodyweight: 82.5, // Will be converted based on user preference
|
|
350
|
+
notes: 'Feeling strong today',
|
|
351
|
+
mood: 'energetic',
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Update session during workout
|
|
355
|
+
await client.sessions.update(session.id, {
|
|
356
|
+
notes: 'Added extra set to bench press',
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Complete the session
|
|
360
|
+
await client.sessions.complete(session.id, 'Great workout!');
|
|
361
|
+
|
|
362
|
+
// Get session details
|
|
363
|
+
const completedSession = await client.sessions.get(session.id);
|
|
364
|
+
|
|
365
|
+
// Get exercise history
|
|
366
|
+
const history = await client.sessions.getExerciseHistory('exercise-id');
|
|
367
|
+
|
|
368
|
+
// Get personal records
|
|
369
|
+
const prs = await client.sessions.getPersonalRecords();
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Training Programs
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
// List program templates
|
|
376
|
+
const { data: templates } = await client.programs.listTemplates();
|
|
377
|
+
|
|
378
|
+
// Create a program
|
|
379
|
+
const program = await client.programs.create({
|
|
380
|
+
name: '5/3/1 Strength Program',
|
|
381
|
+
description: "Wendler's 5/3/1 for main lifts",
|
|
382
|
+
schedule: [
|
|
383
|
+
{
|
|
384
|
+
week: 1,
|
|
385
|
+
day: 1,
|
|
386
|
+
name: 'Squat Day',
|
|
387
|
+
exercises: [
|
|
388
|
+
{
|
|
389
|
+
exerciseId: 'squat-id',
|
|
390
|
+
orderIndex: 1,
|
|
391
|
+
sets: [
|
|
392
|
+
{ order: 1, reps: 5, weight: 100 },
|
|
393
|
+
{ order: 2, reps: 5, weight: 110 },
|
|
394
|
+
{ order: 3, reps: 5, weight: 120 },
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
metadata: {
|
|
401
|
+
difficulty: 'intermediate',
|
|
402
|
+
type: 'strength',
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Enroll in program
|
|
407
|
+
const enrollment = await client.programs.enroll(program.id, {
|
|
408
|
+
startDate: '2024-01-15',
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Get current week schedule
|
|
412
|
+
const currentWeek = await client.programs.getCurrentWeek(enrollment.id);
|
|
413
|
+
|
|
414
|
+
// Get enrollment progress
|
|
415
|
+
const progress = await client.programs.getEnrollmentProgress(enrollment.id);
|
|
416
|
+
console.log(`${progress.completionPercentage}% complete`);
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Metrics & Goals
|
|
420
|
+
|
|
421
|
+
#### Track Metrics
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
// List available metrics
|
|
425
|
+
const { data: metrics } = await client.metrics.list();
|
|
426
|
+
|
|
427
|
+
// Log a metric (e.g., body weight)
|
|
428
|
+
await client.metrics.log({
|
|
429
|
+
metricKey: 'body_weight',
|
|
430
|
+
value: 82.5,
|
|
431
|
+
metadata: {
|
|
432
|
+
time: 'morning',
|
|
433
|
+
conditions: 'fasted',
|
|
434
|
+
},
|
|
435
|
+
loggedAt: new Date().toISOString(),
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Get daily metric aggregate
|
|
439
|
+
const dailyWeight = await client.metrics.getDailyMetric('body_weight', {
|
|
440
|
+
date: '2024-01-15',
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
#### Set Goals
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
// Create a goal
|
|
448
|
+
await client.goals.create({
|
|
449
|
+
metricKey: 'body_weight',
|
|
450
|
+
targetValue: 75.0,
|
|
451
|
+
targetType: 'exact',
|
|
452
|
+
effectiveFrom: '2024-01-15',
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// Get today's progress
|
|
456
|
+
const progress = await client.getTodayProgress();
|
|
457
|
+
console.log(progress.goals);
|
|
458
|
+
|
|
459
|
+
// Set nutrition goals
|
|
460
|
+
await client.goals.createNutritionGoals({
|
|
461
|
+
effectiveDate: '2024-01-15',
|
|
462
|
+
goals: {
|
|
463
|
+
calories: { type: 'exact', value: 2200 },
|
|
464
|
+
protein: { type: 'min', value: 180 },
|
|
465
|
+
carbs: { type: 'range', min: 200, max: 300 },
|
|
466
|
+
fat: { type: 'range', min: 60, max: 80 },
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// Get nutrition progress
|
|
471
|
+
const nutritionProgress = await client.getTodayNutritionProgress();
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### AI Insights
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
// Get AI-powered nutrition insights
|
|
478
|
+
const insights = await client.ai.getFoodInsights({
|
|
479
|
+
startDate: '2024-01-01',
|
|
480
|
+
endDate: '2024-01-15',
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
console.log('Insights:', insights.insights);
|
|
484
|
+
console.log('Recommendations:', insights.recommendations);
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Advanced Features
|
|
488
|
+
|
|
489
|
+
### Auto-Pagination
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
// Iterate through all foods automatically
|
|
493
|
+
for await (const food of client.foods.listAutoPaginate({ limit: 100 })) {
|
|
494
|
+
console.log(food.name);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Or get all pages at once (use with caution for large datasets)
|
|
498
|
+
const allFoods = await client.foods.listAutoPaginate().toArray();
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Unit Conversion
|
|
502
|
+
|
|
503
|
+
The SDK automatically handles unit conversion based on your measurement system preference:
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
// Set measurement system
|
|
507
|
+
client.setMeasurementSystem('imperial');
|
|
508
|
+
|
|
509
|
+
// Get unit converter
|
|
510
|
+
const converter = client.getUnitConverter();
|
|
511
|
+
|
|
512
|
+
// Manual conversion
|
|
513
|
+
const kgValue = converter.weightToMetric(150); // Convert 150 lbs to kg
|
|
514
|
+
const lbsValue = converter.weightFromMetric(68); // Convert 68 kg to lbs
|
|
515
|
+
|
|
516
|
+
// Get unit labels
|
|
517
|
+
console.log(converter.getWeightUnit()); // 'lbs' or 'kg'
|
|
518
|
+
console.log(converter.getDistanceUnit()); // 'mi' or 'm'
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Error Handling
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
import {
|
|
525
|
+
OpenLifeLogError,
|
|
526
|
+
AuthenticationError,
|
|
527
|
+
ValidationError,
|
|
528
|
+
NotFoundError,
|
|
529
|
+
} from '@openlifelog/sdk';
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
await client.foods.get('invalid-id');
|
|
533
|
+
} catch (error) {
|
|
534
|
+
if (error instanceof NotFoundError) {
|
|
535
|
+
console.error('Food not found');
|
|
536
|
+
} else if (error instanceof AuthenticationError) {
|
|
537
|
+
console.error('Please login first');
|
|
538
|
+
} else if (error instanceof ValidationError) {
|
|
539
|
+
console.error('Validation errors:', error.validationErrors);
|
|
540
|
+
} else if (error instanceof OpenLifeLogError) {
|
|
541
|
+
console.error('API error:', error.message, error.statusCode);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Configuration Options
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
const client = new OpenLifeLog({
|
|
550
|
+
// Required: API base URL
|
|
551
|
+
baseUrl: 'http://localhost:8080',
|
|
552
|
+
|
|
553
|
+
// Optional: JWT token - Use this if you already have a token!
|
|
554
|
+
// This is the recommended approach for apps with existing authentication
|
|
555
|
+
apiKey: 'your-jwt-token',
|
|
556
|
+
|
|
557
|
+
// Optional: Measurement system preference
|
|
558
|
+
measurementSystem: 'metric', // or 'imperial'
|
|
559
|
+
|
|
560
|
+
// Optional: Automatic unit conversion
|
|
561
|
+
autoConvertUnits: true,
|
|
562
|
+
|
|
563
|
+
// Optional: Request timeout (ms)
|
|
564
|
+
timeout: 30000,
|
|
565
|
+
|
|
566
|
+
// Optional: Max retry attempts
|
|
567
|
+
maxRetries: 3,
|
|
568
|
+
|
|
569
|
+
// Optional: Enable retries
|
|
570
|
+
enableRetries: true,
|
|
571
|
+
|
|
572
|
+
// Optional: Custom headers
|
|
573
|
+
headers: {
|
|
574
|
+
'X-Custom-Header': 'value',
|
|
575
|
+
},
|
|
576
|
+
|
|
577
|
+
// Optional: Enable debug logging
|
|
578
|
+
debug: false,
|
|
579
|
+
|
|
580
|
+
// Optional: Custom fetch implementation
|
|
581
|
+
fetch: customFetch,
|
|
582
|
+
});
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
## Complete Example: Tracking a Full Day
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
589
|
+
|
|
590
|
+
const client = new OpenLifeLog({
|
|
591
|
+
baseUrl: 'http://localhost:8080',
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// Login
|
|
595
|
+
await client.auth.login({
|
|
596
|
+
email: 'athlete@example.com',
|
|
597
|
+
password: 'securePassword123',
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
// --- Morning: Log body weight ---
|
|
601
|
+
await client.metrics.log({
|
|
602
|
+
metricKey: 'body_weight',
|
|
603
|
+
value: 82.3,
|
|
604
|
+
metadata: { time: 'morning', conditions: 'fasted' },
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
// --- Breakfast: Log food ---
|
|
608
|
+
await client.logFood({
|
|
609
|
+
foodId: 'oatmeal-id',
|
|
610
|
+
name: 'Oatmeal with Berries',
|
|
611
|
+
quantity: 1,
|
|
612
|
+
unitGrams: 250,
|
|
613
|
+
mealType: 'breakfast',
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// --- Workout: Track training session ---
|
|
617
|
+
const session = await client.sessions.start({
|
|
618
|
+
workoutId: 'upper-body-workout-id',
|
|
619
|
+
userBodyweight: 82.3,
|
|
620
|
+
mood: 'energetic',
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
// ... perform workout ...
|
|
624
|
+
|
|
625
|
+
await client.sessions.complete(session.id, 'Hit new PR on bench press!');
|
|
626
|
+
|
|
627
|
+
// --- Post-workout: Log protein shake ---
|
|
628
|
+
await client.logFood({
|
|
629
|
+
foodId: 'protein-shake-id',
|
|
630
|
+
name: 'Protein Shake',
|
|
631
|
+
quantity: 1,
|
|
632
|
+
unitGrams: 300,
|
|
633
|
+
mealType: 'snack',
|
|
634
|
+
notes: 'Post-workout',
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// --- Evening: Check progress ---
|
|
638
|
+
const nutritionSummary = await client.getTodayNutrition();
|
|
639
|
+
console.log('Daily totals:', {
|
|
640
|
+
calories: nutritionSummary.totalCalories,
|
|
641
|
+
protein: nutritionSummary.totalProtein,
|
|
642
|
+
carbs: nutritionSummary.totalCarbs,
|
|
643
|
+
fat: nutritionSummary.totalFat,
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
const goalProgress = await client.getTodayNutritionProgress();
|
|
647
|
+
console.log('Goal progress:', goalProgress.progress);
|
|
648
|
+
|
|
649
|
+
// Get personal records
|
|
650
|
+
const prs = await client.sessions.getPersonalRecords();
|
|
651
|
+
console.log('Personal Records:', prs);
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
## TypeScript Support
|
|
655
|
+
|
|
656
|
+
The SDK is written in TypeScript and provides comprehensive type definitions:
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
import type {
|
|
660
|
+
User,
|
|
661
|
+
Food,
|
|
662
|
+
FoodLog,
|
|
663
|
+
Exercise,
|
|
664
|
+
Workout,
|
|
665
|
+
WorkoutSession,
|
|
666
|
+
NutritionGoal,
|
|
667
|
+
MetricEvent,
|
|
668
|
+
} from '@openlifelog/sdk';
|
|
669
|
+
|
|
670
|
+
// All API responses are fully typed
|
|
671
|
+
const user: User = await client.users.me();
|
|
672
|
+
const food: Food = await client.foods.get('food-id');
|
|
673
|
+
const log: FoodLog = await client.logFood({...});
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
## Best Practices
|
|
677
|
+
|
|
678
|
+
### 1. **Error Handling**
|
|
679
|
+
|
|
680
|
+
Always wrap API calls in try-catch blocks:
|
|
681
|
+
|
|
682
|
+
```typescript
|
|
683
|
+
try {
|
|
684
|
+
const user = await client.users.me();
|
|
685
|
+
} catch (error) {
|
|
686
|
+
if (error instanceof AuthenticationError) {
|
|
687
|
+
// Redirect to login
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### 2. **Pagination**
|
|
693
|
+
|
|
694
|
+
Use auto-pagination for large datasets:
|
|
695
|
+
|
|
696
|
+
```typescript
|
|
697
|
+
// Good: Memory efficient
|
|
698
|
+
for await (const food of client.foods.listAutoPaginate()) {
|
|
699
|
+
processFood(food);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Avoid: Loads all data at once
|
|
703
|
+
const allFoods = await client.foods.listAutoPaginate().toArray();
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### 3. **Unit Conversion**
|
|
707
|
+
|
|
708
|
+
Let the SDK handle unit conversion automatically:
|
|
709
|
+
|
|
710
|
+
```typescript
|
|
711
|
+
// Set preference once
|
|
712
|
+
client.setMeasurementSystem('imperial');
|
|
713
|
+
|
|
714
|
+
// SDK handles conversion automatically
|
|
715
|
+
const session = await client.sessions.create({
|
|
716
|
+
userBodyweight: 180, // SDK knows this is lbs and converts to kg for API
|
|
717
|
+
});
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### 4. **Reuse Client Instance**
|
|
721
|
+
|
|
722
|
+
Create one client instance and reuse it:
|
|
723
|
+
|
|
724
|
+
```typescript
|
|
725
|
+
// Good: Single instance
|
|
726
|
+
const client = new OpenLifeLog(config);
|
|
727
|
+
export default client;
|
|
728
|
+
|
|
729
|
+
// Avoid: Creating multiple instances
|
|
730
|
+
function getUser() {
|
|
731
|
+
const client = new OpenLifeLog(config); // Don't do this
|
|
732
|
+
return client.users.me();
|
|
733
|
+
}
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
## Framework Integration
|
|
737
|
+
|
|
738
|
+
### Next.js (App Router)
|
|
739
|
+
|
|
740
|
+
```typescript
|
|
741
|
+
// lib/openlifelog.ts
|
|
742
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
743
|
+
|
|
744
|
+
export const getClient = (token?: string) => {
|
|
745
|
+
return new OpenLifeLog({
|
|
746
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL!,
|
|
747
|
+
apiKey: token,
|
|
748
|
+
});
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
// app/actions.ts
|
|
752
|
+
'use server';
|
|
753
|
+
|
|
754
|
+
import { cookies } from 'next/headers';
|
|
755
|
+
import { getClient } from '@/lib/openlifelog';
|
|
756
|
+
|
|
757
|
+
export async function getUserProfile() {
|
|
758
|
+
const token = cookies().get('token')?.value;
|
|
759
|
+
const client = getClient(token);
|
|
760
|
+
return await client.users.me();
|
|
761
|
+
}
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
### React Hook
|
|
765
|
+
|
|
766
|
+
```typescript
|
|
767
|
+
// hooks/useOpenLifeLog.ts
|
|
768
|
+
import { useMemo } from 'react';
|
|
769
|
+
import { OpenLifeLog } from '@openlifelog/sdk';
|
|
770
|
+
import { useAuth } from './useAuth';
|
|
771
|
+
|
|
772
|
+
export function useOpenLifeLog() {
|
|
773
|
+
const { token } = useAuth();
|
|
774
|
+
|
|
775
|
+
return useMemo(
|
|
776
|
+
() =>
|
|
777
|
+
new OpenLifeLog({
|
|
778
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL,
|
|
779
|
+
apiKey: token,
|
|
780
|
+
}),
|
|
781
|
+
[token]
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// In your component
|
|
786
|
+
function MyComponent() {
|
|
787
|
+
const client = useOpenLifeLog();
|
|
788
|
+
|
|
789
|
+
const fetchData = async () => {
|
|
790
|
+
const user = await client.users.me();
|
|
791
|
+
// ...
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
// ...
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
## API Reference
|
|
799
|
+
|
|
800
|
+
Full API documentation is available at:
|
|
801
|
+
- [API Documentation](https://docs.openlifelog.com)
|
|
802
|
+
- [TypeScript Types](./types)
|
|
803
|
+
|
|
804
|
+
## Contributing
|
|
805
|
+
|
|
806
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.
|
|
807
|
+
|
|
808
|
+
## License
|
|
809
|
+
|
|
810
|
+
MIT License - see [LICENSE](../../LICENSE) for details.
|
|
811
|
+
|
|
812
|
+
## Support
|
|
813
|
+
|
|
814
|
+
- 📧 Email: support@openlifelog.com
|
|
815
|
+
- 🐛 Issues: [GitHub Issues](https://github.com/kukicado/openlifelog/issues)
|
|
816
|
+
- 📖 Documentation: [docs.openlifelog.com](https://docs.openlifelog.com)
|
|
817
|
+
|
|
818
|
+
## Changelog
|
|
819
|
+
|
|
820
|
+
See [CHANGELOG.md](./CHANGELOG.md) for version history and updates.
|
|
821
|
+
|
|
822
|
+
---
|
|
823
|
+
|
|
824
|
+
Made with ❤️ by the OpenLifeLog team
|