JSPM

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

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

Package Exports

  • @devforgets/pulse

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.


Made with โค๏ธ by DevForgeTS