@happyvertical/smrt-events
Hierarchical event management with scheduling, ticketing, and calendar integration.
v0.19.0EventsCalendar
Overview
smrt-events provides comprehensive event management with infinite hierarchical nesting, recurring patterns, participant tracking, and conflict detection. Perfect for sports, entertainment, conferences, and municipal meetings.
Installation
bash
npm install @happyvertical/smrt-eventsQuick Start
typescript
import { EventCollection, EventSeriesCollection, EventParticipantCollection } from '@happyvertical/smrt-events';
// Initialize
const events = await EventCollection.create({ db: {...} });
const series = await EventSeriesCollection.create({ db: {...} });
const participants = await EventParticipantCollection.create({ db: {...} });
// Create event series
const playoffs = await series.create({
name: 'NBA Playoffs 2025',
typeId: tournamentTypeId,
organizerId: nbaProfileId,
startDate: new Date('2025-04-15'),
endDate: new Date('2025-06-20')
});
await playoffs.save();
// Create game event
const game = await events.create({
name: 'Lakers vs Warriors',
seriesId: playoffs.id,
typeId: gameTypeId,
placeId: arenaId,
startDate: new Date('2025-04-20T19:00:00'),
endDate: new Date('2025-04-20T21:30:00'),
status: 'scheduled',
round: 1
});
await game.save();
// Add participants
const lakers = await participants.create({
eventId: game.id,
profileId: lakersProfileId,
role: 'home',
placement: 0
});
await lakers.save();
const warriors = await participants.create({
eventId: game.id,
profileId: warriorsProfileId,
role: 'away',
placement: 1
});
await warriors.save();Core Models
Event (Hierarchical)
typescript
class Event extends SmrtObject {
name: string
seriesId?: string // FK to EventSeries
parentEventId?: string // Self-referencing hierarchy
typeId: string // FK to EventType
placeId?: string // FK to Place
description?: string
startDate?: Date
endDate?: Date
status: 'scheduled' | 'in_progress' | 'completed' | 'cancelled' | 'postponed'
round?: number // Sequence in series
metadata?: Record<string, any>
// Hierarchy navigation
async getParent(): Promise<Event | null>
async getChildren(): Promise<Event[]>
async getAncestors(): Promise<Event[]>
async getDescendants(): Promise<Event[]>
async getRootEvent(): Promise<Event>
async getHierarchy(): Promise<Event[]>
// Participants
async getParticipants(): Promise<EventParticipant[]>
// Related data
async getSeries(): Promise<EventSeries | null>
async getType(): Promise<EventType | null>
async getPlace(): Promise<Place | null>
// Status
updateStatus(newStatus: EventStatus): void
isInProgress(): boolean
}EventSeries (Recurring)
typescript
class EventSeries extends SmrtObject {
name: string
typeId: string
organizerId: string // FK to Profile
description?: string
startDate?: Date
endDate?: Date
recurrence?: RecurrencePattern
metadata?: Record<string, any>
async getOrganizer(): Promise<Profile | null>
async getEvents(): Promise<Event[]>
}
interface RecurrencePattern {
frequency: 'daily' | 'weekly' | 'monthly' | 'yearly'
interval?: number // Every N periods
count?: number // Total occurrences
until?: Date // End date
byDay?: string[] // ['MO', 'WE', 'FR']
byMonthDay?: number[] // [1, 15]
byMonth?: number[] // [1-12]
}EventParticipant
typescript
class EventParticipant extends SmrtObject {
eventId: string // FK to Event
profileId: string // FK to Profile
role: string // 'home' | 'away' | 'speaker' | 'performer' | etc.
placement?: number // 0=home, 1=away, etc.
groupId?: string // Team/group identifier
metadata?: Record<string, any>
isHome(): boolean // placement === 0
isAway(): boolean // placement === 1
async getEvent(): Promise<Event | null>
async getProfile(): Promise<Profile | null>
async getGroupParticipants(): Promise<EventParticipant[]>
}Hierarchical Events
typescript
// Create hierarchical game structure
const game = await events.create({
name: 'Lakers vs Warriors',
startDate: new Date('2025-01-20T19:00:00'),
status: 'scheduled'
});
await game.save();
// Create quarters
const q1 = await events.create({
name: '1st Quarter',
parentEventId: game.id,
startDate: new Date('2025-01-20T19:00:00'),
endDate: new Date('2025-01-20T19:12:00')
});
await q1.save();
// Create goal event
const goal = await events.create({
name: 'LeBron 3-pointer',
parentEventId: q1.id,
startDate: new Date('2025-01-20T19:05:23'),
metadata: { points: 3, player: 'LeBron James' }
});
await goal.save();
// Navigate hierarchy
const children = await game.getChildren(); // [q1, q2, q3, q4]
const ancestors = await goal.getAncestors(); // [q1, game]
const root = await goal.getRootEvent(); // gameCalendar Integration
Date Range Queries
typescript
// Get events for week
const weekEvents = await events.getByDateRange(
new Date('2025-01-20'),
new Date('2025-01-27')
);
// Get upcoming events
const upcoming = await events.getUpcoming(10);
// Get in-progress events
const inProgress = await events.getInProgress();
// Check scheduling conflict
import { checkSchedulingConflict } from '@happyvertical/smrt-events/utils';
const hasConflict = checkSchedulingConflict(
event1Start, event1End,
event2Start, event2End
);Recurrence Patterns
typescript
// Weekly meeting series
const weeklySeries = await series.create({
name: 'Team Standup',
recurrence: {
frequency: 'weekly',
byDay: ['MO', 'WE', 'FR'],
until: new Date('2025-12-31')
}
});
// Monthly board meeting
const monthlySeries = await series.create({
name: 'Board Meeting',
recurrence: {
frequency: 'monthly',
byMonthDay: [15], // 15th of each month
count: 12 // 12 occurrences
}
});
// Calculate next occurrence
import { calculateNextOccurrence } from '@happyvertical/smrt-events/utils';
const nextDate = calculateNextOccurrence(pattern, new Date());MeetingView Component (NEW v0.19.0)
svelte
<script>
import { MeetingView } from '@happyvertical/smrt-events/svelte';
const meeting = {
id: 'meeting-123',
slug: 'town-council-2025-01-15',
name: 'Town Council Meeting',
startDate: '2025-01-15T19:00:00',
status: 'scheduled',
agendaUrl: '/docs/agenda.pdf',
minutesUrl: '/docs/minutes.pdf',
videoUrl: 'https://youtube.com/watch?v=...',
council: {
id: 'council-1',
name: 'Oak Creek Town Council',
timezone: 'America/Denver'
}
};
</script>
<MeetingView {meeting} calendarUrl="/calendar" />Best Practices
DOs
- Use event series for recurring events
- Check for conflicts before scheduling
- Store performance data in participant metadata
- Initialize default event types with initializeDefaults()
- Use placement field for home/away or speaker order
DON'Ts
- Don't create circular hierarchies (parent references child)
- Don't transition completed events to other states
- Don't skip validation when changing event status
- Don't delete parent events without handling children
- Don't store large binary data in metadata (use smrt-assets)