Files
suaveplan/openspec/changes/enterprise-booking/proposal.md
2026-05-27 23:29:53 +01:00

6.9 KiB

OpenSpec Change: enterprise-booking

  • Change ID: enterprise-booking
  • Package: @suaveplan/booking
  • Category: universal
  • Target path: universal/commerce/booking
  • Status: IN PROGRESS
  • Date: 2026-04-24
  • Reference spec: /mnt/s/web/suaveplan/enterprise/booking.md

Summary

Deliver @suaveplan/booking on the genesis branch from scratch at enterprise quality. The package provides a universal booking engine: resource definitions (time-slotted and non-time-slotted), availability calendars with customisable rule sets (working hours, blackout dates, capacity limits), booking lifecycle state machine (pending → confirmed → completed / cancelled / no_show), cancellation and rescheduling policies (free window, penalty fees), waiting-list management, calendar synchronisation hooks (provider-agnostic), recurring bookings, and an in-memory BookingStore reference adapter. The main-branch version sat at 99.06% coverage. Genesis adds structured errors, Zod schemas, OTel instrumentation, AbortSignal, idempotency enforcement, and property-based tests.


Current-state audit (genesis)

The package does not exist on genesis yet — this is a fresh build.


Available workspace deps on genesis

Package Role
@suaveplan/error (workspace:^) BookingError; BOOKING.* error codes
@suaveplan/types (workspace:^) LoggerLike, Clock, EventBusLike
@suaveplan/events (workspace:^) Booking lifecycle event bus
@suaveplan/datetime (workspace:^) Business-day arithmetic, timezone-aware slot generation
@suaveplan/idempotency (workspace:^) Per-booking idempotency guard
@suaveplan/fsm (workspace:^) FSM interpreter for booking state machine
zod (catalog:forms) Schema validation
@opentelemetry/api (catalog:observability) Spans + counters

Dev deps: vitest, @vitest/coverage-istanbul, fast-check (all catalog:testing).


Public API surface

Core types

type BookingStatus = 'pending' | 'confirmed' | 'completed' | 'cancelled' | 'no_show'

interface Resource {
  id: string
  name: string
  type: 'time_slot' | 'all_day' | 'unit'
  capacity: number          // default 1
  rules: AvailabilityRule[]
  metadata: Record<string, unknown>
}

interface AvailabilityRule {
  type: 'working_hours' | 'blackout' | 'capacity_override'
  daysOfWeek?: number[]     // 0=Sun…6=Sat
  startTime?: string        // HH:MM
  endTime?: string          // HH:MM
  dates?: string[]          // ISO date strings for blackouts
  capacity?: number
  timezone?: string
}

interface Booking {
  id: string
  resourceId: string
  guestId: string
  status: BookingStatus
  start: Date
  end?: Date                // optional for all_day and unit resources
  quantity: number          // default 1
  policy: BookingPolicy
  history: BookingTransition[]
  metadata: Record<string, unknown>
  createdAt: Date
  updatedAt: Date
}

interface BookingPolicy {
  cancellationWindowMs: number   // free cancellation period from booking time
  cancellationFeeMinorUnits?: number
  rescheduleWindowMs?: number
  requiresConfirmation?: boolean // default false (auto-confirms)
}

interface TimeSlot {
  start: Date
  end: Date
  available: number   // remaining capacity
}

BookingEngine

class BookingEngine {
  constructor(options: BookingEngineOptions)
  addResource(resource: Resource): void
  getAvailability(resourceId: string, from: Date, to: Date, options?: AvailabilityOptions): TimeSlot[]
  book(options: BookOptions): Promise<Booking>
  confirm(bookingId: string): Promise<Booking>
  cancel(bookingId: string, options?: CancelOptions): Promise<Booking>
  reschedule(bookingId: string, newStart: Date, newEnd?: Date): Promise<Booking>
  complete(bookingId: string): Promise<Booking>
  markNoShow(bookingId: string): Promise<Booking>
  get(bookingId: string): Booking | undefined
  listByResource(resourceId: string, filter?: BookingFilter): Booking[]
  waitingList(resourceId: string, slot: Date): string[]
  addToWaitingList(resourceId: string, slot: Date, guestId: string): void
  notifyWaitingList(resourceId: string, slot: Date): string[]
}

interface BookingEngineOptions {
  store?: BookingStore
  clock?: Clock
  logger?: LoggerLike
  bus?: EventBusLike
}

Error codes

type BookingErrorCode =
  | 'BOOKING_NOT_FOUND'
  | 'BOOKING_RESOURCE_NOT_FOUND'
  | 'BOOKING_UNAVAILABLE'
  | 'BOOKING_INVALID_TRANSITION'
  | 'BOOKING_CANCELLATION_WINDOW_EXPIRED'
  | 'BOOKING_RESCHEDULE_FAILED'
  | 'BOOKING_CAPACITY_EXCEEDED'
  | 'BOOKING_IDEMPOTENCY_CONFLICT'

Genesis additions

  1. BookingError extends SuaveplanError with eight stable codes.
  2. Zod schemas for Resource, AvailabilityRule, Booking, BookingPolicy, TimeSlot, BookOptions, CancelOptions.
  3. Waiting-list management — when a slot reaches capacity, new bookings join the waiting list; on cancellation, notifyWaitingList returns the next guest IDs to offer the freed slot.
  4. Cancellation policy enforcement — checks clock against createdAt + cancellationWindowMs; throws BOOKING_CANCELLATION_WINDOW_EXPIRED when outside the free window.
  5. OTel instrumentation — span booking.transition with attributes booking.resource_id, booking.from_status, booking.to_status; counter booking.transition_total; histogram booking.availability_query_duration_ms.
  6. Property-based tests — availability slot generation is monotonic (no overlaps, correct ordering) for arbitrary working-hours rules; state machine transition invariants.

File layout

universal/commerce/booking/
  package.json
  tsconfig.json
  vitest.config.ts
  src/
    index.ts
    error-codes.ts
    constants/
      index.ts
    types/
      common.ts
      common.md
    core/
      booking-engine/
        booking-engine.ts
        booking-engine.md
        booking-engine.types.ts
        booking-engine.test.ts
        index.ts
      booking-fsm/
        booking-fsm.ts
        booking-fsm.md
        booking-fsm.test.ts
        index.ts
    availability/
      availability-calculator/
        availability-calculator.ts
        availability-calculator.md
        availability-calculator.test.ts
        index.ts
      rule-evaluator/
        rule-evaluator.ts
        rule-evaluator.md
        rule-evaluator.test.ts
        index.ts
    waiting-list/
      waiting-list/
        waiting-list.ts
        waiting-list.md
        waiting-list.test.ts
        index.ts
    store/
      memory-store/
        memory-store.ts
        memory-store.md
        memory-store.test.ts
        index.ts
    __benchmarks__/
      booking.bench.ts
  tests/
    integration.test.ts
    property.test.ts

Impact

Downstream consumers: @suaveplan/payment integration for booking deposits; any scheduling/reservation UI.

Workspace dep prerequisites: @suaveplan/error, @suaveplan/types, @suaveplan/events, @suaveplan/datetime, @suaveplan/idempotency, @suaveplan/fsm.