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
BookingErrorextendsSuaveplanErrorwith eight stable codes.- Zod schemas for
Resource,AvailabilityRule,Booking,BookingPolicy,TimeSlot,BookOptions,CancelOptions. - Waiting-list management — when a slot reaches capacity, new bookings join the waiting list; on cancellation,
notifyWaitingListreturns the next guest IDs to offer the freed slot. - Cancellation policy enforcement — checks clock against
createdAt + cancellationWindowMs; throwsBOOKING_CANCELLATION_WINDOW_EXPIREDwhen outside the free window. - OTel instrumentation — span
booking.transitionwith attributesbooking.resource_id,booking.from_status,booking.to_status; counterbooking.transition_total; histogrambooking.availability_query_duration_ms. - 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.