guardz-event
Version:
Type-safe event handling with runtime validation using guardz for unsafe data from 3rd parties
444 lines (348 loc) • 11.7 kB
Markdown
A modern, type-safe event handling library that provides multiple ergonomic APIs for handling browser events with validation, security checks, and error handling. Built on top of [guardz 1.11.3](https://www.npmjs.com/package/guardz) for robust runtime type validation.
```typescript
import { onEvent, onMessage, safeHandler } from 'guardz-event';
// Type guard for your data
const isChatMessage = (data: unknown): data is { text: string; userId: string } => {
return typeof data === 'object' && data !== null &&
typeof (data as any).text === 'string' &&
typeof (data as any).userId === 'string';
};
// Simple usage
window.addEventListener('message', onMessage(isChatMessage, {
onSuccess: (data) => console.log('Received:', data.text),
onError: (error) => console.error('Error:', error)
}));
// Or even simpler
window.addEventListener('message', safeHandler(isChatMessage,
(data) => console.log('Received:', data.text),
(error) => console.error('Error:', error)
));
```
The main function for creating safe event handlers:
```typescript
import { onEvent } from 'guardz-event';
// For any event type
const handler = onEvent('message', isChatMessage, {
onSuccess: (data) => { /* handle success */ },
onError: (error) => { /* handle error */ },
tolerance: true,
allowedOrigins: ['https://trusted.com']
});
window.addEventListener('message', handler);
```
Pre-configured handlers for common event types:
```typescript
import { onMessage, onClick, onCustom, onStorage } from 'guardz-event';
// Message events
window.addEventListener('message', onMessage(isChatMessage, {
onSuccess: handleChatMessage
}));
// DOM events
element.addEventListener('click', onClick(isClickData, {
onSuccess: handleClick
}));
// Custom events
element.addEventListener('user-action', onCustom('user-action', isUserAction, {
onSuccess: handleUserAction
}));
// Storage events
window.addEventListener('storage', onStorage(isStorageData, {
onSuccess: handleStorageChange
}));
```
For complex configurations:
```typescript
import { eventGuard } from 'guardz-event';
const handler = eventGuard()
.type('message')
.validate(isChatMessage)
.onSuccess(handleChatMessage)
.onError(handleError)
.tolerance(true)
.allowedOrigins(['https://trusted.com'])
.allowedSources(['chat-widget'])
.build();
window.addEventListener('message', handler);
```
Quick handlers for common use cases:
```typescript
import { safeHandler, safeTolerant } from 'guardz-event';
// Minimal configuration
window.addEventListener('message', safeHandler(isChatMessage, handleData, handleError));
// With tolerance mode
window.addEventListener('message', safeTolerant(isChatMessage, handleData, handleError));
```
```typescript
import { useEvent } from 'guardz-event';
function ChatComponent() {
const { data, error } = useEvent('message', isChatMessage, {
tolerance: true,
allowedOrigins: ['https://trusted.com']
});
if (error) return <div>Error: {error}</div>;
if (!data) return <div>Loading...</div>;
return <div>Message: {data.text}</div>;
}
```
All APIs support these options:
```typescript
interface EventOptions<T> {
onSuccess: (data: T) => void; // Required: Success callback
onError?: (message: string) => void; // Optional: General error handler
onTypeMismatch?: (message: string) => void; // Optional: Type validation errors
onSecurityViolation?: (origin: string, message: string) => void; // Optional: Security errors
tolerance?: boolean; // Optional: Enable tolerance mode
allowedOrigins?: string[]; // Optional: Security: allowed origins
allowedSources?: string[]; // Optional: Security: allowed sources
identifier?: string; // Optional: Custom identifier for errors
}
```
```typescript
window.addEventListener('message', onMessage(isChatMessage, {
onSuccess: handleData,
allowedOrigins: ['https://trusted-domain.com'],
onSecurityViolation: (origin, message) => {
console.warn(`Blocked message from ${origin}: ${message}`);
}
}));
```
```typescript
window.addEventListener('message', onMessage(isChatMessage, {
onSuccess: handleData,
allowedSources: ['trusted-widget'],
onSecurityViolation: (origin, message) => {
console.warn(`Blocked message from untrusted source: ${message}`);
}
}));
```
Tolerance mode allows partial data to pass through with warnings:
```typescript
window.addEventListener('message', onMessage(isChatMessage, {
onSuccess: handleData,
onTypeMismatch: (error) => console.warn('Partial data:', error),
tolerance: true
}));
```
Create type guards for your data structures using Guardz 1.11.3's enhanced type validation:
```typescript
import { isString, isNumber, isBoolean, isObject } from 'guardz';
// Simple object validation
const isUserData = (data: unknown): data is { id: number; name: string } => {
return isObject(data) &&
isNumber((data as any).id) &&
isString((data as any).name);
};
// Complex validation with nested objects
const isOrderData = (data: unknown): data is {
id: string;
items: Array<{ productId: string; quantity: number }>;
total: number;
} => {
if (!isObject(data)) return false;
const order = data as any;
return isString(order.id) &&
Array.isArray(order.items) &&
order.items.every((item: any) =>
isString(item.productId) &&
isNumber(item.quantity)
) &&
isNumber(order.total);
};
```
```typescript
import { isSchema, isShape, isObjectWith, toNumber, toDate } from 'guardz';
// Schema-based validation
const userSchema = {
id: isNumber,
name: isString,
email: isString,
isActive: isBoolean
};
const isUser = isSchema(userSchema);
// Shape-based validation with transformations
const isOrderWithTransform = isShape({
id: isString,
amount: toNumber, // Automatically converts string to number
createdAt: toDate, // Automatically converts string to Date
items: isArrayWithEachItem(isObjectWith({
productId: isString,
quantity: isNumber
}))
});
// Object with specific properties
const isApiResponse = isObjectWith({
success: isBoolean,
data: isObject,
message: isString
});
```
```typescript
import { safePostMessageListener } from 'guardz-event';
window.addEventListener('message', safePostMessageListener(isChatMessage, {
onSuccess: handleData,
onTypeMismatch: handleTypeError,
onSecurityViolation: handleSecurityError,
onError: handleError
}));
```
```typescript
import { onMessage } from 'guardz-event';
window.addEventListener('message', onMessage(isChatMessage, {
onSuccess: handleData,
onTypeMismatch: handleTypeError,
onSecurityViolation: handleSecurityError,
onError: handleError
}));
```
This version includes several new features and improvements:
- **`isSchema`** - Schema-based validation for complex objects
- **`isShape`** - Shape-based validation with automatic data transformation
- **`isObjectWith`** - Validate objects with specific properties
- **`isNestedType`** - Handle nested type validation
- **`isIndexSignature`** - Validate objects with index signatures
- **`toNumber`** - Convert and validate numbers from strings
- **`toDate`** - Convert and validate Date objects from strings
- **`toBoolean`** - Convert and validate booleans from various types
- Better error messages and debugging information
- Improved performance for complex validations
- More flexible type checking options
```bash
npm install guardz-event guardz@^1.11.3
yarn add guardz-event guardz@^1.11.3
```
**Note**: This library requires `guardz` version 1.11.3 or higher as a peer dependency for runtime type validation.
The library is designed to be tree-shakeable. Only the APIs you use will be included in your bundle:
- **Core API only**: ~2KB gzipped
- **All APIs**: ~5KB gzipped
- **With React hooks**: ~6KB gzipped
```typescript
import { onMessage } from 'guardz-event';
import { isShape, toNumber, toDate } from 'guardz';
// Handle messages with automatic data transformation
const isOrderMessage = isShape({
orderId: isString,
amount: toNumber, // Converts string to number
timestamp: toDate, // Converts string to Date
items: isArrayWithEachItem(isObjectWith({
productId: isString,
quantity: toNumber
}))
});
window.addEventListener('message', onMessage(isOrderMessage, {
onSuccess: (data) => {
// data.amount is now a number, data.timestamp is a Date
console.log(`Order ${data.orderId}: $${data.amount} at ${data.timestamp}`);
}
}));
```
```typescript
import { onMessage } from 'guardz-event';
import { isSchema, isObjectWith } from 'guardz';
const apiResponseSchema = {
success: isBoolean,
data: isObject,
message: isString
};
const isApiResponse = isSchema(apiResponseSchema);
window.addEventListener('message', onMessage(isApiResponse, {
onSuccess: (response) => {
if (response.success) {
handleApiData(response.data);
} else {
showError(response.message);
}
}
}));
```
```typescript
import { onClick } from 'guardz-event';
import { isShape, isEmail, toNumber } from 'guardz';
// Enhanced form validation with automatic type conversion
const isFormData = isShape({
email: isEmail, // Validates email format
name: isString,
age: toNumber, // Converts string to number
preferences: isObjectWith({
newsletter: isBoolean,
notifications: isBoolean
})
});
form.addEventListener('submit', onClick(isFormData, {
onSuccess: (data) => {
// data.age is now a number, data.email is validated
submitForm(data);
},
onTypeMismatch: (error) => showValidationError(error)
}));
```
```typescript
import { onCustom } from 'guardz-event';
import { isSchema } from 'guardz';
const userActionSchema = {
action: isString,
payload: isObject,
timestamp: toDate
};
const isUserAction = isSchema(userActionSchema);
document.addEventListener('user-action', onCustom('user-action', isUserAction, {
onSuccess: (data) => {
// data.timestamp is now a Date object
handleUserAction(data.action, data.payload, data.timestamp);
}
}));
```
```typescript
import { onStorage } from 'guardz-event';
import { isShape, toNumber, toBoolean } from 'guardz';
const isStorageData = isShape({
key: isString,
value: isString,
oldValue: isString,
timestamp: toNumber
});
window.addEventListener('storage', onStorage(isStorageData, {
onSuccess: (data) => {
// data.timestamp is now a number
handleStorageChange(data.key, data.value, data.oldValue, data.timestamp);
}
}));
```
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests
5. Submit a pull request
MIT License - see LICENSE file for details.