npm package: @openlifelog/sdk
Description: TypeScript SDK for the OpenLifeLog API - A comprehensive fitness and nutrition tracking platform
Author: OpenLifeLog
Latest version: 1.0.10

# OpenLifeLog TypeScript SDK

> A comprehensive, type-safe TypeScript SDK for the OpenLifeLog API - the complete fitness and nutrition tracking platform.

[![TypeScript](https://img.shields.io/badge/TypeScript-5.3+-blue.svg)](https://www.typescriptlang.org/)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)

## 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

```bash
npm install @openlifelog/sdk
```

```bash
yarn add @openlifelog/sdk
```

```bash
pnpm add @openlifelog/sdk
```

## Quick Start

### Option 1: With JWT Token (Recommended for apps with existing auth)

If your app already handles authentication and you have a JWT token:

```typescript
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:

```typescript
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

```typescript
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:

```typescript
// 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...
}
```

```typescript
// 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:

```typescript
// 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:

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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)

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
// 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:

```typescript
// 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

```typescript
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

```typescript
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

```typescript
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:

```typescript
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:

```typescript
try {
  const user = await client.users.me();
} catch (error) {
  if (error instanceof AuthenticationError) {
    // Redirect to login
  }
}
```

### 2. **Pagination**

Use auto-pagination for large datasets:

```typescript
// 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:

```typescript
// 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:

```typescript
// 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)

```typescript
// 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

```typescript
// 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:
- [API Documentation](https://docs.openlifelog.com)
- [TypeScript Types](./types)

## Contributing

Contributions are welcome! Please see [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.

## License

MIT License - see [LICENSE](../../LICENSE) for details.

## Support

- 📧 Email: support@openlifelog.com
- 🐛 Issues: [GitHub Issues](https://github.com/kukicado/openlifelog/issues)
- 📖 Documentation: [docs.openlifelog.com](https://docs.openlifelog.com)

## Changelog

See [CHANGELOG.md](./CHANGELOG.md) for version history and updates.

---

Made with ❤️ by the OpenLifeLog team