/** * `quoteEntity` — the first step in the booking engine lifecycle. * * Asks the registered adapter "is this row still bookable, and at what * price right now?", persists the answer in `catalog_quotes` with an * expiry, and returns a stable `quoteId` the subsequent `bookEntity` * call can validate against. * * Quotes are short-lived (default TTL: 10 minutes) and not de-duped. * Re-quoting the same row produces a new quote row so the audit trail * shows every quote attempt. */ import type { AnyDrizzleDb } from "@voyantjs/db"; import type { SourceAdapterContext } from "../adapter/contract.js"; import type { PricingBasis } from "../snapshot/schema.js"; import type { BookingDraftShape } from "./draft-shape.js"; import type { OwnedBookingHandlerRegistry } from "./owned-handler.js"; import type { PromotionEvaluationInput, PromotionEvaluationOutput } from "./promotions-contract.js"; import type { SourceAdapterRegistry } from "./registry.js"; /** Default time-to-live for a quote. */ export declare const DEFAULT_QUOTE_TTL_MS: number; export interface QuoteScope { locale: string; audience: string; market: string; currency?: string; } export interface QuoteEntityRequest { /** The catalog row to quote. */ entityModule: string; entityId: string; /** Source pointer, read from the row's provenance. */ sourceKind: string; sourceProvider?: string; sourceConnectionId?: string; sourceRef?: string; /** Variant scope for the quote. */ scope: QuoteScope; /** Vertical-specific parameters echoed to the adapter (date range, pax, etc.). */ parameters?: Record; /** Override the TTL (rare — defaults to `DEFAULT_QUOTE_TTL_MS`). */ ttlMs?: number; /** Adapter context (connection_id, credentials, correlation_id). */ adapterContext: SourceAdapterContext; } export interface QuoteEntityResult { quoteId: string; quotedAt: Date; expiresAt: Date; available: boolean; invalidReason?: string; pricing?: PricingBasis; upstreamPayload?: Record; /** * The journey wizard descriptor — populated when * `deps.contentEnricher` is wired and the entity is sourced. Tells * the wizard which steps + sub-steps to render. Per * `docs/architecture/booking-journey-architecture.md` §3, this is * returned alongside the quote so the journey can render the * correct shape without a follow-up call. * * Undefined when no enricher is wired (today's behavior — the * journey hardcodes a minimal shape until templates wire content). */ shape?: BookingDraftShape; } /** * Input the content enricher receives — quote + scope + parameters. * The enricher reads cached content for the entity and projects a * `BookingDraftShape` that drives the wizard. Verticals compose their * `build*DraftShape` builders into one enricher routed by * `entity_module`. */ export interface QuoteContentEnrichmentInput { db: AnyDrizzleDb; entityModule: string; entityId: string; sourceKind: string; sourceConnectionId?: string; sourceRef?: string; scope: QuoteScope; parameters?: Record; adapterContext: SourceAdapterContext; } /** * Hook called by `quoteEntity` after the live-resolve step succeeds. * Receives entity identity + scope; returns a `BookingDraftShape` (or * null when content is unavailable / the entity is owned and the * enricher chooses not to surface a shape). * * Templates compose this from per-vertical content services, e.g.: * * const enricher: QuoteContentEnricher = async (input) => { * const content = await readContentByModule(input) * return content ? buildDraftShape(input.entityModule, content, input.scope) : null * } */ export type QuoteContentEnricher = (input: QuoteContentEnrichmentInput) => Promise; export interface QuoteEntityDeps { registry: SourceAdapterRegistry; /** * Owned-arm dispatch — when set and the request's source kind is * `"owned"`, the engine routes to a handler keyed by * `entityModule` instead of the SourceAdapterRegistry. Per * booking-journey-architecture §6. * * Templates that ship owned products MUST wire this; templates * that only proxy sourced rows can leave it undefined and the * engine falls through to the legacy adapter path. */ ownedHandlers?: OwnedBookingHandlerRegistry; /** * Optional content-aware enricher. When wired, called after the * adapter's `liveResolve` step succeeds; the returned * `BookingDraftShape` is attached to the quote result so the * journey wizard can render the correct shape without a follow-up * call. * * When not wired (today's default), the quote response omits * `shape` and the journey falls back to its hardcoded minimal * descriptor. * * Errors from the enricher are caught and logged via * `onEnricherError` (defaults to silent) — they MUST NOT fail the * quote because the wizard can render the minimal shape on its * own. */ contentEnricher?: QuoteContentEnricher; /** Optional sink for enricher errors. */ onEnricherError?: (event: { entityModule: string; entityId: string; reason: string; }) => void; /** * Optional promotion-evaluator hook. When wired, called after the * adapter's `liveResolve` succeeds (only for `entity_module == * "products"` in v1). Discounts apply to `pricing.base_amount` * pre-tax; the operator template's `applyOperatorTaxToQuoteResult` * step downstream recomputes taxes against the new base. * * When the customer-supplied code fails validation, the engine * surfaces the result as a `code_*` `invalidReason` on the quote * (`code_not_found`, `code_expired`, `code_not_yet_valid`, * `code_not_applicable`). Auto offers do NOT apply when a bad code * is supplied — the quote is short-circuited to unavailable so the * customer gets clear feedback. * * Per `docs/architecture/promotions-architecture.md` §3.6 + §7.1. */ evaluatePromotions?: (input: PromotionEvaluationInput) => Promise; } /** * Quote the row. Calls `adapter.liveResolve` (sourced) or interprets * `available = true` from a stub (owned, when no adapter is registered * for the `"owned"` kind in this MVP cut). * * Throws `NoAdapterRegisteredError` if the registry has no entry for * `sourceKind`. Persists the quote either way — a `failed` lookup is * still recorded so subsequent diagnostics can see the attempt. */ export declare function quoteEntity(db: AnyDrizzleDb, deps: QuoteEntityDeps, request: QuoteEntityRequest): Promise; //# sourceMappingURL=quote.d.ts.map