Package Exports
- @openlifelog/sdk
Readme
OpenLifeLog TypeScript SDK
A comprehensive, type-safe TypeScript SDK for the OpenLifeLog API - the complete fitness and nutrition tracking platform.
Features
- 🎯 Type-Safe - Full TypeScript support with comprehensive type definitions
- 🔄 Auto-Pagination - Automatic pagination for large data sets
- 📏 Unit Conversion - Automatic conversion between metric and imperial units
- 🔁 Auto-Retry - Automatic retries with exponential backoff for failed requests
- ❌ Error Handling - Rich error classes with detailed error information
- 🚀 Modern API - Intuitive, resource-based API design
- 📦 Tree-Shakeable - Modular design supports tree-shaking
- ⚡ Fast - Optimized for performance with HTTP/2 support
Installation
npm install @openlifelog/sdkyarn add @openlifelog/sdkpnpm add @openlifelog/sdkQuick Start
Option 1: With JWT Token (Recommended for apps with existing auth)
If your app already handles authentication and you have a JWT token:
import { OpenLifeLog } from '@openlifelog/sdk';
// Initialize with JWT token
const client = new OpenLifeLog({
apiKey: 'your-jwt-token-here',
baseUrl: 'http://localhost:8080',
measurementSystem: 'metric', // or 'imperial'
});
// Ready to use immediately - no login needed!
const user = await client.users.me();
await client.logFood({ ... });Option 2: With SDK Authentication
If you want the SDK to handle authentication:
import { OpenLifeLog } from '@openlifelog/sdk';
// Initialize without token
const client = new OpenLifeLog({
baseUrl: 'http://localhost:8080',
measurementSystem: 'metric',
});
// Login to get token
const { token, user } = await client.auth.login({
email: 'john@example.com',
password: 'securePassword123',
});
// Token is automatically set, ready to use!
await client.logFood({ ... });Option 3: Set Token Later
import { OpenLifeLog } from '@openlifelog/sdk';
// Initialize without token
const client = new OpenLifeLog({
baseUrl: 'http://localhost:8080',
});
// Set token later (e.g., after your own auth flow)
client.setApiKey('jwt-token-from-your-auth-system');
// Now ready to use
const user = await client.users.me();Core Concepts
Authentication
Using External Authentication
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:
// Example: Using with Auth0
import { useAuth0 } from '@auth0/auth0-react';
import { OpenLifeLog } from '@openlifelog/sdk';
function MyComponent() {
const { getAccessTokenSilently } = useAuth0();
const getClient = async () => {
const token = await getAccessTokenSilently();
return new OpenLifeLog({
apiKey: token,
baseUrl: process.env.NEXT_PUBLIC_API_URL,
});
};
// Use the client...
}// Example: Using with Next.js session
import { getSession } from 'next-auth/react';
import { OpenLifeLog } from '@openlifelog/sdk';
export async function getServerSideProps(context) {
const session = await getSession(context);
const client = new OpenLifeLog({
apiKey: session.accessToken,
baseUrl: process.env.API_URL,
});
const user = await client.users.me();
// ...
}Using SDK Authentication
If you want the SDK to handle authentication directly:
// Sign up a new user
const { token, user } = await client.auth.signup({
name: 'John Doe',
email: 'john@example.com',
password: 'securePassword123',
});
// Login
const { token, user } = await client.auth.login({
email: 'john@example.com',
password: 'securePassword123',
});
// The token is automatically set after login/signup
// Request password reset
await client.auth.requestPasswordReset({
email: 'john@example.com',
});
// Logout (clears token)
client.auth.logout();Dynamic Token Updates
You can update the token at any time, useful for token refresh scenarios:
// Initial setup
const client = new OpenLifeLog({
apiKey: 'initial-token',
baseUrl: 'http://localhost:8080',
});
// Later, when token refreshes
client.setApiKey('new-refreshed-token');
// Continue using the client with new token
await client.users.me();User Management
// Get current user
const user = await client.users.me();
// Update profile
await client.users.update({
name: 'Jane Doe',
isOnboarded: true,
});
// Get preferences
const prefs = await client.users.getPreferences();
console.log(prefs.measurementSystem); // 'metric' or 'imperial'
// Update preferences
await client.users.updatePreferences({
measurementSystem: 'imperial',
});Food & Nutrition Tracking
Search and Browse Foods
// Search for foods
const { data: foods } = await client.foods.search({
q: 'chicken breast',
limit: 10,
});
// List all foods with pagination
const { data, pageInfo } = await client.foods.list({
limit: 20,
search: 'protein',
});
// Get specific food details
const food = await client.foods.get('food-id');
console.log(food.nutrients.protein); // Protein in grams
// Add to favorites
await client.foods.addToFavorites(food.id);Log Food
// Simple way - using the convenience method
const log = await client.logFood({
foodId: 'food-uuid',
name: 'Chicken Breast',
servingSizeName: 'breast (200g)',
quantity: 1.5,
unitGrams: 200,
mealType: 'lunch',
notes: 'Grilled with olive oil',
});
// Or use the resource directly
const log = await client.foodLogs.create({
foodId: 'food-uuid',
name: 'Chicken Breast',
servingSizeName: 'breast (200g)',
quantity: 1.5,
unitGrams: 200,
mealType: 'lunch',
});Get Nutrition Summary
// Get today's nutrition
const summary = await client.getTodayNutrition();
console.log(`Calories: ${summary.totalCalories}`);
console.log(`Protein: ${summary.totalProtein}g`);
// Get specific date
const summary = await client.foodLogs.getDailySummary({
date: '2024-01-15',
});
// Filter by meal type
const lunchSummary = await client.foodLogs.getDailySummary({
date: '2024-01-15',
mealType: 'lunch',
});Exercise & Workout Management
Exercises
// List exercises
const { data: exercises } = await client.exercises.list({
category: 'strength',
muscleGroup: 'chest',
});
// Search exercises
const { data } = await client.exercises.search({
q: 'bench press',
});
// Create custom exercise
const exercise = await client.exercises.create({
name: 'Custom Push-up Variation',
category: 'strength',
primaryMuscles: ['chest', 'triceps'],
secondaryMuscles: ['front_delts'],
equipment: ['bodyweight'],
capabilities: {
supportsWeight: false,
supportsBodyweightOnly: true,
supportsAssistance: false,
supportsDistance: false,
supportsDuration: true,
supportsTempo: true,
},
});Workout Templates
// Create a workout template
const workout = await client.workouts.create({
name: 'Upper Body A',
description: 'Push-focused upper body workout',
type: 'strength',
});
// Add exercises to workout
await client.workouts.addExercises(workout.id, {
exercises: [
{
exerciseId: 'bench-press-id',
orderIndex: 1,
workoutFormat: 'straight_sets',
setsData: [
{ order: 1, reps: 5, weight: 100, restSeconds: 180 },
{ order: 2, reps: 5, weight: 100, restSeconds: 180 },
{ order: 3, reps: 5, weight: 100, restSeconds: 180 },
],
},
],
});
// Clone a workout
const clonedWorkout = await client.workouts.clone(workout.id);Workout Sessions (Performance Tracking)
// Start a workout session
const session = await client.sessions.start({
workoutId: 'workout-id',
userBodyweight: 82.5, // Will be converted based on user preference
notes: 'Feeling strong today',
mood: 'energetic',
});
// Update session during workout
await client.sessions.update(session.id, {
notes: 'Added extra set to bench press',
});
// Complete the session
await client.sessions.complete(session.id, 'Great workout!');
// Get session details
const completedSession = await client.sessions.get(session.id);
// Get exercise history
const history = await client.sessions.getExerciseHistory('exercise-id');
// Get personal records
const prs = await client.sessions.getPersonalRecords();Training Programs
// List program templates
const { data: templates } = await client.programs.listTemplates();
// Create a program
const program = await client.programs.create({
name: '5/3/1 Strength Program',
description: "Wendler's 5/3/1 for main lifts",
schedule: [
{
week: 1,
day: 1,
name: 'Squat Day',
exercises: [
{
exerciseId: 'squat-id',
orderIndex: 1,
sets: [
{ order: 1, reps: 5, weight: 100 },
{ order: 2, reps: 5, weight: 110 },
{ order: 3, reps: 5, weight: 120 },
],
},
],
},
],
metadata: {
difficulty: 'intermediate',
type: 'strength',
},
});
// Enroll in program
const enrollment = await client.programs.enroll(program.id, {
startDate: '2024-01-15',
});
// Get current week schedule
const currentWeek = await client.programs.getCurrentWeek(enrollment.id);
// Get enrollment progress
const progress = await client.programs.getEnrollmentProgress(enrollment.id);
console.log(`${progress.completionPercentage}% complete`);Metrics & Goals
Track Metrics
// List available metrics
const { data: metrics } = await client.metrics.list();
// Log a metric (e.g., body weight)
await client.metrics.log({
metricKey: 'body_weight',
value: 82.5,
metadata: {
time: 'morning',
conditions: 'fasted',
},
loggedAt: new Date().toISOString(),
});
// Get daily metric aggregate
const dailyWeight = await client.metrics.getDailyMetric('body_weight', {
date: '2024-01-15',
});Set Goals
// Create a goal
await client.goals.create({
metricKey: 'body_weight',
targetValue: 75.0,
targetType: 'exact',
effectiveFrom: '2024-01-15',
});
// Get today's progress
const progress = await client.getTodayProgress();
console.log(progress.goals);
// Set nutrition goals
await client.goals.createNutritionGoals({
effectiveDate: '2024-01-15',
goals: {
calories: { type: 'exact', value: 2200 },
protein: { type: 'min', value: 180 },
carbs: { type: 'range', min: 200, max: 300 },
fat: { type: 'range', min: 60, max: 80 },
},
});
// Get nutrition progress
const nutritionProgress = await client.getTodayNutritionProgress();AI Insights
// Get AI-powered nutrition insights
const insights = await client.ai.getFoodInsights({
startDate: '2024-01-01',
endDate: '2024-01-15',
});
console.log('Insights:', insights.insights);
console.log('Recommendations:', insights.recommendations);Advanced Features
Auto-Pagination
// Iterate through all foods automatically
for await (const food of client.foods.listAutoPaginate({ limit: 100 })) {
console.log(food.name);
}
// Or get all pages at once (use with caution for large datasets)
const allFoods = await client.foods.listAutoPaginate().toArray();Unit Conversion
The SDK automatically handles unit conversion based on your measurement system preference:
// Set measurement system
client.setMeasurementSystem('imperial');
// Get unit converter
const converter = client.getUnitConverter();
// Manual conversion
const kgValue = converter.weightToMetric(150); // Convert 150 lbs to kg
const lbsValue = converter.weightFromMetric(68); // Convert 68 kg to lbs
// Get unit labels
console.log(converter.getWeightUnit()); // 'lbs' or 'kg'
console.log(converter.getDistanceUnit()); // 'mi' or 'm'Error Handling
import {
OpenLifeLogError,
AuthenticationError,
ValidationError,
NotFoundError,
} from '@openlifelog/sdk';
try {
await client.foods.get('invalid-id');
} catch (error) {
if (error instanceof NotFoundError) {
console.error('Food not found');
} else if (error instanceof AuthenticationError) {
console.error('Please login first');
} else if (error instanceof ValidationError) {
console.error('Validation errors:', error.validationErrors);
} else if (error instanceof OpenLifeLogError) {
console.error('API error:', error.message, error.statusCode);
}
}Configuration Options
const client = new OpenLifeLog({
// Required: API base URL
baseUrl: 'http://localhost:8080',
// Optional: JWT token - Use this if you already have a token!
// This is the recommended approach for apps with existing authentication
apiKey: 'your-jwt-token',
// Optional: Measurement system preference
measurementSystem: 'metric', // or 'imperial'
// Optional: Automatic unit conversion
autoConvertUnits: true,
// Optional: Request timeout (ms)
timeout: 30000,
// Optional: Max retry attempts
maxRetries: 3,
// Optional: Enable retries
enableRetries: true,
// Optional: Custom headers
headers: {
'X-Custom-Header': 'value',
},
// Optional: Enable debug logging
debug: false,
// Optional: Custom fetch implementation
fetch: customFetch,
});Complete Example: Tracking a Full Day
import { OpenLifeLog } from '@openlifelog/sdk';
const client = new OpenLifeLog({
baseUrl: 'http://localhost:8080',
});
// Login
await client.auth.login({
email: 'athlete@example.com',
password: 'securePassword123',
});
// --- Morning: Log body weight ---
await client.metrics.log({
metricKey: 'body_weight',
value: 82.3,
metadata: { time: 'morning', conditions: 'fasted' },
});
// --- Breakfast: Log food ---
await client.logFood({
foodId: 'oatmeal-id',
name: 'Oatmeal with Berries',
quantity: 1,
unitGrams: 250,
mealType: 'breakfast',
});
// --- Workout: Track training session ---
const session = await client.sessions.start({
workoutId: 'upper-body-workout-id',
userBodyweight: 82.3,
mood: 'energetic',
});
// ... perform workout ...
await client.sessions.complete(session.id, 'Hit new PR on bench press!');
// --- Post-workout: Log protein shake ---
await client.logFood({
foodId: 'protein-shake-id',
name: 'Protein Shake',
quantity: 1,
unitGrams: 300,
mealType: 'snack',
notes: 'Post-workout',
});
// --- Evening: Check progress ---
const nutritionSummary = await client.getTodayNutrition();
console.log('Daily totals:', {
calories: nutritionSummary.totalCalories,
protein: nutritionSummary.totalProtein,
carbs: nutritionSummary.totalCarbs,
fat: nutritionSummary.totalFat,
});
const goalProgress = await client.getTodayNutritionProgress();
console.log('Goal progress:', goalProgress.progress);
// Get personal records
const prs = await client.sessions.getPersonalRecords();
console.log('Personal Records:', prs);TypeScript Support
The SDK is written in TypeScript and provides comprehensive type definitions:
import type {
User,
Food,
FoodLog,
Exercise,
Workout,
WorkoutSession,
NutritionGoal,
MetricEvent,
} from '@openlifelog/sdk';
// All API responses are fully typed
const user: User = await client.users.me();
const food: Food = await client.foods.get('food-id');
const log: FoodLog = await client.logFood({...});Best Practices
1. Error Handling
Always wrap API calls in try-catch blocks:
try {
const user = await client.users.me();
} catch (error) {
if (error instanceof AuthenticationError) {
// Redirect to login
}
}2. Pagination
Use auto-pagination for large datasets:
// Good: Memory efficient
for await (const food of client.foods.listAutoPaginate()) {
processFood(food);
}
// Avoid: Loads all data at once
const allFoods = await client.foods.listAutoPaginate().toArray();3. Unit Conversion
Let the SDK handle unit conversion automatically:
// Set preference once
client.setMeasurementSystem('imperial');
// SDK handles conversion automatically
const session = await client.sessions.create({
userBodyweight: 180, // SDK knows this is lbs and converts to kg for API
});4. Reuse Client Instance
Create one client instance and reuse it:
// Good: Single instance
const client = new OpenLifeLog(config);
export default client;
// Avoid: Creating multiple instances
function getUser() {
const client = new OpenLifeLog(config); // Don't do this
return client.users.me();
}Framework Integration
Next.js (App Router)
// lib/openlifelog.ts
import { OpenLifeLog } from '@openlifelog/sdk';
export const getClient = (token?: string) => {
return new OpenLifeLog({
baseUrl: process.env.NEXT_PUBLIC_API_URL!,
apiKey: token,
});
};
// app/actions.ts
'use server';
import { cookies } from 'next/headers';
import { getClient } from '@/lib/openlifelog';
export async function getUserProfile() {
const token = cookies().get('token')?.value;
const client = getClient(token);
return await client.users.me();
}React Hook
// hooks/useOpenLifeLog.ts
import { useMemo } from 'react';
import { OpenLifeLog } from '@openlifelog/sdk';
import { useAuth } from './useAuth';
export function useOpenLifeLog() {
const { token } = useAuth();
return useMemo(
() =>
new OpenLifeLog({
baseUrl: process.env.NEXT_PUBLIC_API_URL,
apiKey: token,
}),
[token]
);
}
// In your component
function MyComponent() {
const client = useOpenLifeLog();
const fetchData = async () => {
const user = await client.users.me();
// ...
};
// ...
}API Reference
Full API documentation is available at:
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for details.
License
MIT License - see LICENSE for details.
Support
- 📧 Email: support@openlifelog.com
- 🐛 Issues: GitHub Issues
- 📖 Documentation: docs.openlifelog.com
Changelog
See CHANGELOG.md for version history and updates.
Made with ❤️ by the OpenLifeLog team