Initial Commit
This commit is contained in:
109
openspec/archive/2026-05-17-add-booking/design.md
Normal file
109
openspec/archive/2026-05-17-add-booking/design.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Design — @suaveplan/booking
|
||||
|
||||
## Architecture overview
|
||||
|
||||
The booking engine is a pipeline composed of six pluggable stages, each owned by an isolated feature module:
|
||||
|
||||
```text
|
||||
Availability → Conflict detection → Pricing → Rules → State machine → Events
|
||||
```
|
||||
|
||||
Every stage is registry-driven (`registerDetector`, `registerAvailabilityStrategy`) so callers can extend behaviour without forking. The pipeline orchestrator is `BookingEngine`, which holds in-memory `Map<id, Bookable | Booking | BookingHold>` registries plus an in-package `Hooks<BookingHookMap>` instance and an `EventBus`.
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
src/
|
||||
index.ts — explicit named root barrel
|
||||
error-codes/
|
||||
error-codes.ts | error-codes.test.ts | error-codes.md | index.ts
|
||||
hooks/
|
||||
hooks.ts | hooks.test.ts | hooks.md | hooks.types.ts | index.ts
|
||||
utils/
|
||||
utils.ts | utils.test.ts | utils.md | index.ts
|
||||
constants/
|
||||
constants.ts | constants.md | index.ts
|
||||
types/
|
||||
bookable.types.ts (+ .md)
|
||||
booking.types.ts (+ .md)
|
||||
booking-hooks.ts (+ .md)
|
||||
capacity.types.ts (+ .md)
|
||||
config.types.ts (+ .md)
|
||||
events.types.ts (+ .md)
|
||||
layout.types.ts (+ .md)
|
||||
pricing.types.ts (+ .md)
|
||||
rules.types.ts (+ .md)
|
||||
temporal.types.ts (+ .md)
|
||||
temporal/
|
||||
windows/(windows.ts + test + md + index)
|
||||
recurrence/(…)
|
||||
slots/(…)
|
||||
conflict/
|
||||
detector/(detector.ts + .types.ts + test + md + index)
|
||||
exact-match/(…) | range-overlap/(…) | slot-capacity/(…) | provider-overlap/(…)
|
||||
pricing/
|
||||
calculator/(calculator.ts + .types.ts + test + md + index)
|
||||
seasonal/(…) | dynamic/(…)
|
||||
rules/
|
||||
evaluator/(evaluator.ts + .types.ts + test + md + index)
|
||||
conditions/(…)
|
||||
availability/
|
||||
resolver/(resolver.ts + .types.ts + test + md + index)
|
||||
strategies/(strategies.ts + .types.ts + test + md + index)
|
||||
domains/
|
||||
shared.ts | shared.test.ts | shared.md | index.ts
|
||||
seats/(seats.ts + .types.ts + test + md + index)
|
||||
accommodation/(…) | appointments/(…) | resources/(…)
|
||||
activities/(…) | transport/(…)
|
||||
schemas/
|
||||
booking.schema.ts | booking.schema.test.ts | booking.schema.md | index.ts
|
||||
config.schema.ts | config.schema.test.ts | config.schema.md
|
||||
core/
|
||||
engine/(engine.ts + .types.ts + test + md + index)
|
||||
state-machine/(state-machine.ts + test + md + index)
|
||||
event-bus/(event-bus.ts + test + md + index)
|
||||
conformance.spec-conformance.test.ts — barrel + subpath conformance
|
||||
tests/
|
||||
setup/polyfills.ts
|
||||
```
|
||||
|
||||
## Key decisions
|
||||
|
||||
### 1. In-package `Hooks<TMap>` (vs. depending on `@suaveplan/events/hooks`)
|
||||
|
||||
Genesis `@suaveplan/events` does not yet expose `/hooks` or `/hooks.types`. Rather than block this migration on a separate `events-hooks` change, the upstream Hooks impl is re-implemented in-package under `src/hooks/` with the same public surface. The `BookingHookMap` is defined locally so `Hooks<BookingHookMap>` works without external types. When events ships its hooks subpath, the in-package impl can be removed in a follow-up; the contract preserved here matches the upstream surface exactly so consumers do not need to change.
|
||||
|
||||
### 2. Typed errors over plain `throw new Error`
|
||||
|
||||
Every boundary that previously raised a plain `Error` now raises a `BookingError` extending `SuaveplanError<BookingErrorCode>`. Codes are stable namespaced strings (`"BOOKING.ENGINE_NOT_CONFIGURED"`). Optional discriminants — `bookingId`, `holdId`, `mode`, `strategy` — are attached without polluting the base `context` map; consumers can read them on the typed instance after a narrow.
|
||||
|
||||
The `Hooks` sync-path `console.warn` (upstream behaviour when a handler returns a Promise) is upgraded to a `BookingError({ code: BOOKING.HOOK_RETURNED_PROMISE_IN_SYNC })`. Universal-tier libraries should not log to stdout; the typed error lets callers decide how to handle the bug.
|
||||
|
||||
### 3. Module-level mutable registries preserved
|
||||
|
||||
Upstream uses module-level `Map<>` registries for conflict detectors and availability strategies. The migration preserves this for parity (downstream callers depend on the global mutation). A future change can pivot to scoped registries; that is an out-of-scope behaviour change.
|
||||
|
||||
### 4. Determinism is upstream's responsibility
|
||||
|
||||
Upstream uses `Date.now()` and `Math.random()` inside `generateId` for in-memory id generation. The migration preserves both. Tests assert structural properties (prefix, uniqueness across N calls) rather than exact strings; a future change can thread an injectable `IdGenerator` from `@suaveplan/types` once consumers need deterministic ids.
|
||||
|
||||
### 5. Subpath exports
|
||||
|
||||
Each feature module gets a subpath in `package.json` `exports` so consumers like `@suaveplan/r3f-booking` can import only `@suaveplan/booking/temporal/windows/windows` and skip the engine + schemas + rule evaluator. Tree-shaking is verified at build time (preserveModules + sideEffects:false).
|
||||
|
||||
## Public surface inventory
|
||||
|
||||
- Engine + types (`createBookingEngine`, `BookingEngine`, `BookingEngineInterface`, `AvailabilityQuery`, `AvailabilityResult`, `ConflictResult`, `PriceCalculationRequest`)
|
||||
- State machine (`StateMachine`, `createStateMachine`, `TransitionRequest`, `TransitionResult`)
|
||||
- Event bus (`EventBus`, `createEventBus`)
|
||||
- Hooks (`Hooks`, `HookMap`, `HookContext`, `HookHandler`, `HookHandlerOptions`, `HookResult`, `RegisteredHook`, `Hookable`)
|
||||
- Temporal (windows: 9 fns; recurrence: 3 fns; slots: 4 fns)
|
||||
- Conflict (detector: 4 fns + types; 4 built-ins)
|
||||
- Pricing (calculator: 5 fns + types; seasonal: 4 fns; dynamic: 4 fns + types)
|
||||
- Rules (evaluator: 2 fns; conditions: 8 factories)
|
||||
- Availability (resolver: 4 fns + types; strategies: 3 built-ins + `SlotAvailability` type)
|
||||
- Domains (base + 6 presets + 6 meta types)
|
||||
- Schemas (6 entity + 1 config + 5 validate/safe-validate helpers)
|
||||
- Errors (`BOOKING_CODES`, `BookingError`, `BookingErrorCode`, `BookingErrorOptions`)
|
||||
- Utils (5 helpers) + Constants (15 entries)
|
||||
- All upstream type names preserved verbatim (50+)
|
||||
Reference in New Issue
Block a user