JSPM

@openlifelog/sdk

1.0.2
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 350
  • Score
    100M100P100Q67051F
  • License MIT

TypeScript SDK for the OpenLifeLog API - A comprehensive fitness and nutrition tracking platform

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.

TypeScript 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

npm install @openlifelog/sdk
yarn add @openlifelog/sdk
pnpm add @openlifelog/sdk

Quick Start

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

Changelog

See CHANGELOG.md for version history and updates.


Made with ❤️ by the OpenLifeLog team