@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 ADDED
@@ -0,0 +1,624 @@
1
+ # OpenLifeLog SDK - Quick Examples
2
+
3
+ ## Table of Contents
4
+
5
+ - [Authentication](#authentication)
6
+ - [Food Logging](#food-logging)
7
+ - [Workout Tracking](#workout-tracking)
8
+ - [Goals & Progress](#goals--progress)
9
+ - [Metrics](#metrics)
10
+ - [External Auth Integration](#external-auth-integration)
11
+
12
+ ## Authentication
13
+
14
+ ### Using with JWT Token (External Auth)
15
+
16
+ If your app already handles authentication:
17
+
18
+ ```typescript
19
+ import { OpenLifeLog } from '@openlifelog/sdk';
20
+
21
+ // Simply pass your JWT token
22
+ const client = new OpenLifeLog({
23
+ apiKey: 'your-jwt-token-here',
24
+ baseUrl: 'http://localhost:8080',
25
+ });
26
+
27
+ // Ready to use!
28
+ const user = await client.users.me();
29
+ ```
30
+
31
+ ### Set Token Later
32
+
33
+ ```typescript
34
+ const client = new OpenLifeLog({
35
+ baseUrl: 'http://localhost:8080',
36
+ });
37
+
38
+ // After your auth flow completes
39
+ client.setApiKey('jwt-token-from-your-system');
40
+
41
+ // Now ready
42
+ await client.logFood({ ... });
43
+ ```
44
+
45
+ ### Sign Up
46
+
47
+ ```typescript
48
+ const { token, user } = await client.auth.signup({
49
+ name: 'John Doe',
50
+ email: 'john@example.com',
51
+ password: 'securePassword123',
52
+ });
53
+ ```
54
+
55
+ ### Login
56
+
57
+ ```typescript
58
+ const { token, user } = await client.auth.login({
59
+ email: 'john@example.com',
60
+ password: 'securePassword123',
61
+ });
62
+ ```
63
+
64
+ ## Food Logging
65
+
66
+ ### Simple Food Logging
67
+
68
+ ```typescript
69
+ // Quick and easy
70
+ await client.logFood({
71
+ foodId: 'chicken-breast-id',
72
+ name: 'Chicken Breast',
73
+ quantity: 1.5,
74
+ unitGrams: 200,
75
+ mealType: 'lunch',
76
+ });
77
+ ```
78
+
79
+ ### Search and Log Food
80
+
81
+ ```typescript
82
+ // Search for food
83
+ const { data: foods } = await client.foods.search({
84
+ q: 'chicken breast',
85
+ limit: 5,
86
+ });
87
+
88
+ // Log the first result
89
+ if (foods.length > 0) {
90
+ const food = foods[0];
91
+ await client.logFood({
92
+ foodId: food.id,
93
+ name: food.name,
94
+ servingSizeName: food.servingSizes[0].name,
95
+ quantity: 1,
96
+ unitGrams: food.servingSizes[0].grams,
97
+ mealType: 'lunch',
98
+ });
99
+ }
100
+ ```
101
+
102
+ ### Get Daily Summary
103
+
104
+ ```typescript
105
+ // Today's nutrition
106
+ const summary = await client.getTodayNutrition();
107
+ console.log(`
108
+ Calories: ${summary.totalCalories}
109
+ Protein: ${summary.totalProtein}g
110
+ Carbs: ${summary.totalCarbs}g
111
+ Fat: ${summary.totalFat}g
112
+ `);
113
+ ```
114
+
115
+ ### List Today's Meals
116
+
117
+ ```typescript
118
+ const today = new Date().toISOString().split('T')[0];
119
+ const { data: logs } = await client.foodLogs.list({
120
+ date: today,
121
+ });
122
+
123
+ console.log('Meals:');
124
+ logs.forEach((log) => {
125
+ console.log(`${log.mealType}: ${log.name} - ${log.nutrients.calories} cal`);
126
+ });
127
+ ```
128
+
129
+ ## Workout Tracking
130
+
131
+ ### Create and Start Workout Session
132
+
133
+ ```typescript
134
+ // Option 1: From a workout template
135
+ const session = await client.sessions.start({
136
+ workoutId: 'my-workout-template-id',
137
+ userBodyweight: 82.5,
138
+ notes: 'Feeling strong',
139
+ mood: 'energetic',
140
+ location: 'Home gym',
141
+ });
142
+
143
+ // Option 2: Quick workout without template
144
+ const session = await client.sessions.start({
145
+ name: 'Quick Upper Body',
146
+ userBodyweight: 82.5,
147
+ });
148
+ ```
149
+
150
+ ### Complete a Workout
151
+
152
+ ```typescript
153
+ await client.sessions.complete(session.id, 'Great workout! Hit new PR.');
154
+ ```
155
+
156
+ ### View Workout History
157
+
158
+ ```typescript
159
+ const { data: sessions } = await client.sessions.list({
160
+ startDate: '2024-01-01',
161
+ endDate: '2024-01-31',
162
+ status: 'completed',
163
+ });
164
+
165
+ console.log(`Completed ${sessions.length} workouts this month`);
166
+ ```
167
+
168
+ ### Get Personal Records
169
+
170
+ ```typescript
171
+ const { data: prs } = await client.sessions.getPersonalRecords();
172
+
173
+ prs.forEach((pr) => {
174
+ console.log(`${pr.exerciseName}: ${pr.value} ${pr.unit} (${pr.achievedDate})`);
175
+ });
176
+ ```
177
+
178
+ ### Exercise Performance History
179
+
180
+ ```typescript
181
+ const history = await client.sessions.getExerciseHistory('bench-press-id');
182
+
183
+ console.log(`Bench Press History:`);
184
+ history.history.forEach((entry) => {
185
+ console.log(`${entry.date}: ${entry.totalVolume}kg total volume`);
186
+ });
187
+ ```
188
+
189
+ ## Goals & Progress
190
+
191
+ ### Set Daily Nutrition Goals
192
+
193
+ ```typescript
194
+ await client.goals.createNutritionGoals({
195
+ effectiveDate: '2024-01-15',
196
+ goals: {
197
+ calories: { type: 'exact', value: 2200 },
198
+ protein: { type: 'min', value: 180 },
199
+ carbs: { type: 'range', min: 200, max: 300 },
200
+ fat: { type: 'range', min: 60, max: 80 },
201
+ },
202
+ notes: 'Maintenance phase',
203
+ });
204
+ ```
205
+
206
+ ### Check Today's Progress
207
+
208
+ ```typescript
209
+ const progress = await client.getTodayNutritionProgress();
210
+
211
+ console.log(`Goal Progress:`);
212
+ progress.progress.forEach((p) => {
213
+ const emoji = p.status === 'on_track' ? '✅' : p.status === 'below' ? '⬇️' : '⬆️';
214
+ console.log(`${emoji} ${p.nutrientKey}: ${p.current}/${p.target || 'goal'}`);
215
+ });
216
+ ```
217
+
218
+ ### Set Body Weight Goal
219
+
220
+ ```typescript
221
+ await client.goals.create({
222
+ metricKey: 'body_weight',
223
+ targetValue: 75.0,
224
+ targetType: 'exact',
225
+ effectiveFrom: '2024-01-15',
226
+ });
227
+ ```
228
+
229
+ ## Metrics
230
+
231
+ ### Log Body Weight
232
+
233
+ ```typescript
234
+ await client.metrics.log({
235
+ metricKey: 'body_weight',
236
+ value: 82.5,
237
+ metadata: {
238
+ time: 'morning',
239
+ conditions: 'fasted',
240
+ },
241
+ });
242
+ ```
243
+
244
+ ### Log Multiple Metrics at Once
245
+
246
+ ```typescript
247
+ await client.metrics.bulkCreateEvents({
248
+ metrics: [
249
+ { metricKey: 'body_weight', value: 82.5 },
250
+ { metricKey: 'body_fat_percentage', value: 15.2 },
251
+ { metricKey: 'sleep_hours', value: 7.5 },
252
+ ],
253
+ });
254
+ ```
255
+
256
+ ### View Metric History
257
+
258
+ ```typescript
259
+ const { data: events } = await client.metrics.listEvents({
260
+ metricKey: 'body_weight',
261
+ startDate: '2024-01-01',
262
+ endDate: '2024-01-31',
263
+ });
264
+
265
+ console.log('Weight History:');
266
+ events.forEach((event) => {
267
+ console.log(`${event.loggedAt}: ${event.value}kg`);
268
+ });
269
+ ```
270
+
271
+ ## Complete Daily Routine Example
272
+
273
+ ```typescript
274
+ import { OpenLifeLog } from '@openlifelog/sdk';
275
+
276
+ const client = new OpenLifeLog({
277
+ baseUrl: 'http://localhost:8080',
278
+ });
279
+
280
+ async function dailyRoutine() {
281
+ // Login
282
+ await client.auth.login({
283
+ email: 'athlete@example.com',
284
+ password: 'password123',
285
+ });
286
+
287
+ // Morning: Log weight
288
+ await client.metrics.log({
289
+ metricKey: 'body_weight',
290
+ value: 82.3,
291
+ metadata: { time: 'morning' },
292
+ });
293
+
294
+ // Breakfast
295
+ await client.logFood({
296
+ foodId: 'oatmeal-id',
297
+ name: 'Oatmeal',
298
+ quantity: 1,
299
+ unitGrams: 250,
300
+ mealType: 'breakfast',
301
+ });
302
+
303
+ // Morning workout
304
+ const session = await client.sessions.start({
305
+ workoutId: 'upper-body-id',
306
+ userBodyweight: 82.3,
307
+ });
308
+
309
+ // After workout
310
+ await client.sessions.complete(session.id);
311
+
312
+ // Post-workout shake
313
+ await client.logFood({
314
+ foodId: 'protein-shake-id',
315
+ name: 'Protein Shake',
316
+ quantity: 1,
317
+ unitGrams: 300,
318
+ mealType: 'snack',
319
+ });
320
+
321
+ // Check progress
322
+ const summary = await client.getTodayNutrition();
323
+ const progress = await client.getTodayNutritionProgress();
324
+
325
+ console.log('Daily Summary:', summary);
326
+ console.log('Goal Progress:', progress);
327
+ }
328
+
329
+ dailyRoutine().catch(console.error);
330
+ ```
331
+
332
+ ## Pagination Examples
333
+
334
+ ### Auto-pagination for All Foods
335
+
336
+ ```typescript
337
+ // Automatically handles pagination
338
+ for await (const food of client.foods.listAutoPaginate()) {
339
+ console.log(food.name);
340
+ }
341
+ ```
342
+
343
+ ### Manual Pagination
344
+
345
+ ```typescript
346
+ let cursor: string | undefined;
347
+ let hasMore = true;
348
+
349
+ while (hasMore) {
350
+ const { data, pageInfo } = await client.foods.list({
351
+ limit: 100,
352
+ cursor,
353
+ });
354
+
355
+ data.forEach((food) => console.log(food.name));
356
+
357
+ cursor = pageInfo.nextCursor;
358
+ hasMore = pageInfo.hasMore;
359
+ }
360
+ ```
361
+
362
+ ## Error Handling Example
363
+
364
+ ```typescript
365
+ import {
366
+ OpenLifeLogError,
367
+ AuthenticationError,
368
+ ValidationError,
369
+ NotFoundError,
370
+ } from '@openlifelog/sdk';
371
+
372
+ async function safeApiCall() {
373
+ try {
374
+ const food = await client.foods.get('food-id');
375
+ return food;
376
+ } catch (error) {
377
+ if (error instanceof NotFoundError) {
378
+ console.error('Food not found');
379
+ return null;
380
+ } else if (error instanceof AuthenticationError) {
381
+ console.error('Please login');
382
+ // Redirect to login
383
+ return null;
384
+ } else if (error instanceof ValidationError) {
385
+ console.error('Validation error:', error.validationErrors);
386
+ return null;
387
+ } else if (error instanceof OpenLifeLogError) {
388
+ console.error('API error:', error.message);
389
+ return null;
390
+ } else {
391
+ console.error('Unknown error:', error);
392
+ throw error;
393
+ }
394
+ }
395
+ }
396
+ ```
397
+
398
+ ## Unit Conversion Examples
399
+
400
+ ```typescript
401
+ // Set measurement system
402
+ client.setMeasurementSystem('imperial');
403
+
404
+ // Get converter
405
+ const converter = client.getUnitConverter();
406
+
407
+ // Weight conversion
408
+ const kgToLbs = converter.weightFromMetric(82.5); // 181.88 lbs
409
+ const lbsToKg = converter.weightToMetric(180); // 81.65 kg
410
+
411
+ // Distance conversion
412
+ const metersToMiles = converter.distanceFromMetric(5000, true); // 3.11 miles
413
+ const milesToMeters = converter.distanceToMetric(3.1, true); // 4989.0 meters
414
+
415
+ // Get unit labels
416
+ console.log(converter.getWeightUnit()); // 'lbs'
417
+ console.log(converter.getDistanceUnit(true)); // 'mi'
418
+ ```
419
+
420
+ ## External Auth Integration
421
+
422
+ ### Using with Auth0
423
+
424
+ ```typescript
425
+ import { useAuth0 } from '@auth0/auth0-react';
426
+ import { OpenLifeLog } from '@openlifelog/sdk';
427
+ import { useMemo } from 'react';
428
+
429
+ function useOpenLifeLog() {
430
+ const { getAccessTokenSilently, isAuthenticated } = useAuth0();
431
+
432
+ return useMemo(() => {
433
+ if (!isAuthenticated) return null;
434
+
435
+ return {
436
+ getClient: async () => {
437
+ const token = await getAccessTokenSilently();
438
+ return new OpenLifeLog({
439
+ apiKey: token,
440
+ baseUrl: process.env.NEXT_PUBLIC_API_URL!,
441
+ });
442
+ },
443
+ };
444
+ }, [isAuthenticated, getAccessTokenSilently]);
445
+ }
446
+
447
+ // Usage in component
448
+ function MyComponent() {
449
+ const openlifelog = useOpenLifeLog();
450
+
451
+ const loadData = async () => {
452
+ if (!openlifelog) return;
453
+
454
+ const client = await openlifelog.getClient();
455
+ const user = await client.users.me();
456
+ const summary = await client.getTodayNutrition();
457
+ // ...
458
+ };
459
+
460
+ return <div>...</div>;
461
+ }
462
+ ```
463
+
464
+ ### Using with Clerk
465
+
466
+ ```typescript
467
+ import { useAuth } from '@clerk/nextjs';
468
+ import { OpenLifeLog } from '@openlifelog/sdk';
469
+
470
+ function useOpenLifeLog() {
471
+ const { getToken } = useAuth();
472
+
473
+ const getClient = async () => {
474
+ const token = await getToken();
475
+ return new OpenLifeLog({
476
+ apiKey: token!,
477
+ baseUrl: process.env.NEXT_PUBLIC_API_URL!,
478
+ });
479
+ };
480
+
481
+ return { getClient };
482
+ }
483
+ ```
484
+
485
+ ### Using with NextAuth.js
486
+
487
+ ```typescript
488
+ // app/api/nutrition/route.ts
489
+ import { getServerSession } from 'next-auth/next';
490
+ import { OpenLifeLog } from '@openlifelog/sdk';
491
+ import { authOptions } from '@/lib/auth';
492
+
493
+ export async function GET() {
494
+ const session = await getServerSession(authOptions);
495
+
496
+ if (!session?.accessToken) {
497
+ return Response.json({ error: 'Unauthorized' }, { status: 401 });
498
+ }
499
+
500
+ const client = new OpenLifeLog({
501
+ apiKey: session.accessToken,
502
+ baseUrl: process.env.API_URL!,
503
+ });
504
+
505
+ const summary = await client.getTodayNutrition();
506
+ return Response.json(summary);
507
+ }
508
+ ```
509
+
510
+ ### Using with Firebase Auth
511
+
512
+ ```typescript
513
+ import { getAuth } from 'firebase/auth';
514
+ import { OpenLifeLog } from '@openlifelog/sdk';
515
+
516
+ async function getClient() {
517
+ const auth = getAuth();
518
+ const user = auth.currentUser;
519
+
520
+ if (!user) {
521
+ throw new Error('Not authenticated');
522
+ }
523
+
524
+ const token = await user.getIdToken();
525
+
526
+ return new OpenLifeLog({
527
+ apiKey: token,
528
+ baseUrl: process.env.NEXT_PUBLIC_API_URL!,
529
+ });
530
+ }
531
+
532
+ // Usage
533
+ const client = await getClient();
534
+ const user = await client.users.me();
535
+ ```
536
+
537
+ ### Using with Supabase Auth
538
+
539
+ ```typescript
540
+ import { createClient } from '@supabase/supabase-js';
541
+ import { OpenLifeLog } from '@openlifelog/sdk';
542
+
543
+ const supabase = createClient(
544
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
545
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
546
+ );
547
+
548
+ async function getClient() {
549
+ const {
550
+ data: { session },
551
+ } = await supabase.auth.getSession();
552
+
553
+ if (!session) {
554
+ throw new Error('Not authenticated');
555
+ }
556
+
557
+ return new OpenLifeLog({
558
+ apiKey: session.access_token,
559
+ baseUrl: process.env.NEXT_PUBLIC_API_URL!,
560
+ });
561
+ }
562
+ ```
563
+
564
+ ### Custom Token Refresh
565
+
566
+ ```typescript
567
+ import { OpenLifeLog } from '@openlifelog/sdk';
568
+
569
+ class AuthenticatedOpenLifeLog {
570
+ private client: OpenLifeLog;
571
+ private refreshToken: string;
572
+
573
+ constructor(token: string, refreshToken: string) {
574
+ this.client = new OpenLifeLog({
575
+ apiKey: token,
576
+ baseUrl: process.env.API_URL!,
577
+ });
578
+ this.refreshToken = refreshToken;
579
+ }
580
+
581
+ async refreshAccessToken() {
582
+ // Your token refresh logic
583
+ const response = await fetch('/api/auth/refresh', {
584
+ method: 'POST',
585
+ body: JSON.stringify({ refreshToken: this.refreshToken }),
586
+ });
587
+
588
+ const { accessToken } = await response.json();
589
+
590
+ // Update client with new token
591
+ this.client.setApiKey(accessToken);
592
+
593
+ return accessToken;
594
+ }
595
+
596
+ async withRetry<T>(operation: () => Promise<T>): Promise<T> {
597
+ try {
598
+ return await operation();
599
+ } catch (error: any) {
600
+ // If auth error, try refreshing token once
601
+ if (error.statusCode === 401) {
602
+ await this.refreshAccessToken();
603
+ return await operation();
604
+ }
605
+ throw error;
606
+ }
607
+ }
608
+
609
+ // Expose client methods with auto-refresh
610
+ async logFood(data: any) {
611
+ return this.withRetry(() => this.client.logFood(data));
612
+ }
613
+
614
+ async getTodayNutrition() {
615
+ return this.withRetry(() => this.client.getTodayNutrition());
616
+ }
617
+
618
+ // Add more methods as needed...
619
+ }
620
+
621
+ // Usage
622
+ const client = new AuthenticatedOpenLifeLog(accessToken, refreshToken);
623
+ await client.logFood({ ... }); // Automatically refreshes token if needed
624
+ ```