/** * Catalog booking-engine route module — the full admin + public booking * surface, owned by `@voyant-travel/catalog`. * * A deployment composes this and supplies two structural options: * - `booking` — the `CatalogBookingRoutesOptions` (db / registries / * hold-ttl / promotions / tax hooks) it already builds today, and * - `resolveRegistry(c)` — pulls the process-local `SourceAdapterRegistry` * off the request context (cancel needs it to dispatch to adapters). * * The module mounts the shared lifecycle from `./routes.js` on **two** * surfaces and adds the admin-only order-management endpoints: * * POST /v1/{admin,public}/catalog/quote → quoteEntity * PUT /v1/{admin,public}/catalog/drafts/:id → upsert booking draft * GET /v1/{admin,public}/catalog/drafts/:id → read booking draft * DELETE /v1/{admin,public}/catalog/drafts/:id → delete booking draft * POST /v1/{admin,public}/catalog/holds/place → place hold * POST /v1/{admin,public}/catalog/holds/release → release hold * GET /v1/admin/catalog/orders → listOrders * GET /v1/admin/catalog/orders/:id → getOrderById * POST /v1/admin/catalog/orders/:id/cancel → cancelEntity * GET /v1/{admin,public}/catalog/slots → availability slots * GET /v1/admin/bookings/:id/catalog-snapshot → frozen catalog snapshot * * Auth posture comes from the deployment's `createApp` middleware chain — * `/v1/admin/...` requires staff, `/v1/public/...` accepts the configured * public actors. Per booking-journey-architecture §10 Phase B. * * The slots + catalog-snapshot handlers read across module boundaries * (`@voyant-travel/inventory` / `@voyant-travel/operations`), both of which * already depend on `@voyant-travel/catalog`. Statically importing them here * would create an import cycle, so the cross-package reads are supplied by the * deployment as INJECTED option functions — the package never imports those * modules, it only calls the readers the deployment hands it. */ import { OpenAPIHono } from "@hono/zod-openapi"; import type { AnyDrizzleDb } from "@voyant-travel/db"; import type { ApiModule } from "@voyant-travel/hono/module"; import type { Context, Hono } from "hono"; import type { SourceAdapterRegistry } from "./registry.js"; import { type CatalogBookingRoutesOptions } from "./routes.js"; import { type BookingSessionRoutesOptions } from "./sessions-routes.js"; /** * A single resolved departure/slot as projected by `getProductContent`. * Structural mirror of `@voyant-travel/inventory`'s `ProductDeparture` so the * package needn't import inventory — only the fields the slots handler maps. */ export interface CatalogResolvedDeparture { id: string; starts_at: string; ends_at?: string | null; status?: string | null; capacity?: number | null; remaining?: number | null; } /** * Structural result of the injected `getProductContent` reader. Mirrors * `@voyant-travel/inventory/service-content`'s `ResolvedProductContent`, but * narrowed to the only field the slots handler reads (`content.departures`). */ export interface CatalogResolvedProductContent { content: { departures?: ReadonlyArray; }; } /** Selected storefront/public scope for slot resolution. */ export interface CatalogAvailabilitySlotsScope { market?: string; locale?: string; currency?: string; } /** Locale/market scope passed to the injected `getProductContent` reader. */ export interface CatalogProductContentScope { preferredLocales: ReadonlyArray; audience: "staff" | "customer" | "partner" | "supplier"; market?: string; currency?: string; } /** Adapter/runtime context passed to the injected `getProductContent` reader. */ export interface CatalogProductContentReadContext { registry: SourceAdapterRegistry; forceFresh?: boolean; } /** * Owned-product summary returned by the injected `getOwnedProductById` reader. * Structural mirror of the inventory `productsService.getProductById` result, * narrowed to the two fields the snapshot fallback reads. */ export interface CatalogOwnedProductSummary { name: string | null; description: string | null; } /** * Deployment-supplied options for the catalog booking-engine route module. * Structural only — no deployment imports, no platform bindings. The three * cross-package readers (`getProductContent`, `listAvailabilitySlots`, * `getOwnedProductById`) are INJECTED so the package can host the slots + * snapshot handlers without statically importing `@voyant-travel/inventory` * or `@voyant-travel/operations` (both of which depend on catalog). */ export interface CatalogBookingRouteModuleOptions { /** * The booking-engine lifecycle options (db, source/owned registries, * hold-ttl, promotions, tax transforms). The deployment already builds * these for `createCatalogBookingRoutes`. */ booking: CatalogBookingRoutesOptions; bookingSessions?: Omit; /** * Resolve the process-local source-adapter registry for a request. Used by * the order-cancel handler to dispatch to the registered adapter. */ resolveRegistry(c: Context): SourceAdapterRegistry; /** * Read the resolved product content for a sourced product (slots path). * Modelled on `@voyant-travel/inventory/service-content`'s * `getProductContent`; structural so catalog doesn't import inventory. */ getProductContent(db: AnyDrizzleDb, productId: string, scope: CatalogProductContentScope, ctx: CatalogProductContentReadContext): Promise; /** * Read the owned `availability_slots` rows for a product (owned slots path). * The deployment owns the drizzle query against * `@voyant-travel/operations`; this returns the already-mapped rows. */ listAvailabilitySlots(db: AnyDrizzleDb, productId: string, todayIso: string, scope: CatalogAvailabilitySlotsScope): Promise; /** * Read an owned product by id for the snapshot fallback. Structural mirror * of inventory `productsService.getProductById`; returns `{ name, * description } | null`. */ getOwnedProductById(db: AnyDrizzleDb, productId: string): Promise; } /** * The slot row shape returned by both the sourced and owned slots paths. * Date-bearing fields accept `Date | string` because the owned path forwards * raw drizzle timestamp columns (serialized to ISO strings by `c.json`), * while the sourced path projects ISO strings directly. */ export interface SlotRow { id: string; dateLocal: string; startsAt: string | Date; endsAt: string | Date | null; timezone: string; status: string; unlimited: boolean; remainingPax: number | null; initialPax: number | null; nights: number | null; days: number | null; } /** * Deployment `Variables` the engine reads off the request context — resolved by * the parent app's middleware chain. Permissive: the resolvers own the lookup. */ type Env = { Variables: { db?: AnyDrizzleDb; userId?: string; }; }; /** * Admin-only order-management routes (relative paths; mount at * `/v1/admin/catalog`). Surfaces snapshot rows cross-vertically and routes * cancels back through the registered source adapter. Migrated to * `@hono/zod-openapi` for the admin OpenAPI backfill (voyant#2114) — the * handlers keep returning a plain `Response`, bridged to the inferred * typed-response union by `asRouteResponse`. */ export declare function createCatalogBookingOrdersRoutes(options: CatalogBookingRouteModuleOptions): OpenAPIHono; /** * Mount the full catalog booking-engine surface (both surfaces + admin * orders) onto an absolute-path Hono app. Mirrors the operator's previous * `mountCatalogBookingRoutes`, minus the cross-package snapshot/slots * handlers that have to stay in the deployment (cycle). */ /** * Structural mount target — just the `.route()`/`.get()` surface this function * uses. Decoupled from Hono's full generic signature so deployments can pass an * `OpenAPIHono` parent (whose default `Env` is not assignable to a bare `Hono`'s * via the chained-return `.fetch` variance) WITHOUT a cast — which is what makes * the mounted `.openapi()` sub-apps surface in the build-time OpenAPI spec * (voyant#2114 / voyant#2208). */ export interface CatalogBookingMountTarget { route(path: string, app: Hono): unknown; get(path: string, handler: (c: Context) => Response | Promise): unknown; /** * Register an `@hono/zod-openapi` route + handler. Accepts `any` so an * `OpenAPIHono` parent satisfies the target without a cast — this is what * surfaces the slots/catalog-snapshot legs in the build-time OpenAPI spec * (voyant#2114 / voyant#2208). */ openapi(route: any, handler: any): unknown; } export declare const catalogBookingRoutePaths: readonly ["/v1/admin/catalog/booking-sessions", "/v1/admin/catalog/booking-sessions/:sessionId", "/v1/admin/catalog/booking-sessions/:sessionId/quote", "/v1/admin/catalog/booking-sessions/:sessionId/hold", "/v1/admin/catalog/booking-sessions/:sessionId/abandon", "/v1/admin/catalog/booking-sessions/:sessionId/commit", "/v1/admin/catalog/quote", "/v1/admin/catalog/quotes/batch", "/v1/admin/catalog/drafts/:id", "/v1/admin/catalog/holds/place", "/v1/admin/catalog/holds/release", "/v1/admin/catalog/slots", "/v1/admin/catalog/orders", "/v1/admin/catalog/orders/:id", "/v1/admin/catalog/orders/:id/cancel", "/v1/admin/bookings/:id/catalog-snapshot", "/v1/public/catalog/quote", "/v1/public/catalog/booking-sessions", "/v1/public/catalog/booking-sessions/:sessionId", "/v1/public/catalog/booking-sessions/:sessionId/quote", "/v1/public/catalog/booking-sessions/:sessionId/hold", "/v1/public/catalog/booking-sessions/:sessionId/abandon", "/v1/public/catalog/booking-sessions/:sessionId/commit", "/v1/public/catalog/quotes/batch", "/v1/public/catalog/drafts/:id", "/v1/public/catalog/holds/place", "/v1/public/catalog/holds/release", "/v1/public/catalog/slots"]; export declare const catalogBookingTransactionalPaths: readonly ["/v1/admin/catalog/quote", "/v1/admin/catalog/quotes/batch", "/v1/admin/catalog/holds", "/v1/admin/catalog/orders", "/v1/admin/catalog/booking-sessions", "/v1/public/catalog/quote", "/v1/public/catalog/quotes/batch", "/v1/public/catalog/holds", "/v1/public/catalog/booking-sessions"]; export declare function mountCatalogBookingRoutes(hono: CatalogBookingMountTarget, options: CatalogBookingRouteModuleOptions): void; /** Package-owned descriptor for deployments that inject booking runtime dependencies. */ export declare function createCatalogBookingEngineApiModule(options: CatalogBookingRouteModuleOptions): ApiModule; export {};