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

153 lines
21 KiB
Markdown

# 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`.