npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@voyant-travel/catalog

v0.230.0

Published

Catalog plane foundation for Voyant. The shared cross-cutting infrastructure that Inventory, vertical modules, resale modules, and booking add-on surfaces adopt to participate in a normalized discovery / overlay / snapshot / search surface.

Readme

@voyant-travel/catalog

Catalog plane foundation for Voyant. The shared cross-cutting infrastructure that Inventory, vertical modules, resale modules, and booking add-on surfaces adopt to participate in a normalized discovery / overlay / snapshot / search surface.

This package owns the catalog plane foundation plus semantic search primitives: embedding providers, model compatibility helpers, hybrid/semantic search, and cross-audience federation. Agent runtimes wrap the catalog HTTP APIs directly; MCP packaging is application-owned.

See docs/architecture/catalog-architecture.md for the full design.

Install

pnpm add @voyant-travel/catalog

Install @voyant-travel/catalog-contracts instead when you only need the pure adapter payload types, adapter Zod schemas, field-policy contracts, provenance, drift payloads, or content locale/overlay helpers. Use this package when you also need Drizzle schema, Hono routes, booking-engine integration, search services, or catalog runtime services.

What's in the box

  • ./contractFieldPolicy type and the eleven governance enums. The load-bearing schema decision: every field on every Catalog Item projection is declared with a row in a per-vertical policy file.
  • ./provenanceProvenance shape (source_kind, source_ref, source_freshness) carried by every Catalog Item projection.
  • ./overlay/schema — drizzle table schema for editorial overrides keyed (entity_module, entity_id, field_path, locale, audience, market).
  • ./overlay/resolver — resolver-merge logic with full locale × audience × market fallback chain.
  • ./snapshot/schemabooking_catalog_snapshot table for immutable booking-time Catalog Item projection views.
  • ./indexer/contract — compatibility re-export of the engine-agnostic contracts now owned by @voyant-travel/catalog-contracts/indexer/contract.
  • ./indexer/provider — the catalog.indexer runtime port used by deployment composition.
  • ./indexer/postgres — native Postgres IndexerAdapter, the first-party managed-cloud default. It keeps a rebuildable catalog projection in the deployment database and uses the deployment-owned resident pool. Set the recorded POSTGRES_SEARCH_TEXT_STRATEGY=lakebase and/or POSTGRES_SEARCH_VECTOR_STRATEGY=lakebase only when Lakebase Search has provisioned lakebase_text and/or lakebase_vector; use POSTGRES_SEARCH_VECTOR_STRATEGY=pgvector with a deployment vector dimension when only the vector extension is provisioned. Native FTS remains the portable lexical fallback, and POSTGRES_SEARCH_TYPO_STRATEGY=pgtrgm enables curated-term typo recovery when pg_trgm is provisioned. Its private projectionGeneration(slice) token changes after successful writes and is intended for deployment-level cache keys, not public responses. On transaction-capable deployments, each search reads candidates and facets from a repeatable-read, read-only projection snapshot. Its signed cursors include every searched projection generation and reject continuation after a write or rebuild, preventing mixed-generation pagination. Policy-backed scalar filters are maintained in typed facet rows before search candidate generation. A successful full rebuild retains one predecessor per slice; deployment maintenance may use the private rollbackProjection(slice) operation before steady writes invalidate that rollback snapshot. Interrupted bulk streams retain their staged chunks for a retry when callers reuse the same rebuildRunId for one source snapshot; a changed source uses a new run id and cannot publish stale staging rows. projectionState(slice) reports the pending staged-document count until atomic publication succeeds.
  • ./indexer/typesense — native Typesense IndexerAdapter, retained as a selectable first-party provider.
  • ./indexer/postgres-provider — graph provider factory selected by deployment.providers.search: "postgres".
  • ./indexer/relevance — shared travel relevance corpus and comparison harness for measuring Postgres against a selected baseline adapter.
  • ./indexer/typesense-provider — graph provider factory selected by deployment.providers.search: "typesense".
  • ./search/rerank — Tier 2 two-stage-search orchestration helper for browse-time pricing.
  • ./drift/events — drift event types for upstream change detection.
  • ./events/taxonomy — catalog event names + visibility-filtered payload builders, emitted via @voyant-travel/core/events and consumed by the existing webhook pipeline.
  • ./adapter/contract — public source-adapter contract. Voyant Connect, third-party providers, operator-built adapters all implement this.
  • ./adapter/schemas — zod schemas for source-adapter runtime payloads. Use these at HTTP, queue, RPC, and adapter boundaries instead of re-declaring validators.
  • ./booking-engine — quote/book services plus the Hono route module that backs @voyant-travel/catalog-react/booking-engine and @voyant-travel/bookings-react/journey.

Architectural rules

The catalog plane is a contract, not a polymorphic root. Vertical modules keep their own schemas and adopt this contract; they do not share a row shape. See the architecture doc for the full rationale.

  • Per-vertical operational truth — separate tables per vertical.
  • Shared cross-cutting infrastructure — overlay store, snapshot graph, indexer pipeline, drift events, webhooks.
  • Three composition patterns — nested fields, promoted child entities, referenced CatalogEntries.
  • Three variant axes on overlays — locale, audience, market; sparse, default deployment uses two audiences and one market.

Search index providers

Search-engine choice is deployment configuration, not environment detection. Set deployment.providers.search in voyant.config.ts; credentials configure the selected provider but never select it:

import { defineConfig } from "@voyant-travel/framework/project"

export default defineConfig({
  deployment: {
    target: "node",
    providers: { search: "postgres" },
  },
})

postgres selects the first-party provider declared by this package. It uses the same graph-declared Postgres resource as the application and never opens a separate database pool from an environment variable. typesense remains available and uses TYPESENSE_HOST plus TYPESENSE_API_KEY. The standard self-hosted operator configuration uses search: "none" until search is enabled.

Embedded hosts and tests can supply a custom implementation by selecting deployment.providers.search: "custom" and passing either an IndexerAdapter or IndexerProvider at the catalog.indexer runtime port. The direct adapter form is useful when the host already owns the configured adapter instance:

// voyant.config.ts
import { defineConfig } from "@voyant-travel/framework/project"

export default defineConfig({
  deployment: {
    target: "node",
    providers: { search: "custom" },
  },
})
import { catalogIndexerProviderPort } from "@voyant-travel/catalog/indexer/provider"
import type { IndexerAdapter } from "@voyant-travel/catalog-contracts/indexer/contract"
import { loadVoyantProject } from "@voyant-travel/runtime"

const indexer: IndexerAdapter = createCustomIndexer()

await loadVoyantProject({
  host: {
    runtimePorts: { [catalogIndexerProviderPort.id]: indexer },
  },
})

The explicit port is ignored for none, typesense, algolia, and every other non-custom search selection. Those values remain authoritative and resolve only their selected graph provider. With custom, an explicit host port takes precedence over a graph-declared custom provider; without an explicit port, the graph-declared custom provider resolves normally.

Algolia and other engines are external adapter packages. They implement IndexerAdapter and IndexerProvider from @voyant-travel/catalog-contracts/indexer/contract, publish a graph provider for port catalog.indexer, and declare a matching selection such as { role: "search", value: "algolia" } or { role: "search", value: "custom" }. The deployment admits that package and sets the corresponding deployment.providers.search value; no Algolia SDK or vendor code belongs in @voyant-travel/catalog.

Adapter packages should run the test-framework-neutral conformance kit from @voyant-travel/catalog-contracts/indexer/conformance in their own test suite:

import { assertIndexerAdapterConformance } from "@voyant-travel/catalog-contracts/indexer/conformance"
import type { IndexerProvider } from "@voyant-travel/catalog-contracts/indexer/contract"

const indexerProvider: IndexerProvider = {
  create: (options) => createCustomIndexer(options),
}

await assertIndexerAdapterConformance({
  createAdapter: () => indexerProvider.create({ registries: new Map() }),
})

Provider-neutral maintenance uses the optional IndexerAdapter.admin surface (list, drop, and scan). Raw Typesense collection/search maintenance APIs are not public catalog package surface.

Relevance comparison

@voyant-travel/catalog/indexer/relevance provides a curated travel corpus and an adapter-level harness for comparing Postgres with Typesense (or another approved baseline). It reports recall@k, NDCG@k, zero-result rate, and exact facet-bucket parity. Provider scores are intentionally excluded from the comparison because each engine normalizes relevance differently. Run it against the real deployment adapters and retain the resulting report with the rollout evidence; the built-in corpus is a minimum regression gate, not a replacement for operator-approved travel judgments.

Usage

The catalog plane is consumed by vertical modules; templates wire it together.

import { defineFieldPolicy } from "@voyant-travel/catalog/contract"

export const productCatalogPolicy = defineFieldPolicy([
  {
    path: "title",
    class: "merchandisable",
    merge: "replace",
    drift: "medium",
    reindex: "entry-locale",
    snapshot: "on-book",
    query: "indexed-column",
    localized: true,
    visibility: ["staff", "customer", "partner"],
    editRole: "marketing",
    overrideFriction: "none",
    sourceFreshness: "sync",
  },
  // ...
])

See docs/architecture/catalog-architecture.md for the full contract and worked examples.

Source-adapter runtime validation

import { reserveRequestSchema } from "@voyant-travel/catalog/adapter/schemas"
import type { ReserveRequest } from "@voyant-travel/catalog/adapter/contract"

const request: ReserveRequest = reserveRequestSchema.parse(await req.json())

Reserve and cancel requests may include a scope matching live resolution plus an idempotency_key; cancel results may return status: "pending" with pending_channel for async upstream workflows.

External adapters that do not run the catalog package can import the same schemas and types from @voyant-travel/catalog-contracts/adapter/schemas and @voyant-travel/catalog-contracts/adapter/contract.

Catalog quote and draft HTTP routes

@voyant-travel/catalog exports createCatalogBookingApiModule(...) and createCatalogBookingRoutes(...) for catalog quote, draft, hold, and reservation contracts. The same functions remain available from @voyant-travel/catalog/booking-engine for consumers that prefer the narrower subpath. The module mounts these engine endpoints on both catalog API surfaces. They are not a second booking-row creation authority. Booking Session Commit is the authenticated staff and storefront creation authority; it derives and executes Finance's durable command only after the exact Quote and Hold have been validated:

  • /v1/admin/catalog/*
  • /v1/public/catalog/*

Templates provide the runtime dependencies instead of the package importing deployment code:

import { createCatalogBookingApiModule } from "@voyant-travel/catalog"

export const catalogBookingModule = createCatalogBookingApiModule({
  resolveDb: (c) => c.get("db"),
  resolveSourceRegistry: (c) => getBookingEngineRegistryFromContext(c),
  resolveOwnedHandlers: (c) => getOwnedBookingHandlerRegistryFromContext(c),
})

Apps that protect public routes by default must allow /v1/public/catalog. Template-specific routes such as slots, admin order management, checkout start, and booking snapshot enrichment stay in the template.

Catalog search HTTP routes

@voyant-travel/catalog also exports createCatalogSearchApiModule(...), createCatalogSearchRoutes(...), and mountCatalogSearchRoutes(...) for the plain JSON catalog search endpoint used by admin and storefront UIs:

  • POST /v1/admin/catalog/search
  • POST /v1/public/catalog/search

The module owns audience defaults: admin search uses the runtime defaultScope.audience, while public search defaults to the customer projection. Deployments provide the indexer and optional semantic executor per request:

import {
  createCatalogSearchApiModule,
  executeSemanticSearch,
  type EmbeddingProvider,
} from "@voyant-travel/catalog"

export const catalogSearchModule = createCatalogSearchApiModule({
  resolveRuntime: (c) => buildCatalogSearchRuntime(c),
  executeSearch: ({ adapter, embeddings, slice, request }) =>
    executeSemanticSearch({
      adapter,
      embeddings: embeddings as EmbeddingProvider | undefined,
      slice,
      request,
    }),
})

Search defaults to hybrid mode, downgrades to keyword when no embeddings are available, and retries semantic/hybrid execution as keyword when the semantic path fails. Pass fallbackToKeywordOnSearchError: false to fail closed instead.

Storefront listing pages can request typed index-layer sorting and a compact card projection from the public route:

await fetch("/v1/public/catalog/search", {
  method: "POST",
  body: JSON.stringify({
    vertical: "products",
    query: "",
    mode: "keyword",
    sort: "price-asc",
    projection: "storefront-card",
    pagination: { limit: 12 },
    facets: [{ field: "categorySlugs[]" }, { field: "departureMonths[]" }],
  }),
})

Supported sort values are relevance, price-asc, price-desc, departure-asc, and newest. Sorts are translated by the indexer adapter to safe indexed fields such as priceFromAmountCents and nextDepartureDate; they are not applied after app-side hydration.

When projection: "storefront-card" is present, the response keeps the raw hits, total, and engine facet counts, and also includes cards with the fields storefront product grids commonly need: localized name/slug, primary category, media URLs, price-from and offer badge data, departure aggregates, destinations, and coordinates.