@inputless/events
v1.0.6
Published
Type definitions and event schemas for the Inputless Analytics SDK
Maintainers
Readme
@inputless/events
Shared type definitions and event schemas for Inputless Analytics.
Purpose
Provides TypeScript type definitions, validation schemas, and shared interfaces used across all Inputless packages.
Package Boundary & Responsibilities
This package (@inputless/events) defines WHAT an event is, not HOW events are captured.
✅ What This Package Provides:
- Type Definitions: All event interfaces, metadata types, and type unions
- Validation Schemas: Zod schemas for runtime validation
- Type Utilities: Type guards, validation helpers, serialization utilities
- Type Safety: Complete TypeScript definitions for all event structures
- Documentation: JSDoc comments for IDE autocomplete
❌ What This Package Does NOT Provide:
- Browser Event Listeners: No DOM event capture logic
- Event Collection: No browser API integration
- Tracking Implementation: No tracking/batching logic
- Business Logic: No domain-specific tracking rules
- Runtime Dependencies: Minimal dependencies (only
zodfor validation)
Separation from @inputless/tracker:
| Aspect | @inputless/events | @inputless/tracker |
|--------|---------------------|---------------------|
| Purpose | Type system & validation | Event capture & collection |
| Contains | Types, schemas, utilities | Browser listeners, tracking logic |
| Dependencies | zod only | @inputless/events + browser APIs |
| Exports | Type definitions, validators | Tracker classes, collection logic |
| Usage | Used BY tracker (and other packages) | Uses events package types |
Example Boundary:
// ✅ CORRECT: Events package defines types
import { BaseEvent, EventType, UIEventMetadata } from '@inputless/events';
// ✅ CORRECT: Tracker uses events types
import { BaseEvent } from '@inputless/events';
class InputlessTracker {
trackClick(element: HTMLElement): BaseEvent<UIEventMetadata> {
// Tracker creates event using events package types
return {
id: generateEventId(),
type: 'ui.click',
// ... tracker captures data and creates typed event
};
}
}
// ❌ WRONG: Events package should NOT contain browser listeners
// DO NOT add this to events package:
document.addEventListener('click', handler); // ❌ Belongs in trackerWhen to Use This Package:
- You need TypeScript types for events
- You need to validate event data at runtime
- You need to serialize/deserialize events
- You're building a package that works with events
When to Use @inputless/tracker:
- You need to capture user interactions
- You need browser event listeners
- You need to collect events from the DOM
- You're implementing tracking logic
Features
- Type Safety: Complete TypeScript definitions
- Validation Schemas: Zod schemas for runtime validation
- Event Schemas: Comprehensive event data structures
- Documentation: JSDoc comments for all types
Installation
npm install @inputless/eventsDependencies
zod- Schema validation (peer dependency)
Usage
Basic Setup (Local Only - No API Endpoint)
import {
BaseEvent,
UIEventMetadata,
generateEventId,
getSchemaVersion,
DEFAULT_SCHEMA_VERSION,
} from '@inputless/events';
// Create a UI click event
const clickEvent: BaseEvent<UIEventMetadata> = {
id: generateEventId(),
type: 'ui.click',
timestamp: Date.now(), // Unix epoch milliseconds
sessionId: 'sess_a1b2c3d4-e5f6-7890-abcd-ef1234567890',
ctx: {
url: 'https://example.com/products/123',
route: '/products/[id]',
lang: 'en',
},
dev: {
viewport: [1920, 1080],
dpr: 2,
userAgent: 'Mozilla/5.0',
screen: [1920, 1080],
timezone: -480,
deviceType: 'desktop',
},
data: {
selector: '#button-1',
tagName: 'BUTTON',
position: { x: 100, y: 200 },
pagePosition: { x: 100, y: 500 },
},
pii: false,
channel: ['analytics'],
schema: getSchemaVersion(),
};Module Structure
This module contains:
- Types (
src/types/):EventTypes.ts- Base event interface and EventType union (280+ types)MetadataTypes.ts- All metadata structures (UI, Performance, Network, Business, Form, etc.)SessionTypes.ts- Application context, device context, schema versionCognitiveTypes.ts- Cognitive SDK enrichment typesChannelTypes.ts- Channel routing typesBatchTypes.ts- Batch envelope types
- Constants (
src/constants/):EventTypeConstants.ts- Event type arrays and validation utilitiesChannelConstants.ts- Channel constants and default routingSchemaConstants.ts- Schema version constants
- Schemas (
src/schemas/):EventSchemas.ts- Zod validation schemas for all event structuresCognitiveSchemas.ts- Cognitive enrichment validation schemasBatchSchemas.ts- Batch envelope validation schemas
- Serialization (
src/serialization/):EventSerializer.ts- Event JSON serialization/deserializationBatchSerializer.ts- Batch envelope serialization and creation
- Utilities (
src/utils/):EventUtils.ts- Event manipulation utilities (ID generation, filtering, grouping, cloning)TypeGuards.ts- Type guard functions for all metadata typesValidationUtils.ts- Validation helpers with convenient return types
Example 6: Complete Local Event Processing Pipeline
import {
BaseEvent,
generateEventId,
getSchemaVersion,
serializeEvent,
deserializeEvent,
validateEvent,
filterEventsByType,
groupEventsByType,
sortEventsByTimestamp,
isUIEvent,
isBusinessEvent,
containsPII,
getEventAge,
createBatch,
serializeBatch,
} from '@inputless/events';
// Local event storage
const eventStore: BaseEvent[] = [];
// Function to create and store events locally
function createAndStoreEvent(type: string, data: any): BaseEvent {
const event: BaseEvent = {
id: generateEventId(),
type: type as any,
timestamp: Date.now(),
sessionId: 'sess_local_123',
ctx: {
url: window.location.href,
route: window.location.pathname,
lang: navigator.language,
},
dev: {
viewport: [window.innerWidth, window.innerHeight],
dpr: window.devicePixelRatio,
userAgent: navigator.userAgent,
screen: [screen.width, screen.height],
timezone: new Date().getTimezoneOffset(),
deviceType: 'desktop',
},
data,
pii: false,
channel: ['analytics'],
schema: getSchemaVersion(),
};
// Validate before storing
const validation = validateEvent(event);
if (validation.valid) {
eventStore.push(event);
console.log('Event stored:', event.id);
} else {
console.error('Invalid event:', validation.errors);
}
return event;
}
// Create some events
createAndStoreEvent('ui.click', { selector: '#button-1', position: { x: 100, y: 200 } });
createAndStoreEvent('form.submit', { formId: 'checkout', fieldCount: 5 });
createAndStoreEvent('biz.purchase', { productId: 'prod-123', amount: 99.99 });
// Process events locally
console.log('Total events:', eventStore.length);
// Filter UI events
const uiEvents = filterEventsByType(eventStore, ['ui.click', 'ui.hover']);
console.log('UI events:', uiEvents.length);
// Group by type
const grouped = groupEventsByType(eventStore);
console.log('Events by type:', Array.from(grouped.entries()).map(([type, events]) => ({
type,
count: events.length,
})));
// Sort by timestamp
const sorted = sortEventsByTimestamp(eventStore, 'desc');
console.log('Latest event:', sorted[0]?.type);
// Check for PII
const eventsWithPII = eventStore.filter(containsPII);
console.log('Events with PII:', eventsWithPII.length);
// Serialize events for local storage
const serialized = eventStore.map(serializeEvent);
localStorage.setItem('events', JSON.stringify(serialized));
// Create batch for export
const batch = createBatch(eventStore, {
sdkVersion: '1.0.0',
tenantId: 'local',
sessionId: 'sess_local_123',
});
const batchJson = serializeBatch(batch);
console.log('Batch JSON size:', batchJson.length);Type Definitions
BaseEvent
interface BaseEvent<T extends EventMetadata = EventMetadata> {
id: string; // Unique event identifier (UUID v4)
type: EventType; // Event type (280+ types)
timestamp: number; // Unix epoch milliseconds
sessionId: string; // Session identifier (UUID v4)
userId?: string; // Optional user identifier
ctx: ApplicationContext; // Application context
dev: DeviceContext; // Device/browser context
data: T; // Event-specific metadata
pii: boolean; // PII flag
channel: Channel[]; // Target channels
schema: SchemaVersion; // Schema version
cog?: CognitiveEnrichment; // Cognitive enrichment (optional)
explain_local?: string; // Local explainability (optional)
}EventType
// 280+ event types across categories:
// - ui.* (click, hover, focus, etc.)
// - form.* (submit, validation, etc.)
// - perf.* (lcp, fcp, cls, etc.)
// - net.* (request, response, etc.)
// - biz.* (purchase, cart, etc.)
// - And many more...
type EventType =
| 'ui.click'
| 'ui.hover'
| 'form.submit'
| 'perf.lcp'
| 'biz.purchase'
// ... 280+ more typesValidation
import { BaseEventSchema, validateEvent } from '@inputless/events';
// Using Zod schema directly
const zodResult = BaseEventSchema.safeParse(eventData);
if (zodResult.success) {
const validEvent = zodResult.data;
}
// Using validation utility
const result = validateEvent(eventData);
if (result.valid) {
const validEvent = result.data;
} else {
console.error('Validation errors:', result.errors);
}Exports
Types
BaseEvent<T>- Main event interface (generic with metadata type)EventType- Event type union (280+ types)ApplicationContext- Application/page contextDeviceContext- Device/browser contextSchemaVersion- Schema version informationCognitiveEnrichment- Cognitive SDK enrichment dataChannel- Channel routing typeBatchEnvelope- Batch envelope structure- All metadata types (
UIEventMetadata,PerformanceEventMetadata, etc.)
Constants
ALL_EVENT_TYPES- Complete list of all event typesCHANNELS- All available channelsCURRENT_SCHEMA_VERSION- Current schema versionisValidEventType()- Validate event type stringsgetDefaultChannels()- Get default channels for event type
Validation Schemas (Zod)
BaseEventSchema- Validate base event structureCognitiveEnrichmentSchema- Validate cognitive dataBatchEnvelopeSchema- Validate batch envelopesvalidateEvent()- Validate event with detailed errorsvalidateBatch()- Validate batch envelope
Serialization
serializeEvent()- Serialize event to JSONdeserializeEvent()- Deserialize JSON to eventserializeBatch()- Serialize batch envelope to JSONdeserializeBatch()- Deserialize JSON to batchcreateBatch()- Create batch envelope from events array
Utilities
generateEventId()- Generate unique event identifierisInChannel()- Check if event is routed to specific channelcontainsPII()- Check if event contains PIIgetEventAge()- Get event age in millisecondsisEventExpired()- Check if event is expiredcloneEvent()- Deep clone eventmergeEvents()- Merge multiple events for aggregationfilterEventsByType()- Filter events by event typefilterEventsByChannel()- Filter events by channelgroupEventsByType()- Group events by type into MapsortEventsByTimestamp()- Sort events by timestamp- Type guards:
isUIEvent(),isPerformanceEvent(),isNetworkEvent(), etc. validateEvent()- Validate event with convenient return typevalidateBatch()- Validate batch with convenient return typesanitizeEvent()- Validate and sanitize malformed eventsformatValidationErrors()- Format Zod errors for display
Usage Examples
Example 1: Creating Different Event Types Locally
import {
BaseEvent,
UIEventMetadata,
FormEventMetadata,
PerformanceEventMetadata,
BusinessEventMetadata,
generateEventId,
getSchemaVersion,
} from '@inputless/events';
// Create UI click event
const clickEvent: BaseEvent<UIEventMetadata> = {
id: generateEventId(),
type: 'ui.click',
timestamp: Date.now(),
sessionId: 'sess_abc123',
ctx: {
url: 'https://example.com',
route: '/',
lang: 'en',
},
dev: {
viewport: [1920, 1080],
dpr: 2,
userAgent: 'Mozilla/5.0',
screen: [1920, 1080],
timezone: -480,
deviceType: 'desktop',
},
data: {
selector: '#button-1',
tagName: 'BUTTON',
position: { x: 100, y: 200 },
},
pii: false,
channel: ['analytics'],
schema: getSchemaVersion(),
};
// Create form submit event
const formEvent: BaseEvent<FormEventMetadata> = {
id: generateEventId(),
type: 'form.submit',
timestamp: Date.now(),
sessionId: 'sess_abc123',
ctx: {
url: 'https://example.com/checkout',
route: '/checkout',
lang: 'en',
},
dev: {
viewport: [1920, 1080],
dpr: 2,
userAgent: 'Mozilla/5.0',
screen: [1920, 1080],
timezone: -480,
deviceType: 'desktop',
},
data: {
formId: 'checkout-form',
fieldCount: 5,
completionTime: 12000, // milliseconds
},
pii: false,
channel: ['analytics'],
schema: getSchemaVersion(),
};
// Create performance event
const perfEvent: BaseEvent<PerformanceEventMetadata> = {
id: generateEventId(),
type: 'perf.lcp',
timestamp: Date.now(),
sessionId: 'sess_abc123',
ctx: {
url: 'https://example.com',
route: '/',
lang: 'en',
},
dev: {
viewport: [1920, 1080],
dpr: 2,
userAgent: 'Mozilla/5.0',
screen: [1920, 1080],
timezone: -480,
deviceType: 'desktop',
},
data: {
value: 2400, // milliseconds
rating: 'good',
},
pii: false,
channel: ['analytics'],
schema: getSchemaVersion(),
};
// Create business event (e-commerce)
const businessEvent: BaseEvent<BusinessEventMetadata> = {
id: generateEventId(),
type: 'biz.purchase',
timestamp: Date.now(),
sessionId: 'sess_abc123',
ctx: {
url: 'https://example.com/checkout',
route: '/checkout',
lang: 'en',
},
dev: {
viewport: [1920, 1080],
dpr: 2,
userAgent: 'Mozilla/5.0',
screen: [1920, 1080],
timezone: -480,
deviceType: 'desktop',
},
data: {
productId: 'prod-123',
amount: 99.99,
currency: 'USD',
quantity: 1,
},
pii: false,
channel: ['analytics'],
schema: getSchemaVersion(),
};Example 2: Validating Events Locally
import {
BaseEventSchema,
validateEvent,
sanitizeEvent,
formatValidationErrors,
type ValidationResult,
} from '@inputless/events';
// Validate event
const result: ValidationResult<BaseEvent> = validateEvent(eventData);
if (result.valid) {
// Event is valid
const validEvent = result.data!;
console.log('Valid event:', validEvent);
} else {
// Event has validation errors
console.error('Validation errors:', result.errors);
// Format errors for display
const formattedErrors = formatValidationErrors(result.errors!);
console.error('Formatted errors:', formattedErrors);
}
// Sanitize malformed event
// Attempts to fix invalid events by removing invalid fields
const sanitized = sanitizeEvent(malformedEventData);
if (sanitized) {
console.log('Sanitized event:', sanitized);
} else {
console.error('Event could not be sanitized');
}
// Using Zod schema directly
const zodResult = BaseEventSchema.safeParse(eventData);
if (zodResult.success) {
const validEvent = zodResult.data;
} else {
console.error('Zod errors:', zodResult.error.format());
}Example 3: Serializing and Deserializing Events Locally
import {
serializeEvent,
deserializeEvent,
serializeEventPretty,
createBatch,
serializeBatch,
deserializeBatch,
serializeBatchPretty,
type BaseEvent,
} from '@inputless/events';
// Serialize single event
const json = serializeEvent(event);
console.log('Serialized event:', json);
// Serialize with pretty formatting (for debugging)
const prettyJson = serializeEventPretty(event);
console.log('Pretty JSON:', prettyJson);
// Deserialize event
const deserializedEvent: BaseEvent = deserializeEvent(json);
console.log('Deserialized event:', deserializedEvent);
// Create batch envelope
const batch = createBatch([event1, event2, event3], {
sdkVersion: '1.0.0',
tenantId: 'acme',
sessionId: 'sess_abc123',
});
// Serialize batch
const batchJson = serializeBatch(batch);
console.log('Serialized batch:', batchJson);
// Serialize batch with pretty formatting
const prettyBatchJson = serializeBatchPretty(batch);
console.log('Pretty batch JSON:', prettyBatchJson);
// Deserialize batch
const deserializedBatch = deserializeBatch(batchJson);
console.log('Deserialized batch:', deserializedBatch);
console.log('Batch events:', deserializedBatch.events);Example 4: Using Constants and Event Type Utilities
import {
isValidEventType,
getDefaultChannels,
getEventCategory,
isEventInCategory,
CHANNELS,
UI_EVENT_TYPES,
FORM_EVENT_TYPES,
PERFORMANCE_EVENT_TYPES,
BUSINESS_EVENT_TYPES,
ALL_EVENT_TYPES,
CURRENT_SCHEMA_VERSION,
DEFAULT_SCHEMA_VERSION,
isSchemaVersionSupported,
getSchemaVersion,
} from '@inputless/events';
// Validate event type
if (isValidEventType('ui.click')) {
console.log('Valid event type');
}
// Get default channels for event type
const channels = getDefaultChannels('ui.click'); // ['analytics']
console.log('Default channels:', channels);
// Get all available channels
const allChannels = CHANNELS; // ['analytics', 'ads', 'security']
console.log('All channels:', allChannels);
// Get event category
const category = getEventCategory('ui.click'); // 'ui'
console.log('Event category:', category);
// Check if event is in category
if (isEventInCategory('ui.click', 'ui')) {
console.log('Event is a UI event');
}
// Access event type arrays
console.log('UI events:', UI_EVENT_TYPES);
console.log('Form events:', FORM_EVENT_TYPES);
console.log('Performance events:', PERFORMANCE_EVENT_TYPES);
console.log('Business events:', BUSINESS_EVENT_TYPES);
console.log('All event types:', ALL_EVENT_TYPES.length); // 280+ types
// Schema version utilities
console.log('Current schema version:', CURRENT_SCHEMA_VERSION); // 1
console.log('Default schema:', DEFAULT_SCHEMA_VERSION); // { v: 1, namespace: 'inputless.v1' }
console.log('Is version 1 supported?', isSchemaVersionSupported(1)); // true
console.log('Is version 2 supported?', isSchemaVersionSupported(2)); // false
// Get schema version
const schema = getSchemaVersion(); // { v: 1, namespace: 'inputless.v1' }
const schemaV1 = getSchemaVersion(1); // { v: 1, namespace: 'inputless.v1' }Example 5: Using Event Utilities for Local Processing
import {
generateEventId,
isInChannel,
containsPII,
getEventAge,
isEventExpired,
cloneEvent,
filterEventsByType,
filterEventsByChannel,
groupEventsByType,
sortEventsByTimestamp,
isUIEvent,
isPerformanceEvent,
isBusinessEvent,
isFormEvent,
validateEvent,
type BaseEvent,
} from '@inputless/events';
// Generate event ID
const eventId = generateEventId(); // "evt_1699123456789_a1b2c3d4"
console.log('Generated ID:', eventId);
// Check if event is in channel
if (isInChannel(event, 'analytics')) {
console.log('Event is routed to analytics');
}
// Check if event contains PII
if (containsPII(event)) {
console.log('Event contains PII - apply privacy minimization');
}
// Get event age
const age = getEventAge(event); // milliseconds since event timestamp
console.log(`Event is ${age}ms old`);
// Check if event is expired
const maxAge = 5 * 60 * 1000; // 5 minutes
if (isEventExpired(event, maxAge)) {
console.log('Event is expired - discard');
}
// Clone event
const clonedEvent = cloneEvent(event);
console.log('Cloned event:', clonedEvent);
// Filter events by type
const clickEvents = filterEventsByType(events, ['ui.click', 'ui.dblclick']);
console.log('Click events:', clickEvents);
// Filter events by channel
const analyticsEvents = filterEventsByChannel(events, 'analytics');
console.log('Analytics events:', analyticsEvents);
// Group events by type
const grouped = groupEventsByType(events);
const clicks = grouped.get('ui.click');
console.log('Click events count:', clicks?.length || 0);
// Sort events by timestamp
const sortedEvents = sortEventsByTimestamp(events, 'asc');
console.log('Sorted events (oldest first):', sortedEvents);
// Use type guards
if (isUIEvent(event)) {
// TypeScript knows event.data is UIEventMetadata
const uiData = event.data;
console.log('UI event selector:', uiData.selector);
}
if (isPerformanceEvent(event)) {
// TypeScript knows event.data is PerformanceEventMetadata
const perfData = event.data;
console.log('Performance value:', perfData.value);
}
if (isBusinessEvent(event)) {
// TypeScript knows event.data is BusinessEventMetadata
const bizData = event.data;
console.log('Business amount:', bizData.amount);
}
if (isFormEvent(event)) {
// TypeScript knows event.data is FormEventMetadata
const formData = event.data;
console.log('Form ID:', formData.formId);
}
// Validate event
const result = validateEvent(eventData);
if (result.valid) {
console.log('Valid event:', result.data);
} else {
console.error('Validation errors:', result.errors);
}Distribution
npm package: @inputless/events
Version: 1.0.0+
Registry: npm
Implementation Status
✅ Type Definitions: Complete (BaseEvent, 280+ EventTypes, all MetadataTypes)
✅ Constants: Complete (EventTypeConstants, ChannelConstants, SchemaConstants)
✅ Validation Schemas: Complete (Zod schemas for all structures)
✅ Serialization: Complete (JSON serialization/deserialization, batch creation)
✅ Utility Functions: Complete (EventUtils, TypeGuards, ValidationUtils)
✅ Unit Tests: Complete (9 test suites, 222 tests, all passing)
Package Status
🚀 Production Ready: The core @inputless/events package is complete and ready for use. All type definitions, validation schemas, serialization utilities, and helper functions are implemented.
✅ Testing Complete: Comprehensive unit tests implemented with Jest. All 222 tests passing across 9 test suites.
Testing
Unit tests are implemented following the test strategy in doc/TEST_STRATEGY.md:
Test Structure
- Framework: Jest with ts-jest
- Test Suites: 9 test suites, 222 tests, all passing ✅
- Test Structure: Mirrors
src/structure in__tests__/
Test Files
- ✅
__tests__/helpers/testHelpers.ts- Test fixtures and utilities - ✅
__tests__/utils/EventUtils.test.ts- 11 utility functions tested - ✅
__tests__/utils/ValidationUtils.test.ts- Validation helpers tested - ✅
__tests__/utils/TypeGuards.test.ts- 19+ type guard functions tested - ✅
__tests__/schemas/EventSchemas.test.ts- Zod validation schemas tested - ✅
__tests__/schemas/CognitiveSchemas.test.ts- Cognitive enrichment schemas tested - ✅
__tests__/schemas/BatchSchemas.test.ts- Batch envelope schemas tested - ✅
__tests__/serialization/EventSerializer.test.ts- Event serialization tested - ✅
__tests__/serialization/BatchSerializer.test.ts- Batch serialization tested - ✅
__tests__/integration/FullEventFlow.test.ts- End-to-end integration tests
Running Tests
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverageCoverage
- Utilities: Comprehensive coverage of all utility functions
- Validation: Complete coverage of validation schemas and helpers
- Serialization: Full coverage of serialization/deserialization
- Integration: End-to-end flow tests covering complete event lifecycle
