/** * engine-booking — composed **by call**, not by event. * * The in-scope functions are the surface: `createResource`, `holdReservation`, * `confirmReservation`, `joinReservation`, `openReservation`, `leaveReservation`, * `cancelReservation`, `moveReservation`, `startReservation`, `completeReservation`, * `markNoShow`, `expireReservation` and the reads beside them. A vertical imports * those into its own operations and runs them inside its own transaction; the * registered operations below are the engine's default HTTP-reachable bindings, * not a second way in. * * The consequence is stated at length in `lifecycle.ts`: an engine composed by call * takes its invariant from the callee, so `booking/join` is not declared as an edge * to `confirmed` even though a join that fills the last place calls * `confirmReservation` — the move is that function's, and it goes through the same * check on the way. * * There are no consumers here. Nothing composes this engine by emitting at it. */ import { z } from 'zod'; import { type ListPage, type Page } from '@substrat-run/contracts'; /** * The conflict reasons this engine raises — its own vocabulary, narrowing the platform's * `conflict` code (#113). Exported so a vertical can branch on WHY a refusal happened * without importing this engine's types or matching on its prose; `as const` so a typo * is a compile error here rather than a slug nobody ever matches. * * Additive only, like every other engine surface: new reasons may appear, existing ones * do not change spelling. */ export declare const BOOKING_CONFLICT_REASONS: readonly ['already_joined', 'already_left', 'capacity_below_joined', 'hold_expired', "invalid_transition", 'not_yet_expired', 'reservation_full', 'resource_inactive']; export type BookingConflictReason = (typeof BOOKING_CONFLICT_REASONS)[number]; export { atInstant, availabilityInput, cancelReservationInput, createResourceInput, freeInterval, holdReservationCall, holdReservationInput, instantIn, joinReservationCall, joinReservationInput, leaveReservationInput, listReservationsInput, listResourcesInput, moveReservationCall, moveReservationInput, openReservationInput, participant, reservation, reservationAtInput, reservationIdIn, reservationState, resource, setResourceActiveInput, toInstant, type CreateResourceInput, type FreeInterval, type HoldReservationInput, type JoinReservationInput, type MoveReservationInput, type Participant, type Reservation, type ReservationState, type Resource, type SetResourceActiveInput, } from './schemas.js'; export { bookingOperations, BOOKING_PERMISSIONS } from './operations.js'; export { bookingLifecycles } from './lifecycle.js'; /** * The event contract (#696) — what a VERTICAL imports so that consuming this * engine by event is checked rather than guessed. `events.ts` says what these * are and what they are not: types only, vertical-facing only, and why there is * no completion group here. */ export { type BookingEvents, type BookingEventType, type BookingResourceRef, type BookingSlot, type BookingResourceCreatedPayload, type BookingHeldPayload, type BookingConfirmedPayload, type BookingExpiredPayload, type BookingParticipantJoinedPayload, type BookingOpenedPayload, type BookingParticipantLeftPayload, type BookingCancelledPayload, type BookingMovedPayload, type BookingStartedPayload, type BookingCompletedPayload, type BookingNoShowPayload, } from './events.js'; import { type CreateResourceInput, type FreeInterval, type HoldReservationInput, type JoinReservationInput, type MoveReservationInput, type Participant, type Reservation, type ReservationState, type Resource } from './schemas.js'; export { bookingEntities, reservationRow, resourceRow } from './entities.js'; import { type ModuleRegistration, type OperationContext, type PageParams } from '@substrat-run/kernel'; export declare const PERM: { create: string & z.$brand<"PermissionKey">; read: string & z.$brand<"PermissionKey">; hold: string & z.$brand<"PermissionKey">; confirm: string & z.$brand<"PermissionKey">; cancel: string & z.$brand<"PermissionKey">; move: string & z.$brand<"PermissionKey">; complete: string & z.$brand<"PermissionKey">; manageResources: string & z.$brand<"PermissionKey">; }; export declare const bookingManifest: { id: string & z.$brand<"ModuleId">; version: string; kernelContract: string; permissions: { key: string & z.$brand<"PermissionKey">; description: string; }[]; events: { emits: { type: string; schemaVersion: number; }[]; consumes: { type: string; schemaVersion: number; }[]; }; migrations: { journalDir: string; compatibleFrom: string; }; attachmentTargets: { entityType: string; readPermission: string & z.$brand<"PermissionKey">; writePermission?: (string & z.$brand<"PermissionKey">) | undefined; }[]; liveTargets?: { entityType: string; readPermission: string & z.$brand<"PermissionKey">; }[] | undefined; entityRelations?: { entityType: string; parentType: string; }[] | undefined; guards?: { before: string; predicate: string; config: Record; }[] | undefined; schedules?: { operation: string; cadence: { everyMinutes: number; }; input?: Record | undefined; permissions: (string & z.$brand<"PermissionKey">)[]; }[] | undefined; freshness?: { eventType: string; within: { hours: number; }; }[] | undefined; withdraws?: string[] | undefined; entitlementKey: string; envSpec?: { key: string; label?: string | undefined; description: string; placeholder?: string | undefined; required: boolean; secret: boolean; default?: string | undefined; group?: string | undefined; }[] | undefined; ownerGrants?: (string & z.$brand<"PermissionKey">)[] | undefined; entitlements?: string[] | undefined; provides?: string[] | undefined; requires?: string[] | undefined; api?: string | undefined; searchables?: { entityType: string; fields: string[]; table?: string | undefined; idColumn?: string | undefined; tokenizer?: "prefix" | "substring" | undefined; }[] | undefined; lists?: { entityType: string; sortable: string[]; filterable?: string[] | undefined; table?: string | undefined; idColumn?: string | undefined; }[] | undefined; ui?: { routes?: { path: string; screen: string; permission: string & z.$brand<"PermissionKey">; }[] | undefined; nav?: { label: string; icon?: string | undefined; to: string; permission: string & z.$brand<"PermissionKey">; }[] | undefined; entityViews?: { entityType: string; view: string; }[] | undefined; widgets?: { slot: string; component: string; permission: string & z.$brand<"PermissionKey">; }[] | undefined; settingsPanels?: { label: string; component: string; permission: string & z.$brand<"PermissionKey">; }[] | undefined; } | undefined; }; export declare const bookingMigrations: { version: string; sql: string; }[]; /** The typed rejection a vertical surfaces as "that slot was just taken". */ export declare class SlotUnavailable extends Error { readonly resourceId: string; readonly startsAt: string; readonly endsAt: string; readonly code = "SLOT_UNAVAILABLE"; constructor(resourceId: string, startsAt: string, endsAt: string); } /** The one definition of "a hold past its deadline is expired". */ export declare function effectiveStateOf(state: ReservationState, expiresAt: string | null, now: string): ReservationState; export declare function createResource(ctx: OperationContext, rawInput: CreateResourceInput): Resource; export declare function setResourceActive(ctx: OperationContext, input: { resourceId: string; active: boolean; }): Resource; /** * The same resources, as a PAGE — what `booking/list-resources` answers (#811). * * Kernel-composed: the `WHERE`, the `ORDER BY`, the keyset tie-break, the * `LIMIT` and the indexes behind them are all composed from this operation's * declared `paged.over` vocabulary. What stays here is the projection, which is * why `mapPage` exists — it re-shapes the entries and leaves the walk alone. * * `listResources` below is NOT replaced by it. That one is an in-scope fold a * vertical calls inside its own transaction, where the bound is the vertical's * (a club has eight courts, not eight thousand). The unbounded read #811 was * filed against is the invocable ENDPOINT, and that is this one. */ export declare function listResourcesPage(ctx: OperationContext, page: PageParams): Page; export declare function listResources(ctx: OperationContext, kind?: string): Resource[]; /** * Place a tentative hold. Throws {@link SlotUnavailable} if the interval would * overallocate the resource. * * A hold is never permanent — `expiresAt` is mandatory. The same mechanism serves * a payment hold and an open match awaiting players (`fillTarget`). */ export declare function holdReservation(ctx: OperationContext, rawInput: HoldReservationInput): Reservation; /** * held → confirmed. Re-runs the allocation check excluding this reservation, * because the hold may have expired and the slot been taken in the meantime. */ export declare function confirmReservation(ctx: OperationContext, input: { reservationId: string; now?: string; }): Reservation; /** * Expire a hold whose deadline has passed. Idempotent-ish: only `held` rows move. * Because expiry is lazy, calling this is optional for correctness — it exists so * a vertical can surface the transition (and its event) to a UI. */ export declare function expireReservation(ctx: OperationContext, input: { reservationId: string; now?: string; }): Reservation; /** * Add a participant. When the reservation is `held` and reaching `fillTarget`, * this auto-confirms — the open-match mechanic, built out of the payment hold. */ export declare function joinReservation(ctx: OperationContext, rawInput: JoinReservationInput): { participant: Participant; reservation: Reservation; }; /** Soft-leave: the row is never deleted, so the record of who was in stays intact. */ /** * Open an existing reservation to others, or change how many places are on offer. * * `fillTarget` is engine state — it drives the auto-confirm in `joinReservation` * — so a booking cannot be opened up by a vertical keeping its own counter * beside it and hoping the two agree. Additive: reservations made without a * target are unaffected, and a target below the people already on it is refused * rather than silently stranding someone. * * Passing `null` closes it again — a private booking with no places on offer. */ export declare function openReservation(ctx: OperationContext, input: { reservationId: string; fillTarget: number | null; now?: string; }): Reservation; export declare function leaveReservation(ctx: OperationContext, input: { reservationId: string; participantId: string; now?: string; }): Reservation; export declare function cancelReservation(ctx: OperationContext, input: { reservationId: string; reason?: string; now?: string; }): Reservation; /** * Reschedule to another slot and/or resource, keeping the reservation's identity * and its participants. * * Deliberately **not** a general `updateReservation`. Engines model named * transitions rather than field patches (cf. `engine-workorder`), participants are * an append-only log with per-subject events rather than a patchable field (D-C), * and `booking.moved` carrying from/to is worth far more to a consumer than a * generic diff — event payloads freeze once shipped. * * This is not cancel-then-rebook: that would lose the identity, the roster, and * any payment already attached. */ export declare function moveReservation(ctx: OperationContext, rawInput: MoveReservationInput): Reservation; export declare function startReservation(ctx: OperationContext, input: { reservationId: string; now?: string; }): Reservation; /** * The terminal success transition. The payload is deliberately **fat** — resource, * interval and the full participant list — so an invoicing consumer can raise split * charges and an out-of-kernel consumer can build cross-club history, neither * needing a cross-module read. */ export declare function completeReservation(ctx: OperationContext, input: { reservationId: string; now?: string; }): Reservation; export declare function markNoShow(ctx: OperationContext, input: { reservationId: string; now?: string; }): Reservation; export declare function getReservation(ctx: OperationContext, reservationId: string, now?: string): { reservation: Reservation; participants: Participant[]; }; /** * Reservations overlapping a window, as a PAGE — what `booking/list` answers. * * Handler-composed rather than kernel-composed, and the cursor is `id`. The * window is an OVERLAP test (`starts_at < to AND ends_at > from`), which the * kernel's equality-only filter vocabulary cannot express — deliberately, since * a range vocabulary is where a filter becomes a query language. So this read * owns its own `WHERE`. * * The cursor has to be UNIQUE. This list shipped `ORDER BY starts_at, id`, and a * keyset cursor on `starts_at` skips and repeats rows wherever two reservations * share a start — which on a court schedule is every hour. Ids are ULIDs, so * `id` is unique and still roughly chronological by creation; a caller rendering * a calendar sorts the page it got by `startsAt` itself. */ export declare function listReservationsPage(ctx: OperationContext, input: { resourceId?: string; from?: string; to?: string; now?: string; } & ListPage): Page; export declare function listReservations(ctx: OperationContext, input: { resourceId?: string; from?: string; to?: string; now?: string; }): Reservation[]; /** * Free capacity over `[from, to)`, as merged intervals. * * Returns raw gaps between reservations — it knows nothing of opening hours and * will happily report 03:00 as free. Intersecting with the venue's bookable * window is the **vertical's** job (docs/engines/booking.md §4.1). * * Implemented as a sweep over interval boundaries rather than a simple gap walk, * because capacity may exceed 1 (fungible pools), where "free" is a number and not * a boolean. */ /** * The same free intervals, as a PAGE — what `booking/availability` answers. * * A computed fold rather than a table walk, so the whole fold runs and the page * is taken off the end of it. That is not the waste it looks like: the segments * are derived by merging every live reservation in the window, so there is no * partial computation to push into SQL. * * The segments are DISJOINT and returned in order, so `startsAt` is unique among * them — which is what makes it a sound cursor here where it would not be over * reservation rows. */ export declare function availabilityPage(ctx: OperationContext, input: { resourceId: string; from: string; to: string; now?: string; } & ListPage): Page; export declare function availability(ctx: OperationContext, input: { resourceId: string; from: string; to: string; now?: string; }): FreeInterval[]; export declare const bookingModule: ModuleRegistration; //# sourceMappingURL=index.d.ts.map