s-m-r-t

@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-events

Quick 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(); // game

Calendar 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)

Related Modules