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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@devforgets/pulse

v0.2.2

Published

High-performance event system for React applications and services. Features type-safe event handling, automatic cleanup, real-time monitoring, and service integration.

Downloads

214

Readme

@devforgets/pulse

A high-performance event system for React applications and standalone TypeScript/JavaScript code. Built for simplicity and type safety.

npm version TypeScript License: MIT

Table of Contents

Introduction

Pulse is a powerful event system designed for both React applications and standalone TypeScript/JavaScript code. It provides a type-safe, efficient, and developer-friendly way to handle events across your application.

Why Pulse?

  • 🎯 Type Safety: Full TypeScript support with extensive type inference
  • High Performance: Optimized event handling with automatic batching
  • 🧩 Universal Usage: Works in React components and standalone code
  • 📊 DevTools: Built-in debugging and performance monitoring tools
  • 🧹 Smart Cleanup: Automatic memory management
  • 🎨 Developer Experience: Simple, intuitive API

Key Features

  • Type-safe event handling
  • React hooks for components
  • Standalone Events API
  • Automatic event batching
  • Priority-based handlers
  • Automatic cleanup
  • Real-time monitoring
  • Built-in developer tools
  • Memory leak prevention

Installation

# Using npm
npm install @devforgets/pulse

# Using yarn
yarn add @devforgets/pulse

# Using pnpm
pnpm add @devforgets/pulse

Quick Start

React Components

import { createPulseContext } from '@devforgets/pulse';

// Define your events
interface AppEvents {
  'user:login': { userId: string };
  'data:update': { value: number };
}

// Create your pulse context
const { PulseProvider, useEvent, emitEvent } = createPulseContext<AppEvents>();

// Use in your App
function App() {
  return (
    <PulseProvider>
      <YourComponents />
    </PulseProvider>
  );
}

// Use in components
function LoginButton() {
  const emit = emitEvent();

  useEvent('user:login', (data) => {
    console.log('User logged in:', data.userId);
  });

  return <button onClick={() => emit('user:login', { userId: '123' })}>Login</button>;
}

Standalone Usage

import { Events } from '@devforgets/pulse';

interface AppEvents {
  'user:login': { userId: string };
  'data:update': { value: number };
}

// Subscribe to events
Events.on<AppEvents, 'user:login'>('user:login', (data) => {
  console.log('Login:', data.userId);
});

// Emit events
Events.emit<AppEvents, 'user:login'>('user:login', {
  userId: '123'
});

// Batch events
Events.batch<AppEvents>(() => {
  Events.emit('user:login', { userId: '123' });
  Events.emit('data:update', { value: 42 });
});

// Class-based usage
class AuthService {
  private cleanup: Array<() => void> = [];

  constructor() {
    this.cleanup.push(
      Events.on<AppEvents, 'user:login'>('user:login', this.handleLogin)
    );
  }

  private handleLogin = (data: AppEvents['user:login']) => {
    console.log('Auth service:', data.userId);
  };

  public login(userId: string) {
    Events.emit<AppEvents, 'user:login'>('user:login', { userId });
  }

  public dispose() {
    this.cleanup.forEach(unsub => unsub());
  }
}

With DevTools

import { PulseDevTools } from '@devforgets/pulse';

function App() {
  return (
    <PulseProvider>
      <YourComponents />
      {process.env.NODE_ENV === 'development' && <PulseDevTools />}
    </PulseProvider>
  );
}

Core Concepts

Event System

Pulse uses a type-safe event system where events are defined through TypeScript interfaces:

interface AppEvents {
  'user:login': { userId: string; timestamp: number };
  'user:logout': void;
  'data:update': { id: string; value: any };
}

Event Handlers

Handlers can be registered with different priorities and options:

// In React components
function LoginComponent() {
  useEvent('user:login', (data) => {
    console.log('React component:', data.userId);
  });
}

// In standalone code
Events.on<AppEvents, 'user:login'>('user:login', (data) => {
  console.log('Standalone handler:', data.userId);
}, {
  priority: 10  // Optional priority
});

Event Emission

Events can be emitted individually or batched:

// In React components
function UserActions() {
  const emit = emitEvent();
  const batch = batchEvents();

  const handleUpdate = () => {
    batch(() => {
      emit('user:login', { userId: '123', timestamp: Date.now() });
      emit('data:update', { id: 'user-123', value: { status: 'active' } });
    });
  };
}

// In standalone code
Events.batch<AppEvents>(() => {
  Events.emit('user:login', { 
    userId: '123', 
    timestamp: Date.now() 
  });
  Events.emit('data:update', { 
    id: 'user-123', 
    value: { status: 'active' } 
  });
});

Type Safety

Pulse provides complete type safety for events:

interface AppEvents {
  'user:login': { userId: string };
  'data:count': number;
}

// React components - Type checking
useEvent('user:login', (data) => {
  data.userId; // ✓ OK
  data.invalid; // ✗ Error: Property 'invalid' does not exist
});

// Standalone code - Type checking
Events.emit<AppEvents, 'user:login'>('user:login', {
  userId: '123' // ✓ OK
  invalid: true // ✗ Error: Object literal may only specify known properties
});

// Type inference for event names
Events.emit<AppEvents, 'invalid'>( // ✗ Error: 'invalid' is not a valid event
  'invalid',
  { data: 123 }
);

// Type inference for event data
Events.emit<AppEvents, 'data:count'>(
  'data:count',
  'not-a-number' // ✗ Error: Argument of type 'string' is not assignable to parameter of type 'number'
);

Advanced Features

Priority Handlers

Control the order of event handling with priorities:

// React components
function CriticalHandler() {
  // High priority handler executes first
  useEvent('data:update', (data) => {
    console.log('Critical update:', data);
  }, { priority: 100 });
}

// Standalone code
Events.on<AppEvents, 'data:update'>('data:update', (data) => {
  console.log('High priority handler:', data);
}, { priority: 100 });

Events.on<AppEvents, 'data:update'>('data:update', (data) => {
  console.log('Normal priority handler:', data);
}); // Default priority: 0

Event Batching

Group multiple events to optimize performance:

// React components
function DataSync() {
  const emit = emitEvent();
  const batch = batchEvents();

  const syncData = () => {
    batch(() => {
      // All these events will be processed together
      emit('data:update', { id: 'user', value: userData });
      emit('data:update', { id: 'settings', value: settings });
      emit('data:update', { id: 'preferences', value: prefs });
    });
  };
}

// Standalone code
class DataService {
  public updateMultiple(updates: any[]) {
    Events.batch<AppEvents>(() => {
      updates.forEach(update => {
        Events.emit('data:update', update);
      });
    });
  }
}

Subscription Management

Control event subscriptions with fine-grained options:

// React components
function SubscriptionDemo() {
  useEffect(() => {
    const subscription = useEvent('data:update', (data) => {
      console.log('Data:', data);
    }, {
      priority: 10,
      keepAlive: true
    });

    // Control subscription
    subscription.pause();
    subscription.resume();
    console.log('Is paused:', subscription.isPaused());

    return () => subscription.unsubscribe();
  }, []);
}

// Standalone code
class Service {
  private subscriptions: Array<() => void> = [];

  public initialize() {
    const sub = Events.on<AppEvents, 'data:update'>(
      'data:update',
      this.handleData,
      { priority: 10 }
    );

    // Store cleanup function
    this.subscriptions.push(() => sub.unsubscribe());

    // Subscription control
    sub.pause();  // Pause handling
    sub.resume(); // Resume handling
  }

  public cleanup() {
    this.subscriptions.forEach(unsub => unsub());
    this.subscriptions = [];
  }
}

Automatic Cleanup

Pulse provides automatic cleanup for React components and manual cleanup helpers for standalone code:

// React components - Automatic cleanup
function AutoCleanupDemo() {
  // Cleaned up automatically when component unmounts
  useEvent('user:login', (data) => {
    console.log('User:', data);
  });

  // Stays active after unmount
  useEvent('system:status', (data) => {
    console.log('System:', data);
  }, { keepAlive: true });
}

// Standalone code - Manual cleanup
class CleanupDemo {
  private cleanupFunctions: Array<() => void> = [];

  constructor() {
    // Regular subscription
    this.cleanupFunctions.push(
      Events.on<AppEvents, 'event1'>('event1', this.handler1)
    );

    // Multiple subscriptions
    const cleanup = this.subscribeToEvents();
    this.cleanupFunctions.push(cleanup);
  }

  private subscribeToEvents() {
    const subs = [
      Events.on<AppEvents, 'event2'>('event2', this.handler2),
      Events.on<AppEvents, 'event3'>('event3', this.handler3)
    ];

    return () => subs.forEach(sub => sub.unsubscribe());
  }

  public destroy() {
    this.cleanupFunctions.forEach(cleanup => cleanup());
    this.cleanupFunctions = [];
  }
}

API Reference

Events API

Events.on<T, K>()

Subscribe to events with type safety.

function on<T extends Record<string, any>, K extends keyof T>(
  event: K,
  handler: (data: T[K]) => void,
  options?: {
    priority?: number;    // Handler priority (default: 0)
    keepAlive?: boolean; // Keep subscription after cleanup
  }
): PulseSubscription

Example:

const subscription = Events.on<AppEvents, 'user:login'>(
  'user:login',
  (data) => {
    console.log(data.userId);
  },
  { priority: 10 }
);

Events.emit<T, K>()

Emit events with type safety.

function emit<T extends Record<string, any>, K extends keyof T>(
  event: K,
  data: T[K]
): void

Example:

Events.emit<AppEvents, 'user:login'>('user:login', {
  userId: '123'
});

Events.batch<T>()

Batch multiple events for performance.

function batch<T extends Record<string, any>>(
  callback: () => void
): void

Example:

Events.batch<AppEvents>(() => {
  Events.emit('event1', data1);
  Events.emit('event2', data2);
});

React Hooks

createPulseContext<T>()

Create React context and hooks for Pulse.

function createPulseContext<T extends Record<string, any>>(): {
  PulseProvider: React.FC<PropsWithChildren>;
  useEvent: UseEventHook<T>;
  emitEvent: EmitEventHook<T>;
  batchEvents: BatchEventsHook;
}

Example:

const { 
  PulseProvider, 
  useEvent, 
  emitEvent 
} = createPulseContext<AppEvents>();

useEvent()

React hook to subscribe to events.

function useEvent<K extends keyof T>(
  eventName: K,
  handler: (data: T[K]) => void,
  options?: {
    priority?: number;
    keepAlive?: boolean;
    disabled?: boolean;
  }
): void

Example:

useEvent('user:login', (data) => {
  console.log(data.userId);
}, {
  priority: 10,
  keepAlive: true
});

emitEvent()

React hook to get event emitter function.

function emitEvent(): <K extends keyof T>(
  eventName: K,
  data: T[K]
) => void

Example:

const emit = emitEvent();
emit('user:login', { userId: '123' });

Types

PulseSubscription

Subscription control interface.

interface PulseSubscription {
  unsubscribe: () => void;
  pause: () => void;
  resume: () => void;
  isPaused: () => boolean;
}

EventPayload

Get the payload type for an event.

type EventPayload<T, K extends keyof T> = T[K];

Example:

type LoginPayload = EventPayload<AppEvents, 'user:login'>;
// { userId: string }

EventNames

Get all valid event names.

type EventNames<T> = keyof T;

Example:

type ValidEvents = EventNames<AppEvents>;
// 'user:login' | 'data:update' | etc.

DevTools Props

interface DevToolsProps {
  position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
  theme?: 'light' | 'dark';
}

Example:

<PulseDevTools
  position="bottom-right"
  theme="light"
/>

Best Practices

Event Naming

Follow a consistent event naming pattern:

interface AppEvents {
  // Format: 'domain:action'
  'user:login': { userId: string };
  'user:logout': void;

  // Group related events
  'data:fetch': { id: string };
  'data:update': { id: string; value: any };
  'data:delete': { id: string };

  // UI events
  'ui:modal:open': { id: string };
  'ui:modal:close': void;
  'ui:theme:change': { mode: 'light' | 'dark' };
}

Performance Optimization

  1. Use Batching for Multiple Events
// Instead of this ❌
Events.emit('data:update', data1);
Events.emit('ui:refresh', {});
Events.emit('stats:update', stats);

// Do this ✅
Events.batch<AppEvents>(() => {
  Events.emit('data:update', data1);
  Events.emit('ui:refresh', {});
  Events.emit('stats:update', stats);
});
  1. Proper Cleanup
// React components - Let it clean up automatically ✅
useEvent('data:update', handler);

// Standalone code - Always clean up ✅
class Service {
  private cleanup: Array<() => void> = [];

  constructor() {
    this.cleanup.push(
      Events.on<AppEvents, 'data:update'>('data:update', this.handler)
    );
  }

  destroy() {
    this.cleanup.forEach(fn => fn());
  }
}
  1. Use Priority for Critical Handlers
// Critical updates first ✅
Events.on<AppEvents, 'data:update'>(
  'data:update',
  criticalHandler,
  { priority: 100 }
);

// Normal updates later ✅
Events.on<AppEvents, 'data:update'>(
  'data:update',
  normalHandler
);

Code Organization

  1. Centralize Event Types
// events/types.ts
export interface AppEvents {
  'user:login': UserLoginEvent;
  'user:logout': void;
}

export interface UserLoginEvent {
  userId: string;
  timestamp: number;
}

// Use across your app
import type { AppEvents } from './events/types';
  1. Event Utilities
// utils/events.ts
import { Events } from '@devforgets/pulse';
import type { AppEvents } from '../events/types';

export const emitUserLogin = (userId: string) => {
  Events.emit<AppEvents, 'user:login'>('user:login', {
    userId,
    timestamp: Date.now()
  });
};

export const onUserLogin = (
  handler: (data: AppEvents['user:login']) => void
) => {
  return Events.on<AppEvents, 'user:login'>('user:login', handler);
};
  1. Clean Service Organization
// services/auth.ts
import { Events } from '@devforgets/pulse';
import type { AppEvents } from '../events/types';

export class AuthService {
  private cleanup: Array<() => void> = [];

  constructor() {
    // Group related events
    this.setupUserEvents();
    this.setupSessionEvents();
  }

  private setupUserEvents() {
    this.cleanup.push(
      Events.on<AppEvents, 'user:login'>('user:login', this.handleLogin)
    );
  }

  private setupSessionEvents() {
    this.cleanup.push(
      Events.on<AppEvents, 'session:expire'>('session:expire', this.handleExpire)
    );
  }

  destroy() {
    this.cleanup.forEach(fn => fn());
  }
}
  1. Error Handling
// Good error handling ✅
Events.on<AppEvents, 'data:update'>('data:update', (data) => {
  try {
    processData(data);
  } catch (error) {
    Events.emit<AppEvents, 'error:data'>('error:data', {
      error,
      source: 'data:update'
    });
  }
});

// Handle errors at appropriate levels ✅
Events.on<AppEvents, 'error:data'>('error:data', (error) => {
  logError(error);
  showUserFriendlyError(error);
});

Real-World Examples

Form Management

// events/form.events.ts
export interface FormEvents {
  'form:submit': { values: Record<string, any> };
  'form:validate': { field: string; value: any };
  'form:error': { field: string; error: string };
  'form:success': { message: string };
  'form:reset': void;
}

// utils/validation.ts
import { Events } from '@devforgets/pulse';
import type { FormEvents } from '../events/form.events';

export function validateField(field: string, value: any) {
  try {
    if (!value && field === 'email') {
      throw new Error('Email is required');
    }
    if (!value.includes('@') && field === 'email') {
      throw new Error('Invalid email format');
    }
  } catch (error) {
    Events.emit<FormEvents, 'form:error'>('form:error', {
      field,
      error: error.message
    });
    return false;
  }
  return true;
}

// components/FormComponent.tsx
import React, { useState } from 'react';
import { createPulseContext } from '@devforgets/pulse';
import type { FormEvents } from '../events/form.events';

const { useEvent, emitEvent } = createPulseContext<FormEvents>();

function FormComponent() {
  const [errors, setErrors] = useState<Record<string, string>>({});
  const emit = emitEvent();

  useEvent('form:error', ({ field, error }) => {
    setErrors(prev => ({ ...prev, [field]: error }));
  });

  useEvent('form:success', () => {
    setErrors({});
  });

  const handleSubmit = (values: Record<string, any>) => {
    emit('form:submit', { values });
  };

  return (
    <form>
      {/* Form fields */}
      {errors.email && <span className="error">{errors.email}</span>}
    </form>
  );
}

// services/form.service.ts
import { Events } from '@devforgets/pulse';
import type { FormEvents } from '../events/form.events';

export class FormService {
  private cleanup: Array<() => void> = [];

  constructor() {
    this.cleanup.push(
      Events.on<FormEvents, 'form:submit'>('form:submit', this.handleSubmit)
    );
  }

  private handleSubmit = async (data: FormEvents['form:submit']) => {
    try {
      await this.submitToAPI(data.values);
      Events.emit<FormEvents, 'form:success'>('form:success', {
        message: 'Form submitted successfully!'
      });
    } catch (error) {
      Events.emit<FormEvents, 'form:error'>('form:error', {
        field: 'submit',
        error: error.message
      });
    }
  };

  private async submitToAPI(values: Record<string, any>) {
    // API submission logic
  }

  public dispose() {
    this.cleanup.forEach(unsub => unsub());
  }
}

Real-time Updates

// events/realtime.events.ts
export interface RealtimeEvents {
  'ws:connect': void;
  'ws:disconnect': void;
  'ws:message': { type: string; payload: any };
  'ws:error': { code: number; message: string };
  'data:update': { path: string; value: any };
  'data:sync': { timestamp: number };
  'connection:status': { online: boolean };
}

// services/websocket.service.ts
import { Events } from '@devforgets/pulse';
import type { RealtimeEvents } from '../events/realtime.events';

export class WebSocketService {
  private ws: WebSocket | null = null;
  private reconnectTimer: any = null;
  private cleanup: Array<() => void> = [];

  constructor() {
    this.setupEventListeners();
  }

  private setupEventListeners() {
    // Listen for connect requests
    this.cleanup.push(
      Events.on<RealtimeEvents, 'ws:connect'>('ws:connect', () => {
        this.connect();
      })
    );

    // Listen for disconnect requests
    this.cleanup.push(
      Events.on<RealtimeEvents, 'ws:disconnect'>('ws:disconnect', () => {
        this.disconnect();
      })
    );
  }

  private connect() {
    try {
      this.ws = new WebSocket('wss://api.example.com');

      this.ws.onopen = () => {
        Events.emit<RealtimeEvents, 'connection:status'>('connection:status', {
          online: true
        });
      };

      this.ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        Events.emit<RealtimeEvents, 'ws:message'>('ws:message', {
          type: data.type,
          payload: data.payload
        });

        // Also emit specific data updates
        if (data.type === 'update') {
          Events.emit<RealtimeEvents, 'data:update'>('data:update', {
            path: data.payload.path,
            value: data.payload.value
          });
        }
      };

      this.ws.onclose = () => {
        Events.emit<RealtimeEvents, 'connection:status'>('connection:status', {
          online: false
        });
        this.scheduleReconnect();
      };
    } catch (error) {
      Events.emit<RealtimeEvents, 'ws:error'>('ws:error', {
        code: 500,
        message: error.message
      });
    }
  }

  private scheduleReconnect() {
    if (this.reconnectTimer) return;
    this.reconnectTimer = setTimeout(() => {
      Events.emit<RealtimeEvents, 'ws:connect'>('ws:connect', undefined);
    }, 5000);
  }

  private disconnect() {
    this.ws?.close();
    this.ws = null;
    clearTimeout(this.reconnectTimer);
  }

  public dispose() {
    this.disconnect();
    this.cleanup.forEach(unsub => unsub());
  }
}

// components/RealtimeStatus.tsx
function RealtimeStatus() {
  const [isOnline, setIsOnline] = useState(false);

  useEvent('connection:status', ({ online }) => {
    setIsOnline(online);
  });

  useEvent('ws:error', ({ message }) => {
    console.error('WebSocket error:', message);
  });

  return (
    <div className={`status ${isOnline ? 'online' : 'offline'}`}>
      {isOnline ? 'Connected' : 'Disconnected'}
    </div>
  );
}

Theme Management

// events/theme.events.ts
export interface ThemeEvents {
  'theme:change': { mode: 'light' | 'dark' };
  'theme:customize': { colors: Record<string, string> };
  'theme:reset': void;
  'theme:loaded': { mode: 'light' | 'dark'; colors: Record<string, string> };
}

// services/theme.service.ts
import { Events } from '@devforgets/pulse';
import type { ThemeEvents } from '../events/theme.events';

export class ThemeService {
  private cleanup: Array<() => void> = [];

  constructor() {
    this.cleanup.push(
      Events.on<ThemeEvents, 'theme:change'>('theme:change', this.handleThemeChange),
      Events.on<ThemeEvents, 'theme:customize'>('theme:customize', this.handleCustomColors),
      Events.on<ThemeEvents, 'theme:reset'>('theme:reset', this.handleReset)
    );

    // Load initial theme
    this.loadSavedTheme();
  }

  private loadSavedTheme() {
    const savedTheme = localStorage.getItem('theme-mode') || 'light';
    const savedColors = JSON.parse(localStorage.getItem('theme-colors') || '{}');

    Events.emit<ThemeEvents, 'theme:loaded'>('theme:loaded', {
      mode: savedTheme as 'light' | 'dark',
      colors: savedColors
    });
  }

  private handleThemeChange = ({ mode }: ThemeEvents['theme:change']) => {
    localStorage.setItem('theme-mode', mode);
    document.documentElement.classList.toggle('dark', mode === 'dark');
  };

  private handleCustomColors = ({ colors }: ThemeEvents['theme:customize']) => {
    localStorage.setItem('theme-colors', JSON.stringify(colors));
    Object.entries(colors).forEach(([key, value]) => {
      document.documentElement.style.setProperty(`--color-${key}`, value);
    });
  };

  private handleReset = () => {
    localStorage.removeItem('theme-mode');
    localStorage.removeItem('theme-colors');
    document.documentElement.classList.remove('dark');
    document.documentElement.removeAttribute('style');
  };

  public dispose() {
    this.cleanup.forEach(unsub => unsub());
  }
}

// components/ThemeToggle.tsx
function ThemeToggle() {
  const [mode, setMode] = useState<'light' | 'dark'>('light');
  const emit = emitEvent();

  useEvent('theme:loaded', ({ mode }) => {
    setMode(mode);
  });

  const toggleTheme = () => {
    const newMode = mode === 'light' ? 'dark' : 'light';
    emit('theme:change', { mode: newMode });
  };

  return (
    <button onClick={toggleTheme}>
      Switch to {mode === 'light' ? 'Dark' : 'Light'} Mode
    </button>
  );
}

Contributing

We welcome contributions to Pulse! Here's how you can help:

Development Setup

  1. Fork and clone the repository:
git clone https://github.com/YOUR_USERNAME/pulse.git
cd pulse
  1. Install dependencies:
pnpm install
  1. Create a branch for your work:
git checkout -b feature/your-feature-name
  1. Start development:
pnpm dev

Running Tests

# Run all tests
pnpm test

# Run specific test file
pnpm test src/events.test.ts

# Run with coverage
pnpm coverage

Building

# Build the package
pnpm build

# Check types
pnpm type-check

# Run linting
pnpm lint

Guidelines

  1. Code Style
  • Follow TypeScript best practices
  • Use provided ESLint configuration
  • Include JSDoc comments for public APIs
  • Follow existing patterns
  1. Testing
  • Write tests for new features
  • Maintain existing test coverage
  • Test both success and error cases
  • Test React and standalone usage
  1. Documentation
  • Update README for new features
  • Add TSDoc comments
  • Include examples
  • Update changelog
  1. Pull Requests
  • Create focused PRs
  • Add tests
  • Update documentation
  • Request review
  • Reference issues

Common Development Tasks

  1. Adding a New Feature
# Create feature branch
git checkout -b feature/new-feature

# Make your changes
# Add tests in __tests__ directory
# Update documentation

# Run checks
pnpm lint
pnpm test
pnpm build

# Commit using conventional commits
git commit -m "feat: add new feature"

# Push and create PR
git push origin feature/new-feature
  1. Fixing a Bug
# Create bug fix branch
git checkout -b fix/bug-name

# Make your changes
# Add test that reproduces the bug
# Update changelog

# Run checks
pnpm lint
pnpm test
pnpm build

# Commit using conventional commits
git commit -m "fix: resolve bug description"

# Push and create PR
git push origin fix/bug-name

Release Process

  1. Update version in package.json
  2. Update CHANGELOG.md
  3. Create release commit:
    git commit -m "chore: release v0.1.x"
  4. Create tag:
    git tag v0.1.x
  5. Push changes and tag:
    git push && git push --tags

Need Help?

  • Check existing issues
  • Read documentation
  • Join discussions
  • Ask questions in issues
  • Follow contributing guidelines

License

MIT License

Copyright (c) 2024 DevForgeTS

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.