Files
2026-05-27 23:29:53 +01:00

21 KiB

Add @suaveplan/booking — universal JSON-configurable booking engine

Package: @suaveplan/booking Status: PROPOSED Type: migration (upstream ../suaveplan/packages/security/booking)

  • Design: ./design.md
  • Spec delta: ./specs/booking/spec.md

Why

STATUS.md lists @suaveplan/booking as Pending. The upstream package is a substantial, universal-tier, runtime-pure library (51 source files across 11 feature groups, 27 test files, ~99% upstream coverage) that owns a problem genesis currently has no answer for: a JSON-driven booking engine that runs identically in Bun, Node, browsers, and Web Workers and covers six distinct domains (seats, accommodation, appointments, resources, activities, transport) through a single pipeline (availability → conflict detection → pricing → rules → state machine → events). No genesis package covers this surface today, and a string of pending downstream packages (@suaveplan/r3f-booking, the api-plugin-bank/api-plugin-banking plugins, future commerce-tier UIs) depend on the BookingSystemConfig, Bookable, Booking, and BookingEngine contracts. Migrating it now unblocks every booking-aware package downstream.

The migration upgrades the package to genesis quality: every plain throw new Error(...) becomes a BookingError extending SuaveplanError with a stable BOOKING.* code registry; the inline @suaveplan/events/hooks dependency (not migrated on genesis) is replaced by a small in-package Hooks<TMap> implementation that ships under src/hooks/ with the same public surface the upstream engine consumed; subpath exports become exhaustive (one per public module) so consumers tree-shake; every implementation file gets a sibling .md; coverage moves from upstream's ~99% to 100/100/100/100; the package starts at 0.1.0. The enterprise audit (../suaveplan/enterprise/booking.md) lists ambitious additions (property-based tests, full OTel observability, persistence-tier integration, AbortSignal propagation, feature flags) — the typed-error upgrade and the in-package Hooks are in scope here; the other audit items are explicitly deferred to follow-up changes.

What changes

  • ADDED: BookingEngine<TBookableMeta, TBookingMeta> and createBookingEngine — the pipeline orchestrator with configure, registerBookable, getBookable, listBookables, checkAvailability, getAvailableSlots, createHold, getHold, confirmBooking, cancelBooking, modifyBooking, calculatePrice, joinWaitlist, on, off, getBooking, and the bookingHooks getter exposing the in-package Hooks<BookingHookMap>.
  • ADDED: StateMachine and createStateMachine — configurable status-transition state machine with canTransition, getAvailableTransitions, getAutoTransitions, getAllStatuses. TransitionRequest/TransitionResult types preserved.
  • ADDED: EventBus and createEventBus — typed booking event bus with on/off/emit/clear/listenerCount/eventTypes.
  • ADDED: Hooks<TMap extends HookMap> — in-package data-transform pipeline (replaces the upstream @suaveplan/events/hooks dependency that does not exist on genesis). Public surface preserves hook/unhook/unhookAll/applyHooks/applyHooksAsync/applyHooksWithResult/hookNames/hookCount/hasHook. Types HookMap/HookContext/HookHandler/HookHandlerOptions/HookResult/RegisteredHook re-exported.
  • ADDED: Temporal — windowsOverlap, windowsOverlapWithBuffer, mergeWindows, splitWindow, windowDurationMs, windowContains, createWindow, isValidWindow, windowsAdjacent; expandRecurrence, isRecurring, createRecurrenceRule; generateTimeSlots, generateDailySlots, filterAvailableSlots, reduceSlotCapacity.
  • ADDED: Conflict detection — pluggable registerDetector/detectConflicts/getRegisteredModes/clearDetectorRegistry registry with four built-ins (detectExactMatch, detectRangeOverlap, detectSlotCapacity, detectProviderOverlap). OverlapResult/ConflictDetectionMode/ConflictDetectorFn/CapacityContext types exported.
  • ADDED: Pricing — calculatePrice, calculateBasePrice, applyTiers, applyModifiers, calculateTax; applySeasonalRate, calculateSeasonalMultiplier, findApplicableRates, getSeasonName; applyDynamicPricing, calculateDynamicMultiplier, createDynamicContext, findApplicableDynamicRules. PriceCalculationContext/PricingPipelineStep/DynamicPricingContext types exported.
  • ADDED: Rules — evaluateRule, evaluateRules evaluator with 11 operators (eq, neq, gt, gte, lt, lte, in, not_in, between, regex, default deny); built-in condition factories createMinDurationCondition, createMaxDurationCondition, createAdvanceBookingCondition, createQuantityRangeCondition, createAttributeCondition, createWeekendCondition, createStatusCondition, createCustomerLimitCondition. EvaluationContext/ConditionEvaluatorFn types exported.
  • ADDED: Availability — pluggable registerAvailabilityStrategy/resolveAvailability/getRegisteredStrategies/clearStrategyRegistry with three built-ins (exactMatchStrategy, rangeOverlapStrategy, capacityStrategy). AvailabilityStrategyFn/SlotAvailability types exported.
  • ADDED: Domain presets — createBaseConfig, createSeatConfig, createAccommodationConfig, createAppointmentConfig, createResourceConfig, createActivityConfig, createTransportConfig. Domain meta types SeatBookableMeta, AccommodationBookableMeta, AppointmentBookableMeta, ResourceBookableMeta, ActivityBookableMeta, TransportBookableMeta exported.
  • ADDED: Schemas — bookableSchema, bookingSchema, bookingRequestSchema, holdRequestSchema, timeWindowSchema, validateBookable, validateBooking, validateBookingRequest; bookingSystemConfigSchema, validateConfig, safeValidateConfig.
  • ADDED: Utils — parseDurationMs (ISO-8601 duration), clampNumber, deepFreeze, generateId, isNonEmptyString.
  • ADDED: Constants — DEFAULT_HOLD_DURATION_MINUTES, DEFAULT_MAX_HOLD_DURATION_MINUTES, DEFAULT_MAX_ADVANCE_DAYS, DEFAULT_SCHEMA_VERSION, DEFAULT_CURRENCY, DEFAULT_TAX_STRATEGY, DEFAULT_ROUNDING_MODE, DEFAULT_ROUNDING_PRECISION, DEFAULT_AVAILABILITY_REFRESH_MS, DEFAULT_CACHE_TTL_MS, DEFAULT_SLOT_DURATION_MS, DEFAULT_LOCALE, DEFAULT_DATE_FORMAT, DEFAULT_TIME_FORMAT, DEFAULT_FIRST_DAY_OF_WEEK.
  • ADDED: BOOKING_CODES registry via defineCodes("BOOKING", [...]) and BookingError extends SuaveplanError<BookingErrorCode> carrying optional bookingId/holdId/mode/strategy discriminants. Codes: ENGINE_NOT_CONFIGURED, BOOKING_NOT_FOUND, BOOKING_CONFLICT, TRANSITION_NOT_ALLOWED, UNKNOWN_CONFLICT_MODE, UNKNOWN_AVAILABILITY_STRATEGY, HOOK_RETURNED_PROMISE_IN_SYNC.
  • ADDED: All public types — Bookable, BookableId, BookableQuery, BookableQueryResult, Booking, BookingId, BookingModification, BookingRequest, CancellationResult, CancellationTerms, HoldRequest, WaitlistRequest, BookingHold, CapacityPool, OverbookingStrategy, WaitlistEntry, BookingSystemConfig and every sub-type (TenantConfig, EngineConfig, StrategyRegistration, HoldConfig, WaitlistConfig, NotificationConfig, NotificationTrigger, StatusTransition, AttributeDefinition, BookableTypeDefinition, LayoutConfig, LocaleConfig, AvailabilityConfig, ConflictDetectorConfig, PricingEngineConfig), BookingEvent, BookingEventHandler, BookingEventType, EventSubscription, LayoutColorScheme, LayoutNode, NodePosition, BookingPrice, DynamicPricingRule, PriceModifier, PriceRule, PriceTier, SeasonalRate, BookingRule, RuleAction, RuleCondition, RuleEvaluationResult, RecurrenceRule, TimeSlot, TimeWindow, BookingHookMap, BookingCreatingHookData, BookingCancellingHookData, BookingCreatingHandler, BookingCancellingHandler, AvailabilityQuery, AvailabilityResult, BookingEngineInterface, ConflictResult, PriceCalculationRequest, TransitionRequest, TransitionResult.
  • ADDED: Subpath exports per feature module (29 subpaths). Tree-shakeable.

Out of scope

  • Property-based tests via fast-check. Listed in the enterprise audit. The upstream test suite covers every operator, allocation invariant, conflict mode, and pipeline branch at ~99% coverage with example-based tests; reaching 100% on the migrated surface is the gate, and fast-check will be folded in once the deterministic suite is green. Reason: deterministic suite hits the 100% gate first; property tests are an additive quality layer, not a parity requirement.
  • OpenTelemetry observability surface (spans/counters/histograms on every public entry point). Listed in the enterprise audit. Reason: the universal-tier package has no in-process observability today, and adding it requires designing a LoggerLike/MeterLike dependency injection contract that the upstream package does not have. Belongs to a follow-up add-booking-observability change after the enterprise observability story for universal-tier packages stabilises.
  • AbortSignal propagation through every async path. The package surface is currently synchronous (the only async path is Hooks.applyHooksAsync); upstream does not thread AbortSignal. Adding cancellation to a sync surface requires public-API changes outside the parity scope. Reason: behaviour-preserving migration; cancellation is a separate concern.
  • Persistence-tier integration (StorageLike/DatabaseLike backed bookable+booking repositories). Upstream uses an in-memory Map for both. Reason: the parity contract is the in-memory engine; persistence belongs to @suaveplan/booking-persistence follow-up that consumes this package.
  • Feature-flag-gated rollouts of risky new behaviours. Listed in the enterprise audit. Reason: the migration adds no new risky behaviours; the typed-error replacement is a strict additive narrowing.
  • i18n keys for error messages. Errors currently carry English strings. Reason: @suaveplan/error exposes an i18n resolver hook; wiring booking-specific message keys is a follow-up when the genesis i18n catalog for booking lands.
  • Migration to @suaveplan/events/hooks once that subpath ships. Genesis @suaveplan/events does not yet expose /hooks or /hooks.types. The migration ships a small Hooks<TMap> implementation inside src/hooks/ with the same public API; once events ships the hooks subpath, a follow-up change can replace the inline impl with the dependency import without breaking consumers. Reason: dependency unavailability — see §0b parity rule.
  • Benchmarks via mitata. §9 of CLAUDE.md asks for src/__benchmarks__/. Reason: deferred — coverage and gates are the primary acceptance criteria; benchmarks land in a follow-up alongside the observability surface.
  • @suaveplan/schema-registry integration for versioned config schemas. Listed in the enterprise audit. Reason: schema-registry consumers don't exist on genesis yet for this package; will land when first network-tier consumer surfaces.

Source mapping

This change derives from ../suaveplan/enterprise/booking.md and the upstream source at ../suaveplan/packages/security/booking/src/.

Requirement Source lines Notes
Universal-tier purity (zero browser/Node/React deps) enterprise/booking.md L37; CLAUDE.md cross-cutting Reinforced here
Pluggable booking engine pipeline (availability → conflict → pricing → rules → state machine → events) upstream src/core/engine/engine.ts L1-L441 Verbatim behaviour, typed errors
Configurable status state machine upstream src/core/state-machine/state-machine.ts L1-L98 Verbatim
Typed booking event bus (pub/sub for 15 event types) upstream src/core/event-bus/event-bus.ts L1-L96 Verbatim
In-package Hooks<TMap> data-transform pipeline upstream src/types/booking-hooks.ts + @suaveplan/events/hooks Migration replaces external dep with in-package impl (see Out of Scope)
Temporal window operations (overlap, merge, split, duration, contains, adjacent, validity) upstream src/temporal/windows/windows.ts L1-L125 Verbatim
Recurrence rule expansion (daily/weekly/monthly/yearly + exceptions + days-of-week) upstream src/temporal/recurrence/recurrence.ts L1-L100 Verbatim
Time-slot generation (fixed interval + daily-window helpers + filtering + capacity reduction) upstream src/temporal/slots/slots.ts L1-L94 Verbatim
Pluggable conflict-detector registry with four built-in modes upstream src/conflict/detector/detector.ts + src/conflict/{exact-match,range-overlap,slot-capacity,provider-overlap}/*.ts Verbatim, typed errors
Price calculation pipeline (base → tiers → modifiers → tax → rounding) upstream src/pricing/calculator/calculator.ts L1-L179 Verbatim
Seasonal rate resolver (wrap-around year, multiplier, season name lookup) upstream src/pricing/seasonal/seasonal.ts L1-L80 Verbatim
Dynamic pricing rules (occupancy / advance days / demand score triggers) upstream src/pricing/dynamic/dynamic.ts L1-L62 Verbatim
Rule evaluator with 11 operators and condition aggregation logic (all/any) upstream src/rules/evaluator/evaluator.ts L1-L153 Verbatim
Condition factories (min/max duration, advance booking, quantity range, attribute, weekend, status, customer-limit) upstream src/rules/conditions/conditions.ts L1-L103 Verbatim
Pluggable availability strategy registry with three built-ins upstream src/availability/resolver/resolver.ts + src/availability/strategies/strategies.ts Verbatim, typed errors
Six domain presets returning fully populated BookingSystemConfig upstream src/domains/shared.ts + src/domains/{seats,accommodation,appointments,resources,activities,transport}/*.ts Verbatim — defaults preserved
Zod validation schemas (bookable, booking, booking request, hold request, time window, config) upstream src/schemas/booking.schema.ts + src/schemas/config.schema.ts Verbatim
Utility helpers (parseDurationMs, clampNumber, deepFreeze, generateId, isNonEmptyString) upstream src/utils/utils.ts L1-L79 Verbatim
Default constants (15 entries) upstream src/constants/index.ts L1-L21 Verbatim
Typed error hierarchy BookingError extending SuaveplanError with stable BOOKING.* codes enterprise/booking.md L43, L61 (cross-cutting "structured errors") Migration upgrade: replaces every throw new Error(...)
Subpath exports per public module (29 subpaths) enterprise/booking.md L37 (tree-shakeable) Expanded from upstream's 28

Requirements with no source line

Requirement Justification
100% test coverage (istanbul, all four axes) Genesis-wide policy (CLAUDE.md §7) — upstream was at ~99%
Sibling .md per implementation file (≥200 words, basic + advanced examples) Genesis-wide policy (CLAUDE.md §4) — upstream had partial docs
Test files import runner primitives from @suaveplan/testing/runner Genesis-wide policy (AGENTS.md §7) — upstream imported vitest directly
Explicit named re-exports in every feature-module barrel (no export *) Genesis-wide policy (AGENTS.md §6a)
Package version starts at 0.1.0 Genesis-wide policy (CLAUDE.md "Editing") — upstream was 1.0.6
In-package Hooks<TMap> replaces external @suaveplan/events/hooks import Dependency not migrated on genesis (§0b) — see Out of Scope for the migration path

Parity Checklist

Capability Upstream Migrated Notes
BookingEngine full surface (configure, registerBookable, getBookable, listBookables, checkAvailability, getAvailableSlots, createHold, getHold, confirmBooking, cancelBooking, modifyBooking, calculatePrice, joinWaitlist, on, off, getBooking, bookingHooks) ✅ ✅ Verbatim
StateMachine full surface ✅ ✅ Verbatim
EventBus full surface (15 event types) ✅ ✅ Verbatim
Temporal: windows / recurrence / slots (16 functions total) ✅ ✅ Verbatim
Conflict: 4 built-in detectors + pluggable registry ✅ ⬆️ Enhanced Unknown conflict detection mode raises BookingError({ code: BOOKING.UNKNOWN_CONFLICT_MODE })
Pricing: calculator + seasonal + dynamic (15 functions total) ✅ ✅ Verbatim
Rules: evaluator (11 operators) + 8 condition factories ✅ ✅ Verbatim
Availability: 3 built-in strategies + pluggable registry ✅ ⬆️ Enhanced Unknown availability strategy raises BookingError({ code: BOOKING.UNKNOWN_AVAILABILITY_STRATEGY })
Domain presets (seats / accommodation / appointments / resources / activities / transport + base) ✅ ✅ Verbatim defaults
Zod schemas (bookable / booking / booking-request / hold-request / time-window / config) ✅ ✅ Verbatim
Utils (parseDurationMs, clampNumber, deepFreeze, generateId, isNonEmptyString) ✅ ✅ Verbatim
Default constants (15 entries) ✅ ✅ Verbatim
All public types (~50 type exports) ✅ ✅ Verbatim — every name preserved
@suaveplan/events/hooks dependency ✅ ⬆️ Replaced In-package Hooks<TMap> ships under src/hooks/; same public surface
Plain throw new Error(...) from boundaries ✅ ⬆️ Replaced All raised errors extend SuaveplanError with stable BOOKING.* codes
Subpath exports per public module ✅ ⬆️ Enhanced 29 subpaths (up from upstream 28; adds ./hooks/hooks)
Property-based tests (fast-check) 🚫 upstream (audit asks) 🚫 deferred Deterministic suite hits 100% first; fast-check added in follow-up
OTel observability surface 🚫 upstream (audit asks) 🚫 deferred Requires MeterLike/LoggerLike DI design; follow-up change
AbortSignal propagation 🚫 upstream (audit asks) 🚫 deferred Sync surface; cancellation belongs to async-rewrite follow-up
Persistence-tier integration 🚫 upstream (audit asks) 🚫 deferred Belongs to @suaveplan/booking-persistence
Feature flags 🚫 upstream (audit asks) 🚫 deferred No risky new behaviours introduced by this migration
i18n message keys 🚫 upstream (audit asks) 🚫 deferred Wired when the booking i18n catalog lands
@suaveplan/schema-registry integration 🚫 upstream (audit asks) 🚫 deferred No network-tier consumer yet
Benchmarks (mitata) 🚫 upstream (audit asks) 🚫 deferred Coverage gate is primary criterion; benches land alongside observability
BookingError discriminants (bookingId/holdId/mode/strategy) 🚫 upstream ⬆️ Added Genesis convention for typed errors

Reference docs consulted

  • ✅ ../suaveplan/packages/security/booking/src/ — all 51 implementation files read
  • ✅ ../suaveplan/packages/security/booking/README.md — public API documentation
  • ✅ ../suaveplan/packages/security/booking/package.json — dep graph + subpath exports (28 entries)
  • ✅ ../suaveplan/enterprise/booking.md — enterprise audit, in full
  • ✅ ../suaveplan/packages/universal/events/src/hooks/ — upstream Hooks impl + types (re-implemented in-package)
  • 🚫 ../suaveplan/openspec/specs/booking/spec.md — does not exist (upstream has no formal spec; parity contract is the source itself)
  • 🚫 ../suaveplan/openspec/changes/*/proposal.md touching booking — none found beyond enterprise-booking

Verification approach

Every requirement in the spec delta carries a **Verified by:** line naming the test files that pin its scenarios. The test layout mirrors the source layout (one <feature>.test.ts per <feature>.ts):

  • src/error-codes/error-codes.test.ts — every BOOKING.* code reachable from a real call site
  • src/hooks/hooks.test.ts — Hooks<TMap> register/unhook/apply/applyAsync/applyWithResult/abort/once/priority/hookCount/hasHook
  • src/temporal/windows/windows.test.ts, src/temporal/recurrence/recurrence.test.ts, src/temporal/slots/slots.test.ts — temporal operations
  • src/conflict/detector/detector.test.ts, src/conflict/exact-match/exact-match.test.ts, src/conflict/range-overlap/range-overlap.test.ts, src/conflict/slot-capacity/slot-capacity.test.ts, src/conflict/provider-overlap/provider-overlap.test.ts
  • src/pricing/calculator/calculator.test.ts, src/pricing/seasonal/seasonal.test.ts, src/pricing/dynamic/dynamic.test.ts
  • src/rules/evaluator/evaluator.test.ts, src/rules/conditions/conditions.test.ts
  • src/availability/resolver/resolver.test.ts, src/availability/strategies/strategies.test.ts
  • src/domains/shared.test.ts, src/domains/{seats,accommodation,appointments,resources,activities,transport}/<domain>.test.ts
  • src/schemas/booking.schema.test.ts, src/schemas/config.schema.test.ts
  • src/core/engine/engine.test.ts, src/core/state-machine/state-machine.test.ts, src/core/event-bus/event-bus.test.ts
  • src/utils/utils.test.ts

Impact

Affected dependents

Run bunx turbo ls --filter=...@suaveplan/booking to enumerate.

  • No dependents — this is a new universal-tier leaf package being added to genesis. Every potential downstream (@suaveplan/r3f-booking, the api-plugin-bank/api-plugin-banking plugins) is still in the Pending list and will adopt this package when migrated.

Migration impact

None — additive only. No existing genesis package imports @suaveplan/booking.