npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@harmoni-org/sdk

v0.0.3

Published

Harmoni SDK - API abstraction layer with utilities and helpers

Readme

🎵 Harmoni SDK

A powerful, type-safe SDK for seamless backend integration with comprehensive utilities and tools.

npm version npm downloads License: MIT CI TypeScript

✨ Features

  • 🚀 Modern Architecture - Built with TypeScript, supports ESM & CJS
  • 🔐 Real Token Refresh - Auto-refresh with proper request retry on 401
  • 💾 Session Persistence - Automatic token storage & restoration across app reloads
  • 📱 Cross-Platform - Works in browsers, React Native, Expo, Node.js, and more
  • 🔄 Smart Retry - Exponential backoff that doesn't break POST requests
  • 📤 Upload Support - Automatic FormData handling with progress tracking
  • 🌐 SSR-Safe - Works in Node.js, browsers, and edge runtimes
  • 🎯 Type Safety - Full TypeScript support with comprehensive types
  • 🧩 Modular Design - Easy to extend with new modules
  • 🎬 Watch Together - Real-time synchronized video playback with WebSocket
  • 💬 Real-time Chat - Built-in chat for Watch Together rooms
  • 🎮 Player Integration - Abstract interfaces for any video player (HTML5, VLC, custom)
  • 🛠️ Rich Utilities - String, date, object, validation helpers included
  • 📦 Tree-shakeable - Only bundle what you use (~8KB core, ~15KB with utils)
  • Well Tested - Comprehensive test coverage
  • 📚 Production Ready - Battle-tested patterns, see PRODUCTION_READY.md

📦 Installation

npm install @harmoni-org/sdk
yarn add @harmoni-org/sdk
pnpm add @harmoni-org/sdk

🎮 Try the Demo

Want to see it in action? Check out the demo project that uses the published npm package:

cd demo
npm install
npm run demo           # Basic usage demo
npm run demo:watch     # Watch Together demo
npm run demo:player    # Player integration demo

See demo folder for complete examples using @harmoni-org/sdk from npm!

🚀 Quick Start

Basic Usage

import { HarmoniSDK } from '@harmoni-org/sdk';

// Initialize the SDK
const sdk = new HarmoniSDK({
  baseURL: 'https://api.yourbackend.com',
  timeout: 30000,
  autoRefreshToken: true, // Automatically refresh tokens on 401
  // autoRestoreToken: true (default) - Token persists across reloads
  retryConfig: {
    maxRetries: 3,
    retryDelay: 1000,
    retryableStatuses: [408, 429, 500, 502, 503, 504],
  },
});

// Login - token automatically saved to localStorage
const user = await sdk.auth.login({
  emailOrUsername: '[email protected]',
  password: 'secure_password',
});
console.log('Logged in as:', user.username);

// 🔄 User closes and reopens app...
// ✅ Token is automatically restored! No extra code needed.

// Update user profile
await sdk.user.updateProfile({
  username: 'newusername',
});

// Watch Together - Synchronized playback
await sdk.watchTogether.connect();
const room = await sdk.watchTogether.createRoom({ roomName: 'Movie Night' });

sdk.watchTogether.onRoomUpdate((update) => {
  if (update.metadata.action === 'play') player.play();
});

sdk.watchTogether.play(); // Syncs to all users
await sdk.watchTogether.sendMessage('Hello! 👋');

Note: The SDK is fully SSR-safe and works in Node.js, browsers, and edge runtimes.

Advanced Configuration

import { HarmoniSDK } from '@harmoni-org/sdk';

const sdk = new HarmoniSDK({
  baseURL: 'https://api.yourbackend.com',
  timeout: 30000,
  headers: {
    'X-Custom-Header': 'value',
  },
  retryConfig: {
    maxRetries: 3,
    retryDelay: 1000,
    retryableStatuses: [408, 429, 500, 502, 503, 504],
  },
  autoRefreshToken: true,
});

// Set auth token manually if you have it stored
sdk.setAuthToken('your-access-token');

📖 API Documentation

Authentication Module

// Check if username is available
const isAvailable = await sdk.auth.isUsernameUnique('johndoe');

// Check if email is available
const emailAvailable = await sdk.auth.isEmailUnique('[email protected]');

// Register a new user
const user = await sdk.auth.register({
  username: 'johndoe',
  password: 'secure_password',
  email: '[email protected]', // optional
});
// Returns: { id, username, email, token, refreshToken }

// Login with email or username
const user = await sdk.auth.login({
  emailOrUsername: 'johndoe', // can be email or username
  password: 'password',
});
// Returns: { id, username, email, token, refreshToken }

// Verify current token
const verified = await sdk.auth.verifyToken();
// Returns: { user: { id, username, email, createdAt } }

// Refresh access token
const newToken = await sdk.auth.refreshAccessToken();
// Returns: string (new access token)

// Logout
await sdk.auth.logout();

// Password reset flow
await sdk.auth.requestPasswordReset('[email protected]');
await sdk.auth.resetPassword('reset-token', 'new-password');

// Change password (authenticated)
await sdk.auth.changePassword('old-password', 'new-password');

// Email verification
await sdk.auth.verifyEmail('verification-token');
await sdk.auth.resendVerificationEmail('[email protected]');

Watch Together Module

// Connect to Watch Together service
await sdk.watchTogether.connect();

// Create a room
const room = await sdk.watchTogether.createRoom({
  roomName: 'Movie Night',
});
console.log('Room ID:', room.roomId);

// Or join existing room
const room = await sdk.watchTogether.joinRoom({
  roomId: 'abc123',
});

// Listen for events
sdk.watchTogether.onRoomUpdate((update) => {
  if (update.metadata.userId !== myUserId) {
    switch (update.metadata.action) {
      case 'play':
        player.play();
        break;
      case 'pause':
        player.pause();
        break;
      case 'seek':
        player.seek(update.roomUpdates.syncState?.time);
        break;
    }
  }
});

// Sync check (automatic every 30s, or manual)
sdk.watchTogether.onSyncState((syncState) => {
  const diff = Math.abs(player.getCurrentTime() - syncState.time);
  if (diff > 1) player.seek(syncState.time);
});

// Control playback (synced to all users)
sdk.watchTogether.play();
sdk.watchTogether.pause();
sdk.watchTogether.seek(125.5);

// Send chat messages
await sdk.watchTogether.sendMessage('Hello everyone!');

sdk.watchTogether.onChatMessage((message) => {
  console.log(`${message.username}: ${message.content}`);
});

// Update file info
sdk.watchTogether.updateFileInfo({
  fileId: 'video-001',
  name: 'movie.mkv',
  fullTime: 7200, // 2 hours
  hash: 'abc123',
});

// Leave room
sdk.watchTogether.leaveRoom();

// Disconnect
sdk.watchTogether.disconnect();

See Watch Together Module Documentation for complete API reference.

Video Player Integration

The SDK provides abstract interfaces for integrating any video player with Watch Together:

import { HTML5VideoController, SyncedPlayer } from '@harmoni-org/sdk';

// Create player (HTML5, VLC, YouTube, custom, etc.)
const player = new HTML5VideoController('video-element');
await player.start();

// Connect player to Watch Together
const syncedPlayer = new SyncedPlayer(player, sdk.watchTogether, {
  getCurrentUserId: () => currentUser.id,
  onPlayerStopped: () => sdk.watchTogether.leaveRoom(),
});

// Load and play - automatically syncs to all users!
await player.loadMedia('https://example.com/video.mp4');
await player.play();

// When user seeks, pauses, or plays - all users follow
// When other users control their player - yours follows

Key Features:

  • ✅ Abstract interfaces for any player (HTML5, VLC, YouTube, custom)
  • ✅ Automatic user action detection (play, pause, seek)
  • ✅ Smart sync (distinguishes user actions from sync commands)
  • ✅ No feedback loops
  • ✅ Built-in HTML5 implementation
  • ✅ Create custom controllers for any player

Create Custom Player:

import { VideoPlayerController } from '@harmoni-org/sdk';

class MyCustomPlayer implements VideoPlayerController {
  async play(): Promise<void> {
    // Your play logic
  }

  async getStatus(): Promise<IPlayerState> {
    return {
      isPlaying: this.myPlayer.isPlaying(),
      currentTime: this.myPlayer.getCurrentTime(),
      // ... map your player's state
    };
  }

  // Implement other methods...
}

// Use with Watch Together
const syncedPlayer = new SyncedPlayer(new MyCustomPlayer(), sdk.watchTogether, options);

See Player Integration Guide for complete documentation.

User Module

// Get user by ID
const user = await sdk.user.getById('user-id-123');

// Get current user profile
const profile = await sdk.user.getCurrentUser();

// Update profile
await sdk.user.updateProfile({
  username: 'johndoe_new',
  email: '[email protected]',
});

// List users with pagination
const users = await sdk.user.list({
  page: 1,
  limit: 20,
});

// Search users
const results = await sdk.user.search('john', {
  page: 1,
  limit: 10,
});

// Upload avatar
const file = new File(['...'], 'avatar.jpg');
const { url } = await sdk.user.uploadAvatar(file);

// Delete avatar
await sdk.user.deleteAvatar();

// Delete account
await sdk.user.deleteAccount();

🛠️ Utilities

String Utilities

import { stringUtils } from '@harmoni-org/sdk';

stringUtils.capitalize('hello'); // 'Hello'
stringUtils.titleCase('hello world'); // 'Hello World'
stringUtils.slugify('Hello World!'); // 'hello-world'
stringUtils.truncate('Long text...', 10); // 'Long te...'
stringUtils.isValidEmail('[email protected]'); // true
stringUtils.camelToSnake('myVariable'); // 'my_variable'
stringUtils.snakeToCamel('my_variable'); // 'myVariable'
stringUtils.randomString(16); // 'aB3dEf5gH7jK9mN0'

Date Utilities

import { dateUtils } from '@harmoni-org/sdk';

dateUtils.formatDate(new Date(), 'YYYY-MM-DD'); // '2024-01-15'
dateUtils.timeAgo(new Date('2024-01-01')); // '2 weeks ago'
dateUtils.addDays(new Date(), 7); // Date 7 days from now
dateUtils.addHours(new Date(), 3); // Date 3 hours from now
dateUtils.isToday(new Date()); // true
dateUtils.isPast(new Date('2023-01-01')); // true
dateUtils.isFuture(new Date('2025-01-01')); // true

Object Utilities

import { objectUtils } from '@harmoni-org/sdk';

const original = { a: 1, b: { c: 2 } };
const cloned = objectUtils.deepClone(original);

const merged = objectUtils.deepMerge({ a: 1, b: 2 }, { b: 3, c: 4 }); // { a: 1, b: 3, c: 4 }

const picked = objectUtils.pick({ a: 1, b: 2, c: 3 }, ['a', 'c']); // { a: 1, c: 3 }
const omitted = objectUtils.omit({ a: 1, b: 2, c: 3 }, ['b']); // { a: 1, c: 3 }

objectUtils.isEmpty({}); // true
objectUtils.isEmpty({ a: 1 }); // false

const value = objectUtils.getNestedValue({ a: { b: { c: 1 } } }, 'a.b.c'); // 1

Validation Utilities

import { validationUtils } from '@harmoni-org/sdk';

// Email validation
const emailResult = validationUtils.validateEmail('[email protected]');
// { valid: true }

// Password validation
const passwordResult = validationUtils.validatePassword('MyP@ssw0rd', {
  minLength: 8,
  requireUppercase: true,
  requireLowercase: true,
  requireNumbers: true,
  requireSpecialChars: true,
});
// { valid: true }

// URL validation
const urlResult = validationUtils.validateUrl('https://example.com');
// { valid: true }

// Phone validation
const phoneResult = validationUtils.validatePhone('+1234567890');
// { valid: true }

// Required field
const requiredResult = validationUtils.required('value', 'Username');
// { valid: true }

// Length validation
validationUtils.minLength('test', 3, 'Username'); // { valid: true }
validationUtils.maxLength('test', 10, 'Username'); // { valid: true }

Storage Utilities

import { localStorage, sessionStorage } from '@harmoni-org/sdk';

// Local storage (SSR-safe - uses memory fallback in Node.js)
localStorage.set('user', { id: 1, name: 'John' });
const user = localStorage.get<{ id: number; name: string }>('user');
localStorage.remove('user');
localStorage.clear();

// Check if browser storage is available
if (localStorage.isAvailable()) {
  console.log('Using browser localStorage');
} else {
  console.log('Using in-memory storage (SSR/Node)');
}

// Session storage
sessionStorage.set('token', 'abc123');
const token = sessionStorage.get<string>('token');

🔌 Advanced Usage

File Uploads

The SDK automatically handles FormData for file uploads:

// Upload user avatar
const file = document.querySelector('input[type="file"]').files[0];
const result = await sdk.user.uploadAvatar(file);
console.log('Avatar URL:', result.url);

// Upload with progress tracking
const formData = new FormData();
formData.append('file', file);

await sdk.getHttpClient().post('/uploads', formData, {
  onUploadProgress: (progressEvent) => {
    const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
    console.log(`Upload progress: ${progress}%`);
  },
});

See examples/upload-example.ts for more upload patterns.

Token Refresh with Request Retry

The SDK automatically:

  1. Detects 401 errors
  2. Calls refresh token endpoint
  3. Retries the original request with new token
const sdk = new HarmoniSDK({
  baseURL: 'https://api.example.com',
  autoRefreshToken: true, // Enable auto-refresh
});

// This request will automatically retry if token expires
const data = await sdk.user.getCurrentUser();
// If 401 → refreshes token → retries request → returns data

Smart Retry Logic

The SDK only retries safe operations (GET, HEAD, OPTIONS) by default:

const sdk = new HarmoniSDK({
  baseURL: 'https://api.example.com',
  retryConfig: {
    maxRetries: 3,
    retryDelay: 1000, // Base delay (uses exponential backoff)
    retryableStatuses: [408, 429, 500, 502, 503, 504],
  },
});

// GET requests will retry on network errors or 5xx errors
await sdk.user.list(); // ✅ Will retry

// POST/PATCH/DELETE won't retry (not idempotent)
await sdk.auth.login(creds); // ❌ Won't retry (POST)

Custom Interceptors

const sdk = new HarmoniSDK({ baseURL: 'https://api.example.com' });

// Add request interceptor
sdk.getHttpClient().addRequestInterceptor({
  onFulfilled: (config) => {
    console.log('Request:', config.url);
    return config;
  },
  onRejected: (error) => {
    console.error('Request error:', error);
    return Promise.reject(error);
  },
});

// Add response interceptor
sdk.getHttpClient().addResponseInterceptor({
  onFulfilled: (response) => {
    console.log('Response:', response.status);
    return response;
  },
  onRejected: (error) => {
    console.error('Response error:', error);
    return Promise.reject(error);
  },
});

Custom Error Handling

import { ApiError } from '@harmoni-org/sdk';

try {
  await sdk.auth.login(credentials);
} catch (error) {
  if (ApiError.isApiError(error)) {
    console.error('API Error:', error.status, error.message);
    console.error('Error code:', error.code);
    console.error('Details:', error.details);
  } else {
    console.error('Unknown error:', error);
  }
}

Creating Custom Modules

import { HttpClient } from '@harmoni-org/sdk';

class CustomModule {
  constructor(private http: HttpClient) {}

  async customEndpoint() {
    return this.http.get('/custom/endpoint');
  }
}

// Extend the SDK
const sdk = new HarmoniSDK({ baseURL: 'https://api.example.com' });
const customModule = new CustomModule(sdk.getHttpClient());

🏗️ Project Structure

harmoni-sdk/
├── src/
│   ├── core/              # Core HTTP client & error handling
│   │   ├── http/
│   │   └── errors/
│   ├── modules/           # Feature modules (auth, user, etc.)
│   │   ├── auth/
│   │   └── user/
│   ├── utils/             # Utility functions
│   ├── types/             # TypeScript types
│   ├── sdk/               # Main SDK class
│   └── index.ts           # Main entry point
├── tests/                 # Test files
├── .github/workflows/     # CI/CD pipelines
└── dist/                  # Build output (ESM + CJS)

🧪 Testing

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Generate coverage report
npm run test -- --coverage

🔨 Development

# Install dependencies
npm install

# Start development mode (watch)
npm run dev

# Build the package
npm run build

# Run linter
npm run lint

# Format code
npm run format

# Type check
npm run typecheck

📝 Creating a Release

This project uses Changesets for version management.

  1. Create a changeset:
npm run changeset
  1. Commit the changeset files
  2. Push to GitHub
  3. A "Version Packages" PR will be created automatically
  4. Merge the PR to publish to npm

🤝 Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add tests for your changes
  5. Run tests and linting (npm test && npm run lint)
  6. Commit your changes (git commit -m 'feat: add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

📚 Additional Resources

Documentation

Examples

External Links

🙏 Acknowledgments

📞 Support


Made with ❤️ by the Harmoni Team