@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
Maintainers
Readme
@devforgets/pulse
A high-performance event system for React applications and standalone TypeScript/JavaScript code. Built for simplicity and type safety.
Table of Contents
- Introduction
- Installation
- Quick Start
- Core Concepts
- Advanced Features
- DevTools
- API Reference
- Best Practices
- Examples
- Contributing
- License
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
- 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);
});
- 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());
}
}
- 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
- 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';
- 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);
};
- 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());
}
}
- 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
- Fork and clone the repository:
git clone https://github.com/YOUR_USERNAME/pulse.git
cd pulse
- Install dependencies:
pnpm install
- Create a branch for your work:
git checkout -b feature/your-feature-name
- 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
- Code Style
- Follow TypeScript best practices
- Use provided ESLint configuration
- Include JSDoc comments for public APIs
- Follow existing patterns
- Testing
- Write tests for new features
- Maintain existing test coverage
- Test both success and error cases
- Test React and standalone usage
- Documentation
- Update README for new features
- Add TSDoc comments
- Include examples
- Update changelog
- Pull Requests
- Create focused PRs
- Add tests
- Update documentation
- Request review
- Reference issues
Common Development Tasks
- 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
- 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
- Update version in package.json
- Update CHANGELOG.md
- Create release commit:
git commit -m "chore: release v0.1.x"
- Create tag:
git tag v0.1.x
- 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.