@voyant-travel/catalog 0.117.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +190 -0
- package/dist/adapter/booking-forwarding.d.ts +2 -0
- package/dist/adapter/booking-forwarding.d.ts.map +1 -0
- package/dist/adapter/booking-forwarding.js +1 -0
- package/dist/adapter/channel-push-contracts.d.ts +2 -0
- package/dist/adapter/channel-push-contracts.d.ts.map +1 -0
- package/dist/adapter/channel-push-contracts.js +1 -0
- package/dist/adapter/contract.d.ts +2 -0
- package/dist/adapter/contract.d.ts.map +1 -0
- package/dist/adapter/contract.js +1 -0
- package/dist/adapter/contract.test.d.ts +2 -0
- package/dist/adapter/contract.test.d.ts.map +1 -0
- package/dist/adapter/contract.test.js +390 -0
- package/dist/adapter/provider-contracts.d.ts +2 -0
- package/dist/adapter/provider-contracts.d.ts.map +1 -0
- package/dist/adapter/provider-contracts.js +1 -0
- package/dist/adapter/provider-contracts.test.d.ts +2 -0
- package/dist/adapter/provider-contracts.test.d.ts.map +1 -0
- package/dist/adapter/provider-contracts.test.js +206 -0
- package/dist/adapter/schemas.d.ts +2 -0
- package/dist/adapter/schemas.d.ts.map +1 -0
- package/dist/adapter/schemas.js +1 -0
- package/dist/adapter/schemas.test.d.ts +2 -0
- package/dist/adapter/schemas.test.d.ts.map +1 -0
- package/dist/adapter/schemas.test.js +344 -0
- package/dist/booking-engine/book.d.ts +124 -0
- package/dist/booking-engine/book.d.ts.map +1 -0
- package/dist/booking-engine/book.js +311 -0
- package/dist/booking-engine/cancel.d.ts +40 -0
- package/dist/booking-engine/cancel.d.ts.map +1 -0
- package/dist/booking-engine/cancel.js +56 -0
- package/dist/booking-engine/checkout-finalize.d.ts +146 -0
- package/dist/booking-engine/checkout-finalize.d.ts.map +1 -0
- package/dist/booking-engine/checkout-finalize.js +132 -0
- package/dist/booking-engine/contracts.d.ts +9 -0
- package/dist/booking-engine/contracts.d.ts.map +1 -0
- package/dist/booking-engine/contracts.js +8 -0
- package/dist/booking-engine/contracts.test.d.ts +2 -0
- package/dist/booking-engine/contracts.test.d.ts.map +1 -0
- package/dist/booking-engine/contracts.test.js +116 -0
- package/dist/booking-engine/draft-shape.d.ts +10 -0
- package/dist/booking-engine/draft-shape.d.ts.map +1 -0
- package/dist/booking-engine/draft-shape.js +9 -0
- package/dist/booking-engine/draft-shape.test.d.ts +2 -0
- package/dist/booking-engine/draft-shape.test.d.ts.map +1 -0
- package/dist/booking-engine/draft-shape.test.js +74 -0
- package/dist/booking-engine/drafts-schema.d.ts +302 -0
- package/dist/booking-engine/drafts-schema.d.ts.map +1 -0
- package/dist/booking-engine/drafts-schema.js +53 -0
- package/dist/booking-engine/drafts-service.d.ts +41 -0
- package/dist/booking-engine/drafts-service.d.ts.map +1 -0
- package/dist/booking-engine/drafts-service.js +108 -0
- package/dist/booking-engine/errors.d.ts +81 -0
- package/dist/booking-engine/errors.d.ts.map +1 -0
- package/dist/booking-engine/errors.js +113 -0
- package/dist/booking-engine/index.d.ts +36 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +34 -0
- package/dist/booking-engine/orders.d.ts +41 -0
- package/dist/booking-engine/orders.d.ts.map +1 -0
- package/dist/booking-engine/orders.js +49 -0
- package/dist/booking-engine/owned-handler.d.ts +166 -0
- package/dist/booking-engine/owned-handler.d.ts.map +1 -0
- package/dist/booking-engine/owned-handler.js +50 -0
- package/dist/booking-engine/owned-handler.test.d.ts +2 -0
- package/dist/booking-engine/owned-handler.test.d.ts.map +1 -0
- package/dist/booking-engine/owned-handler.test.js +63 -0
- package/dist/booking-engine/promotions-contract.d.ts +8 -0
- package/dist/booking-engine/promotions-contract.d.ts.map +1 -0
- package/dist/booking-engine/promotions-contract.js +7 -0
- package/dist/booking-engine/quote-enricher.test.d.ts +12 -0
- package/dist/booking-engine/quote-enricher.test.d.ts.map +1 -0
- package/dist/booking-engine/quote-enricher.test.js +138 -0
- package/dist/booking-engine/quote.d.ts +163 -0
- package/dist/booking-engine/quote.d.ts.map +1 -0
- package/dist/booking-engine/quote.js +259 -0
- package/dist/booking-engine/registry.d.ts +85 -0
- package/dist/booking-engine/registry.d.ts.map +1 -0
- package/dist/booking-engine/registry.js +118 -0
- package/dist/booking-engine/registry.test.d.ts +2 -0
- package/dist/booking-engine/registry.test.d.ts.map +1 -0
- package/dist/booking-engine/registry.test.js +132 -0
- package/dist/booking-engine/routes-contracts.d.ts +169 -0
- package/dist/booking-engine/routes-contracts.d.ts.map +1 -0
- package/dist/booking-engine/routes-contracts.js +63 -0
- package/dist/booking-engine/routes.d.ts +7 -0
- package/dist/booking-engine/routes.d.ts.map +1 -0
- package/dist/booking-engine/routes.js +443 -0
- package/dist/booking-engine/routes.test.d.ts +2 -0
- package/dist/booking-engine/routes.test.d.ts.map +1 -0
- package/dist/booking-engine/routes.test.js +304 -0
- package/dist/booking-engine/schema.d.ts +455 -0
- package/dist/booking-engine/schema.d.ts.map +1 -0
- package/dist/booking-engine/schema.js +75 -0
- package/dist/booking-engine/snapshot-content.d.ts +120 -0
- package/dist/booking-engine/snapshot-content.d.ts.map +1 -0
- package/dist/booking-engine/snapshot-content.js +110 -0
- package/dist/booking-engine/snapshot-content.test.d.ts +2 -0
- package/dist/booking-engine/snapshot-content.test.d.ts.map +1 -0
- package/dist/booking-engine/snapshot-content.test.js +213 -0
- package/dist/booking-engine/sync.d.ts +136 -0
- package/dist/booking-engine/sync.d.ts.map +1 -0
- package/dist/booking-engine/sync.js +177 -0
- package/dist/booking-engine/sync.test.d.ts +2 -0
- package/dist/booking-engine/sync.test.d.ts.map +1 -0
- package/dist/booking-engine/sync.test.js +377 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +1 -0
- package/dist/contract.test.d.ts +2 -0
- package/dist/contract.test.d.ts.map +1 -0
- package/dist/contract.test.js +107 -0
- package/dist/drift/events.d.ts +2 -0
- package/dist/drift/events.d.ts.map +1 -0
- package/dist/drift/events.js +1 -0
- package/dist/drift/events.test.d.ts +2 -0
- package/dist/drift/events.test.d.ts.map +1 -0
- package/dist/drift/events.test.js +100 -0
- package/dist/embeddings/contract.d.ts +85 -0
- package/dist/embeddings/contract.d.ts.map +1 -0
- package/dist/embeddings/contract.js +42 -0
- package/dist/embeddings/contract.test.d.ts +2 -0
- package/dist/embeddings/contract.test.d.ts.map +1 -0
- package/dist/embeddings/contract.test.js +30 -0
- package/dist/embeddings/gemini.d.ts +110 -0
- package/dist/embeddings/gemini.d.ts.map +1 -0
- package/dist/embeddings/gemini.js +118 -0
- package/dist/embeddings/gemini.test.d.ts +2 -0
- package/dist/embeddings/gemini.test.d.ts.map +1 -0
- package/dist/embeddings/gemini.test.js +132 -0
- package/dist/embeddings/model-registry.d.ts +62 -0
- package/dist/embeddings/model-registry.d.ts.map +1 -0
- package/dist/embeddings/model-registry.js +78 -0
- package/dist/embeddings/model-registry.test.d.ts +2 -0
- package/dist/embeddings/model-registry.test.d.ts.map +1 -0
- package/dist/embeddings/model-registry.test.js +81 -0
- package/dist/embeddings/openai.d.ts +81 -0
- package/dist/embeddings/openai.d.ts.map +1 -0
- package/dist/embeddings/openai.js +123 -0
- package/dist/embeddings/openai.test.d.ts +2 -0
- package/dist/embeddings/openai.test.d.ts.map +1 -0
- package/dist/embeddings/openai.test.js +164 -0
- package/dist/events/taxonomy.d.ts +158 -0
- package/dist/events/taxonomy.d.ts.map +1 -0
- package/dist/events/taxonomy.js +99 -0
- package/dist/events/taxonomy.test.d.ts +2 -0
- package/dist/events/taxonomy.test.d.ts.map +1 -0
- package/dist/events/taxonomy.test.js +48 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/indexer/contract.d.ts +203 -0
- package/dist/indexer/contract.d.ts.map +1 -0
- package/dist/indexer/contract.js +16 -0
- package/dist/indexer/typesense-search-query.d.ts +31 -0
- package/dist/indexer/typesense-search-query.d.ts.map +1 -0
- package/dist/indexer/typesense-search-query.js +185 -0
- package/dist/indexer/typesense.d.ts +105 -0
- package/dist/indexer/typesense.d.ts.map +1 -0
- package/dist/indexer/typesense.js +394 -0
- package/dist/indexer/typesense.test.d.ts +2 -0
- package/dist/indexer/typesense.test.d.ts.map +1 -0
- package/dist/indexer/typesense.test.js +253 -0
- package/dist/overlay/resolver.d.ts +101 -0
- package/dist/overlay/resolver.d.ts.map +1 -0
- package/dist/overlay/resolver.js +167 -0
- package/dist/overlay/resolver.test.d.ts +2 -0
- package/dist/overlay/resolver.test.d.ts.map +1 -0
- package/dist/overlay/resolver.test.js +179 -0
- package/dist/overlay/schema.d.ts +266 -0
- package/dist/overlay/schema.d.ts.map +1 -0
- package/dist/overlay/schema.js +71 -0
- package/dist/provenance.d.ts +2 -0
- package/dist/provenance.d.ts.map +1 -0
- package/dist/provenance.js +1 -0
- package/dist/schema-sourced-entries.d.ts +344 -0
- package/dist/schema-sourced-entries.d.ts.map +1 -0
- package/dist/schema-sourced-entries.js +75 -0
- package/dist/schema.d.ts +21 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +20 -0
- package/dist/search/federate.d.ts +58 -0
- package/dist/search/federate.d.ts.map +1 -0
- package/dist/search/federate.js +103 -0
- package/dist/search/federate.test.d.ts +2 -0
- package/dist/search/federate.test.d.ts.map +1 -0
- package/dist/search/federate.test.js +146 -0
- package/dist/search/rerank.d.ts +77 -0
- package/dist/search/rerank.d.ts.map +1 -0
- package/dist/search/rerank.js +68 -0
- package/dist/search/rerank.test.d.ts +2 -0
- package/dist/search/rerank.test.d.ts.map +1 -0
- package/dist/search/rerank.test.js +60 -0
- package/dist/search/routes.d.ts +144 -0
- package/dist/search/routes.d.ts.map +1 -0
- package/dist/search/routes.js +288 -0
- package/dist/search/routes.test.d.ts +2 -0
- package/dist/search/routes.test.d.ts.map +1 -0
- package/dist/search/routes.test.js +322 -0
- package/dist/search/semantic.d.ts +63 -0
- package/dist/search/semantic.d.ts.map +1 -0
- package/dist/search/semantic.js +75 -0
- package/dist/search/semantic.test.d.ts +2 -0
- package/dist/search/semantic.test.d.ts.map +1 -0
- package/dist/search/semantic.test.js +143 -0
- package/dist/services/build-indexer-document.test.d.ts +2 -0
- package/dist/services/build-indexer-document.test.d.ts.map +1 -0
- package/dist/services/build-indexer-document.test.js +102 -0
- package/dist/services/content-service.d.ts +125 -0
- package/dist/services/content-service.d.ts.map +1 -0
- package/dist/services/content-service.js +139 -0
- package/dist/services/content-service.test.d.ts +2 -0
- package/dist/services/content-service.test.d.ts.map +1 -0
- package/dist/services/content-service.test.js +322 -0
- package/dist/services/indexer-service.d.ts +109 -0
- package/dist/services/indexer-service.d.ts.map +1 -0
- package/dist/services/indexer-service.js +123 -0
- package/dist/services/indexer-service.test.d.ts +2 -0
- package/dist/services/indexer-service.test.d.ts.map +1 -0
- package/dist/services/indexer-service.test.js +176 -0
- package/dist/services/overlay-service.d.ts +108 -0
- package/dist/services/overlay-service.d.ts.map +1 -0
- package/dist/services/overlay-service.js +211 -0
- package/dist/services/overlay-service.test.d.ts +2 -0
- package/dist/services/overlay-service.test.d.ts.map +1 -0
- package/dist/services/overlay-service.test.js +79 -0
- package/dist/services/snapshot-builder.test.d.ts +2 -0
- package/dist/services/snapshot-builder.test.d.ts.map +1 -0
- package/dist/services/snapshot-builder.test.js +93 -0
- package/dist/services/snapshot-service.d.ts +78 -0
- package/dist/services/snapshot-service.d.ts.map +1 -0
- package/dist/services/snapshot-service.js +165 -0
- package/dist/services/sourced-entry-service.d.ts +142 -0
- package/dist/services/sourced-entry-service.d.ts.map +1 -0
- package/dist/services/sourced-entry-service.js +203 -0
- package/dist/services/sourced-entry-service.test.d.ts +10 -0
- package/dist/services/sourced-entry-service.test.d.ts.map +1 -0
- package/dist/services/sourced-entry-service.test.js +66 -0
- package/dist/snapshot/schema.d.ts +362 -0
- package/dist/snapshot/schema.d.ts.map +1 -0
- package/dist/snapshot/schema.js +102 -0
- package/package.json +210 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FieldPolicyRegistry } from "../contract.js";
|
|
2
|
+
import type { IndexerSlice, SearchRequest } from "./contract.js";
|
|
3
|
+
export interface TypesenseSearchQuery {
|
|
4
|
+
q: string;
|
|
5
|
+
query_by: string;
|
|
6
|
+
filter_by?: string;
|
|
7
|
+
facet_by?: string;
|
|
8
|
+
sort_by?: string;
|
|
9
|
+
per_page?: number;
|
|
10
|
+
page?: number;
|
|
11
|
+
vector_query?: string;
|
|
12
|
+
prefix?: boolean;
|
|
13
|
+
exclude_fields?: string;
|
|
14
|
+
drop_tokens_threshold?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Translates the catalog plane's `SearchRequest` into a Typesense query.
|
|
18
|
+
* Converts the filter expression tree, the audience-scoped query, and the
|
|
19
|
+
* pagination shape.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildSearchQuery(request: SearchRequest, registry: FieldPolicyRegistry, slice?: IndexerSlice): TypesenseSearchQuery;
|
|
22
|
+
/**
|
|
23
|
+
* Returns the policy-owned default text fields for Typesense keyword search.
|
|
24
|
+
* Hosted Typesense-compatible proxies can use this as their server-side
|
|
25
|
+
* fallback when callers omit `query_by`, avoiding client-side schema scraping.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildDefaultTypesenseSearchFields(registry: FieldPolicyRegistry, slice?: IndexerSlice): string[];
|
|
28
|
+
export declare function buildDefaultTypesenseQueryBy(registry: FieldPolicyRegistry, slice?: IndexerSlice): string;
|
|
29
|
+
export declare function typesenseTypeForField(name: string, isList: boolean): "string" | "string[]" | "int32" | "int64" | "float" | "bool" | "object" | "float[]";
|
|
30
|
+
export declare function isTypesenseSortableStringField(field: string): boolean;
|
|
31
|
+
//# sourceMappingURL=typesense-search-query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typesense-search-query.d.ts","sourceRoot":"","sources":["../../src/indexer/typesense-search-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,KAAK,EAAE,YAAY,EAAgB,aAAa,EAAoB,MAAM,eAAe,CAAA;AAEhG,MAAM,WAAW,oBAAoB;IACnC,CAAC,EAAE,MAAM,CAAA;IACT,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,CAAC,EAAE,YAAY,GACnB,oBAAoB,CAsDtB;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,CAAC,EAAE,YAAY,GACnB,MAAM,EAAE,CAIV;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,CAAC,EAAE,YAAY,GACnB,MAAM,CAGR;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,OAAO,GACd,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAWrF;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAErE"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translates the catalog plane's `SearchRequest` into a Typesense query.
|
|
3
|
+
* Converts the filter expression tree, the audience-scoped query, and the
|
|
4
|
+
* pagination shape.
|
|
5
|
+
*/
|
|
6
|
+
export function buildSearchQuery(request, registry, slice) {
|
|
7
|
+
const perPage = request.pagination?.limit ?? 20;
|
|
8
|
+
// Cursor doubles as the 1-indexed page number for Typesense's `page`
|
|
9
|
+
// parameter. Callers wanting a different cursor strategy (e.g. opaque
|
|
10
|
+
// cursor for streaming results) write a different adapter.
|
|
11
|
+
const page = parsePageCursor(request.pagination?.cursor) ?? 1;
|
|
12
|
+
const query = {
|
|
13
|
+
q: request.query.length > 0 ? request.query : "*",
|
|
14
|
+
query_by: buildDefaultTypesenseQueryBy(registry, slice),
|
|
15
|
+
per_page: perPage,
|
|
16
|
+
page,
|
|
17
|
+
// Strip the vector field from response payloads. At e.g. 3072-dim that's
|
|
18
|
+
// roughly 12 KB per hit of float-array noise the caller never reads.
|
|
19
|
+
exclude_fields: "text_embedding,embedding_model_id",
|
|
20
|
+
};
|
|
21
|
+
if (request.filters && request.filters.length > 0) {
|
|
22
|
+
query.filter_by = serializeFilters(request.filters);
|
|
23
|
+
}
|
|
24
|
+
if (request.facets && request.facets.length > 0) {
|
|
25
|
+
query.facet_by = request.facets.map((f) => normalizeTypesenseField(f.field)).join(",");
|
|
26
|
+
}
|
|
27
|
+
const sortBy = resolveTypesenseSortBy(request.sort, registry, slice);
|
|
28
|
+
if (sortBy) {
|
|
29
|
+
query.sort_by = sortBy;
|
|
30
|
+
}
|
|
31
|
+
// Hybrid / semantic mode: attach the vector query if an embedding was
|
|
32
|
+
// provided. The actual embedding generation for the query string lives in
|
|
33
|
+
// the search/semantic helper (Phase 2); this adapter just relays it.
|
|
34
|
+
if ((request.mode === "hybrid" || request.mode === "semantic") && request.query_embedding) {
|
|
35
|
+
// Ground the ANN search against a candidate pool larger than `per_page`
|
|
36
|
+
// so caller-side pagination has actual results to walk through. Typesense
|
|
37
|
+
// resolves `max(k, per_page)` per the docs, so we lift the floor.
|
|
38
|
+
const k = Math.max(perPage * 10, 100);
|
|
39
|
+
const vectorOpts = [`k:${k}`];
|
|
40
|
+
if (request.alpha != null)
|
|
41
|
+
vectorOpts.push(`alpha:${request.alpha}`);
|
|
42
|
+
if (request.distance_threshold != null) {
|
|
43
|
+
vectorOpts.push(`distance_threshold:${request.distance_threshold}`);
|
|
44
|
+
}
|
|
45
|
+
query.vector_query = `text_embedding:([${request.query_embedding.join(",")}], ${vectorOpts.join(", ")})`;
|
|
46
|
+
}
|
|
47
|
+
// For multi-token hybrid queries, the docs warn that the default
|
|
48
|
+
// `drop_tokens_threshold` (10) leads to redundant internal keyword
|
|
49
|
+
// re-runs. Disable token drop entirely for short catalog queries.
|
|
50
|
+
if (request.mode === "hybrid" && request.query.length > 0) {
|
|
51
|
+
query.drop_tokens_threshold = 0;
|
|
52
|
+
}
|
|
53
|
+
return query;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Returns the policy-owned default text fields for Typesense keyword search.
|
|
57
|
+
* Hosted Typesense-compatible proxies can use this as their server-side
|
|
58
|
+
* fallback when callers omit `query_by`, avoiding client-side schema scraping.
|
|
59
|
+
*/
|
|
60
|
+
export function buildDefaultTypesenseSearchFields(registry, slice) {
|
|
61
|
+
return registry.policies
|
|
62
|
+
.filter((policy) => isDefaultSearchablePolicy(policy, slice))
|
|
63
|
+
.map((policy) => normalizeTypesenseField(policy.path));
|
|
64
|
+
}
|
|
65
|
+
export function buildDefaultTypesenseQueryBy(registry, slice) {
|
|
66
|
+
const fields = buildDefaultTypesenseSearchFields(registry, slice);
|
|
67
|
+
return fields.length > 0 ? fields.join(",") : "title";
|
|
68
|
+
}
|
|
69
|
+
export function typesenseTypeForField(name, isList) {
|
|
70
|
+
if (isList)
|
|
71
|
+
return "string[]";
|
|
72
|
+
if (BOOLEAN_FIELD_NAMES.has(name) || /^(has|is)[A-Z]/.test(name))
|
|
73
|
+
return "bool";
|
|
74
|
+
if (FLOAT_FIELD_NAMES.has(name) || /(Latitude|Longitude)$/.test(name))
|
|
75
|
+
return "float";
|
|
76
|
+
if (INTEGER_FIELD_NAMES.has(name) ||
|
|
77
|
+
INTEGER_FIELD_SUFFIXES.some((suffix) => name.endsWith(suffix))) {
|
|
78
|
+
return "int64";
|
|
79
|
+
}
|
|
80
|
+
return "string";
|
|
81
|
+
}
|
|
82
|
+
export function isTypesenseSortableStringField(field) {
|
|
83
|
+
return SORTABLE_STRING_FIELD_NAMES.has(field);
|
|
84
|
+
}
|
|
85
|
+
function resolveTypesenseSortBy(sort, registry, slice) {
|
|
86
|
+
if (!sort || sort === "relevance")
|
|
87
|
+
return undefined;
|
|
88
|
+
const fields = SORT_FIELDS[sort];
|
|
89
|
+
const field = fields.find((candidate) => isSortableField(candidate, registry, slice));
|
|
90
|
+
if (!field)
|
|
91
|
+
return undefined;
|
|
92
|
+
const direction = sort === "price-desc" || sort === "newest" ? "desc" : "asc";
|
|
93
|
+
return `${field}:${direction}`;
|
|
94
|
+
}
|
|
95
|
+
const SORT_FIELDS = {
|
|
96
|
+
"price-asc": ["priceFromAmountCents", "sellAmountCents"],
|
|
97
|
+
"price-desc": ["priceFromAmountCents", "sellAmountCents"],
|
|
98
|
+
"departure-asc": ["nextDepartureDate", "nextDepartureAt", "startDateEpochDays", "startDate"],
|
|
99
|
+
newest: ["publishedAt", "createdAt"],
|
|
100
|
+
};
|
|
101
|
+
function isSortableField(field, registry, slice) {
|
|
102
|
+
const policy = registry.resolve(field);
|
|
103
|
+
if (!policy)
|
|
104
|
+
return false;
|
|
105
|
+
if (!slice || slice.audience === "staff-admin")
|
|
106
|
+
return true;
|
|
107
|
+
return policy.visibility.includes(slice.audience);
|
|
108
|
+
}
|
|
109
|
+
function parsePageCursor(cursor) {
|
|
110
|
+
if (!cursor)
|
|
111
|
+
return undefined;
|
|
112
|
+
const n = Number(cursor);
|
|
113
|
+
return Number.isFinite(n) && n >= 1 ? Math.floor(n) : undefined;
|
|
114
|
+
}
|
|
115
|
+
function serializeFilters(filters) {
|
|
116
|
+
return filters.map(serializeFilter).filter(Boolean).join(" && ");
|
|
117
|
+
}
|
|
118
|
+
function serializeFilter(filter) {
|
|
119
|
+
switch (filter.kind) {
|
|
120
|
+
case "eq":
|
|
121
|
+
return `${normalizeTypesenseField(filter.field)}:=${typeof filter.value === "string" ? `"${filter.value}"` : filter.value}`;
|
|
122
|
+
case "in":
|
|
123
|
+
return `${normalizeTypesenseField(filter.field)}:[${filter.values.map((v) => (typeof v === "string" ? `"${v}"` : v)).join(",")}]`;
|
|
124
|
+
case "range": {
|
|
125
|
+
const parts = [];
|
|
126
|
+
const field = normalizeTypesenseField(filter.field);
|
|
127
|
+
if (filter.gte != null)
|
|
128
|
+
parts.push(`${field}:>=${filter.gte}`);
|
|
129
|
+
if (filter.lte != null)
|
|
130
|
+
parts.push(`${field}:<=${filter.lte}`);
|
|
131
|
+
return parts.join(" && ");
|
|
132
|
+
}
|
|
133
|
+
case "and":
|
|
134
|
+
return filter.clauses.map(serializeFilter).join(" && ");
|
|
135
|
+
case "or":
|
|
136
|
+
return `(${filter.clauses.map(serializeFilter).join(" || ")})`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function normalizeTypesenseField(field) {
|
|
140
|
+
return field.endsWith("[]") ? field.slice(0, -2) : field;
|
|
141
|
+
}
|
|
142
|
+
function isDefaultSearchablePolicy(policy, slice) {
|
|
143
|
+
if (policy.query !== "indexed-column")
|
|
144
|
+
return false;
|
|
145
|
+
if (policy.class !== "merchandisable" && policy.class !== "structural")
|
|
146
|
+
return false;
|
|
147
|
+
if (!isVisibleInSlice(policy.visibility, slice))
|
|
148
|
+
return false;
|
|
149
|
+
const field = normalizeTypesenseField(policy.path);
|
|
150
|
+
return isTextSearchableField(field) && !isNonSearchTextField(field);
|
|
151
|
+
}
|
|
152
|
+
function isVisibleInSlice(visibility, slice) {
|
|
153
|
+
if (!slice || slice.audience === "staff-admin")
|
|
154
|
+
return true;
|
|
155
|
+
return visibility.includes(slice.audience);
|
|
156
|
+
}
|
|
157
|
+
function isTextSearchableField(name) {
|
|
158
|
+
const type = typesenseTypeForField(name, false);
|
|
159
|
+
return type === "string" || type === "string[]";
|
|
160
|
+
}
|
|
161
|
+
function isNonSearchTextField(name) {
|
|
162
|
+
return SORTABLE_STRING_FIELD_NAMES.has(name) || NON_SEARCH_TEXT_FIELD_RE.test(name);
|
|
163
|
+
}
|
|
164
|
+
const BOOLEAN_FIELD_NAMES = new Set(["activated"]);
|
|
165
|
+
const FLOAT_FIELD_NAMES = new Set(["latitude", "longitude"]);
|
|
166
|
+
const INTEGER_FIELD_NAMES = new Set(["pax"]);
|
|
167
|
+
const INTEGER_FIELD_SUFFIXES = [
|
|
168
|
+
"AmountCents",
|
|
169
|
+
"Percent",
|
|
170
|
+
"Count",
|
|
171
|
+
"Days",
|
|
172
|
+
"EpochDays",
|
|
173
|
+
"EpochMs",
|
|
174
|
+
"Minutes",
|
|
175
|
+
"Total",
|
|
176
|
+
];
|
|
177
|
+
const SORTABLE_STRING_FIELD_NAMES = new Set([
|
|
178
|
+
"createdAt",
|
|
179
|
+
"endDate",
|
|
180
|
+
"nextDepartureAt",
|
|
181
|
+
"nextDepartureDate",
|
|
182
|
+
"publishedAt",
|
|
183
|
+
"startDate",
|
|
184
|
+
]);
|
|
185
|
+
const NON_SEARCH_TEXT_FIELD_RE = /(?:^|\.)(?:.*Url|.*Uri|.*Href|.*Html|.*Markdown)$/i;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Typesense IndexerAdapter — the v1 default for catalog-plane search.
|
|
3
|
+
*
|
|
4
|
+
* Uses an injected `TypesenseClient` interface (mirroring the storage R2Bucket
|
|
5
|
+
* binding pattern) so the package doesn't take a hard dep on the Typesense
|
|
6
|
+
* HTTP SDK. Templates wire in the actual client.
|
|
7
|
+
*
|
|
8
|
+
* See `docs/architecture/catalog-architecture.md` §5.4.1 for design.
|
|
9
|
+
*/
|
|
10
|
+
import type { FieldPolicyRegistry } from "../contract.js";
|
|
11
|
+
import type { DocumentEmitter, IndexerAdapter, IndexerSlice } from "./contract.js";
|
|
12
|
+
import { type TypesenseSearchQuery } from "./typesense-search-query.js";
|
|
13
|
+
export { buildDefaultTypesenseQueryBy, buildDefaultTypesenseSearchFields, buildSearchQuery, type TypesenseSearchQuery, } from "./typesense-search-query.js";
|
|
14
|
+
/**
|
|
15
|
+
* Minimal interface the Typesense client must satisfy. Templates pass in
|
|
16
|
+
* the real `typesense` SDK client (or a custom HTTP wrapper) and the adapter
|
|
17
|
+
* uses only these methods.
|
|
18
|
+
*/
|
|
19
|
+
export interface TypesenseClient {
|
|
20
|
+
collections(name?: string): {
|
|
21
|
+
create(schema: TypesenseCollectionSchema): Promise<void>;
|
|
22
|
+
update(schema: Partial<TypesenseCollectionSchema>): Promise<void>;
|
|
23
|
+
delete(): Promise<void>;
|
|
24
|
+
retrieve(): Promise<TypesenseCollectionSchema>;
|
|
25
|
+
documents(): {
|
|
26
|
+
import(documents: unknown[], options?: {
|
|
27
|
+
action?: "upsert" | "create";
|
|
28
|
+
}): Promise<unknown>;
|
|
29
|
+
delete(query: {
|
|
30
|
+
filter_by: string;
|
|
31
|
+
}): Promise<unknown>;
|
|
32
|
+
search(query: TypesenseSearchQuery): Promise<TypesenseSearchResponse>;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface TypesenseFieldSchema {
|
|
37
|
+
name: string;
|
|
38
|
+
type: "string" | "string[]" | "int32" | "int64" | "float" | "bool" | "object" | "float[]";
|
|
39
|
+
facet?: boolean;
|
|
40
|
+
index?: boolean;
|
|
41
|
+
optional?: boolean;
|
|
42
|
+
sort?: boolean;
|
|
43
|
+
num_dim?: number;
|
|
44
|
+
vec_dist?: "cosine" | "ip";
|
|
45
|
+
}
|
|
46
|
+
export interface TypesenseCollectionSchema {
|
|
47
|
+
name: string;
|
|
48
|
+
fields: TypesenseFieldSchema[];
|
|
49
|
+
default_sorting_field?: string;
|
|
50
|
+
enable_nested_fields?: boolean;
|
|
51
|
+
metadata?: Record<string, unknown>;
|
|
52
|
+
}
|
|
53
|
+
export interface TypesenseSearchHit {
|
|
54
|
+
document: Record<string, unknown>;
|
|
55
|
+
text_match: number;
|
|
56
|
+
}
|
|
57
|
+
export interface TypesenseSearchResponse {
|
|
58
|
+
hits: TypesenseSearchHit[];
|
|
59
|
+
found: number;
|
|
60
|
+
facet_counts?: Array<{
|
|
61
|
+
field_name: string;
|
|
62
|
+
counts: Array<{
|
|
63
|
+
value: string | number;
|
|
64
|
+
count: number;
|
|
65
|
+
}>;
|
|
66
|
+
}>;
|
|
67
|
+
}
|
|
68
|
+
export interface TypesenseIndexerOptions {
|
|
69
|
+
client: TypesenseClient;
|
|
70
|
+
/** Embedding dimension shipped by the configured EmbeddingProvider. */
|
|
71
|
+
vectorDimensions?: number | null;
|
|
72
|
+
/** Optional collection-name prefix (useful for multi-tenant single-cluster setups). */
|
|
73
|
+
collectionPrefix?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Field-policy registries keyed by vertical. Seeds the per-vertical registry
|
|
76
|
+
* cache so a search-only process (the worker, which never runs
|
|
77
|
+
* `ensureCollection`) builds queries against the REAL policy — including
|
|
78
|
+
* numeric sort/filter fields. Without this, search falls back to
|
|
79
|
+
* `inferRegistryFromCollection`, which only knows string fields, so numeric
|
|
80
|
+
* sorts (e.g. `price-asc` → `priceFromAmountCents`) silently no-op.
|
|
81
|
+
*/
|
|
82
|
+
registries?: ReadonlyMap<string, FieldPolicyRegistry>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Returns the Typesense collection name for one variant slice. Stable across
|
|
86
|
+
* runs so existing collections survive deployments.
|
|
87
|
+
*/
|
|
88
|
+
export declare function collectionName(slice: IndexerSlice, prefix?: string): string;
|
|
89
|
+
/**
|
|
90
|
+
* Builds a Typesense collection schema from the field-policy registry. Maps
|
|
91
|
+
* field-policy types onto Typesense field types using `query` + `class` from
|
|
92
|
+
* the policy.
|
|
93
|
+
*/
|
|
94
|
+
export declare function buildCollectionSchema(slice: IndexerSlice, registry: FieldPolicyRegistry, options?: {
|
|
95
|
+
vectorDimensions?: number | null;
|
|
96
|
+
collectionPrefix?: string;
|
|
97
|
+
}): TypesenseCollectionSchema;
|
|
98
|
+
export declare function createTypesenseIndexer(options: TypesenseIndexerOptions): IndexerAdapter;
|
|
99
|
+
/**
|
|
100
|
+
* Helper for verticals that want to register a `DocumentEmitter` against
|
|
101
|
+
* this adapter. Currently a thin pass-through; reserved for future emitter
|
|
102
|
+
* registry extensions.
|
|
103
|
+
*/
|
|
104
|
+
export declare function attachEmitter<TSource>(emitter: DocumentEmitter<TSource>): DocumentEmitter<TSource>;
|
|
105
|
+
//# sourceMappingURL=typesense.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typesense.d.ts","sourceRoot":"","sources":["../../src/indexer/typesense.ts"],"names":[],"mappings":"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"}
|