/** * Live package/cruise offer routes — owned by `@voyant-travel/catalog`. * * agent-quality: file-size exception -- Live offer routes keep search, detail, * and pricing adapter normalization together until the route is split by * provider surface. * * POST /v1/admin/catalog/package-offers * POST /v1/admin/catalog/package-detail * POST /v1/admin/catalog/package-search * POST /v1/admin/catalog/departure-airports * POST /v1/admin/catalog/cruise-price * POST /v1/admin/catalog/cruise-sailing-pricing * * Sourced packages (TUI) are synced into the catalog as a priced *summary* * (the cards). The actual bookable units — departure dates, room/board, flights * and per-departure prices — are **live**: they come from Voyant Connect's * `packages/search`, not from static content. The connect-sdk doesn't wrap that * endpoint yet, so we call it through the client transport. * * Given a catalog product id, we resolve its connection + upstream * accommodation ref from `catalog_sourced_entries`, then fan the search out and * return the offers mapped to a lean, render-ready shape. The call is live, so * it can 5xx (TUI staging) — we retry once and fail soft with an empty list. * * Deployment-specific access (Voyant Connect client construction, the Typesense * hero-field lookup, and destination-name resolution) is INJECTED via options * so this package never statically imports connect-sdk / typesense / geo. The * handlers only call the structural surface the deployment hands them. */ import { OpenAPIHono } from "@hono/zod-openapi"; import type { AnyDrizzleDb } from "@voyant-travel/db"; import type { ApiExtension } from "@voyant-travel/hono/module"; import type { Context } from "hono"; /** * The structural subset of the Voyant Connect client the offer handlers call. * The deployment builds the real client (from its env) and hands back this * shape; the package never imports `@voyant-travel/connect-sdk`. */ export interface CatalogOffersConnectClient { transport: { request(path: string, init: { method: string; body?: unknown; unwrapData?: boolean; }): Promise; }; accommodations: { getOnConnection(connectionId: string, externalId: string, options?: { locale?: string; }): Promise; }; cruises: { getOnConnection(connectionId: string, externalId: string): Promise; listSailingPricing(connectionId: string, sailingRef: string): Promise; }; } /** Search-index fields used to enrich offer cards. */ export interface CatalogOffersIndexFields { name?: string; thumbnailUrl?: string; stars?: string | number; destinations?: string[]; countryCodes?: string[]; } /** A resolved departure airport, code + friendly label. */ export interface CatalogOffersAirportLabel { code: string; label: string; } /** A destination filter for the live search / airport probe. */ export interface CatalogOffersSearchDestination { countryCode?: string; region?: string; city?: string; destinationCodes?: string[]; } /** * Deployment-supplied options for the catalog offer route module. Structural * only — the three injected functions encapsulate every connect-sdk / typesense * / geo access so the package stays free of those static imports. */ export interface CatalogOffersRouteModuleOptions { /** * Build the Voyant Connect client for this request, or return `null` when * Connect isn't configured (missing api key / operator id). When `null`, the * handlers fall back to the "connect_not_configured" empty-list responses. */ resolveConnectClient(c: Context): CatalogOffersConnectClient | null; /** * Resolve product ids → their indexed hero fields (Typesense). Best-effort; * the deployment owns the typesense call and returns an empty map on failure. */ fetchIndexFields(c: Context, productIds: string[]): Promise>; /** * Resolve a destination → its dynamic (live-composable) catalog hotel ids * from the deployment's search index, capped to `limit`. Empty array when the * index isn't configured. */ resolveDynamicHotelIds(c: Context, destination: CatalogOffersSearchDestination, limit: number): Promise; /** * Resolve departure airport codes (OTP, IAS…) to "City (CODE)" labels. Must * never throw — falls back to the bare code on any error. */ resolveAirportLabels(c: Context, codes: string[]): Promise; } /** * Deployment `Variables` the offer handlers read off the request context — the * parent app's middleware chain resolves `db`. Permissive: the handlers cast to * `PostgresJsDatabase` for the sourced-entry lookups. */ type Env = { Variables: { db?: AnyDrizzleDb; }; }; /** * The catalog admin offer routes (relative paths; mount at * `/v1/admin/catalog`). All connect/typesense/geo access is injected via * `options`. 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 createCatalogOffersAdminRoutes(options: CatalogOffersRouteModuleOptions): OpenAPIHono; /** Package-owned descriptor for deployments that inject catalog offer providers. */ export declare function createCatalogOffersApiExtension(options: CatalogOffersRouteModuleOptions): ApiExtension; export {};