@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.
Files changed (243) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +190 -0
  3. package/dist/adapter/booking-forwarding.d.ts +2 -0
  4. package/dist/adapter/booking-forwarding.d.ts.map +1 -0
  5. package/dist/adapter/booking-forwarding.js +1 -0
  6. package/dist/adapter/channel-push-contracts.d.ts +2 -0
  7. package/dist/adapter/channel-push-contracts.d.ts.map +1 -0
  8. package/dist/adapter/channel-push-contracts.js +1 -0
  9. package/dist/adapter/contract.d.ts +2 -0
  10. package/dist/adapter/contract.d.ts.map +1 -0
  11. package/dist/adapter/contract.js +1 -0
  12. package/dist/adapter/contract.test.d.ts +2 -0
  13. package/dist/adapter/contract.test.d.ts.map +1 -0
  14. package/dist/adapter/contract.test.js +390 -0
  15. package/dist/adapter/provider-contracts.d.ts +2 -0
  16. package/dist/adapter/provider-contracts.d.ts.map +1 -0
  17. package/dist/adapter/provider-contracts.js +1 -0
  18. package/dist/adapter/provider-contracts.test.d.ts +2 -0
  19. package/dist/adapter/provider-contracts.test.d.ts.map +1 -0
  20. package/dist/adapter/provider-contracts.test.js +206 -0
  21. package/dist/adapter/schemas.d.ts +2 -0
  22. package/dist/adapter/schemas.d.ts.map +1 -0
  23. package/dist/adapter/schemas.js +1 -0
  24. package/dist/adapter/schemas.test.d.ts +2 -0
  25. package/dist/adapter/schemas.test.d.ts.map +1 -0
  26. package/dist/adapter/schemas.test.js +344 -0
  27. package/dist/booking-engine/book.d.ts +124 -0
  28. package/dist/booking-engine/book.d.ts.map +1 -0
  29. package/dist/booking-engine/book.js +311 -0
  30. package/dist/booking-engine/cancel.d.ts +40 -0
  31. package/dist/booking-engine/cancel.d.ts.map +1 -0
  32. package/dist/booking-engine/cancel.js +56 -0
  33. package/dist/booking-engine/checkout-finalize.d.ts +146 -0
  34. package/dist/booking-engine/checkout-finalize.d.ts.map +1 -0
  35. package/dist/booking-engine/checkout-finalize.js +132 -0
  36. package/dist/booking-engine/contracts.d.ts +9 -0
  37. package/dist/booking-engine/contracts.d.ts.map +1 -0
  38. package/dist/booking-engine/contracts.js +8 -0
  39. package/dist/booking-engine/contracts.test.d.ts +2 -0
  40. package/dist/booking-engine/contracts.test.d.ts.map +1 -0
  41. package/dist/booking-engine/contracts.test.js +116 -0
  42. package/dist/booking-engine/draft-shape.d.ts +10 -0
  43. package/dist/booking-engine/draft-shape.d.ts.map +1 -0
  44. package/dist/booking-engine/draft-shape.js +9 -0
  45. package/dist/booking-engine/draft-shape.test.d.ts +2 -0
  46. package/dist/booking-engine/draft-shape.test.d.ts.map +1 -0
  47. package/dist/booking-engine/draft-shape.test.js +74 -0
  48. package/dist/booking-engine/drafts-schema.d.ts +302 -0
  49. package/dist/booking-engine/drafts-schema.d.ts.map +1 -0
  50. package/dist/booking-engine/drafts-schema.js +53 -0
  51. package/dist/booking-engine/drafts-service.d.ts +41 -0
  52. package/dist/booking-engine/drafts-service.d.ts.map +1 -0
  53. package/dist/booking-engine/drafts-service.js +108 -0
  54. package/dist/booking-engine/errors.d.ts +81 -0
  55. package/dist/booking-engine/errors.d.ts.map +1 -0
  56. package/dist/booking-engine/errors.js +113 -0
  57. package/dist/booking-engine/index.d.ts +36 -0
  58. package/dist/booking-engine/index.d.ts.map +1 -0
  59. package/dist/booking-engine/index.js +34 -0
  60. package/dist/booking-engine/orders.d.ts +41 -0
  61. package/dist/booking-engine/orders.d.ts.map +1 -0
  62. package/dist/booking-engine/orders.js +49 -0
  63. package/dist/booking-engine/owned-handler.d.ts +166 -0
  64. package/dist/booking-engine/owned-handler.d.ts.map +1 -0
  65. package/dist/booking-engine/owned-handler.js +50 -0
  66. package/dist/booking-engine/owned-handler.test.d.ts +2 -0
  67. package/dist/booking-engine/owned-handler.test.d.ts.map +1 -0
  68. package/dist/booking-engine/owned-handler.test.js +63 -0
  69. package/dist/booking-engine/promotions-contract.d.ts +8 -0
  70. package/dist/booking-engine/promotions-contract.d.ts.map +1 -0
  71. package/dist/booking-engine/promotions-contract.js +7 -0
  72. package/dist/booking-engine/quote-enricher.test.d.ts +12 -0
  73. package/dist/booking-engine/quote-enricher.test.d.ts.map +1 -0
  74. package/dist/booking-engine/quote-enricher.test.js +138 -0
  75. package/dist/booking-engine/quote.d.ts +163 -0
  76. package/dist/booking-engine/quote.d.ts.map +1 -0
  77. package/dist/booking-engine/quote.js +259 -0
  78. package/dist/booking-engine/registry.d.ts +85 -0
  79. package/dist/booking-engine/registry.d.ts.map +1 -0
  80. package/dist/booking-engine/registry.js +118 -0
  81. package/dist/booking-engine/registry.test.d.ts +2 -0
  82. package/dist/booking-engine/registry.test.d.ts.map +1 -0
  83. package/dist/booking-engine/registry.test.js +132 -0
  84. package/dist/booking-engine/routes-contracts.d.ts +169 -0
  85. package/dist/booking-engine/routes-contracts.d.ts.map +1 -0
  86. package/dist/booking-engine/routes-contracts.js +63 -0
  87. package/dist/booking-engine/routes.d.ts +7 -0
  88. package/dist/booking-engine/routes.d.ts.map +1 -0
  89. package/dist/booking-engine/routes.js +443 -0
  90. package/dist/booking-engine/routes.test.d.ts +2 -0
  91. package/dist/booking-engine/routes.test.d.ts.map +1 -0
  92. package/dist/booking-engine/routes.test.js +304 -0
  93. package/dist/booking-engine/schema.d.ts +455 -0
  94. package/dist/booking-engine/schema.d.ts.map +1 -0
  95. package/dist/booking-engine/schema.js +75 -0
  96. package/dist/booking-engine/snapshot-content.d.ts +120 -0
  97. package/dist/booking-engine/snapshot-content.d.ts.map +1 -0
  98. package/dist/booking-engine/snapshot-content.js +110 -0
  99. package/dist/booking-engine/snapshot-content.test.d.ts +2 -0
  100. package/dist/booking-engine/snapshot-content.test.d.ts.map +1 -0
  101. package/dist/booking-engine/snapshot-content.test.js +213 -0
  102. package/dist/booking-engine/sync.d.ts +136 -0
  103. package/dist/booking-engine/sync.d.ts.map +1 -0
  104. package/dist/booking-engine/sync.js +177 -0
  105. package/dist/booking-engine/sync.test.d.ts +2 -0
  106. package/dist/booking-engine/sync.test.d.ts.map +1 -0
  107. package/dist/booking-engine/sync.test.js +377 -0
  108. package/dist/contract.d.ts +2 -0
  109. package/dist/contract.d.ts.map +1 -0
  110. package/dist/contract.js +1 -0
  111. package/dist/contract.test.d.ts +2 -0
  112. package/dist/contract.test.d.ts.map +1 -0
  113. package/dist/contract.test.js +107 -0
  114. package/dist/drift/events.d.ts +2 -0
  115. package/dist/drift/events.d.ts.map +1 -0
  116. package/dist/drift/events.js +1 -0
  117. package/dist/drift/events.test.d.ts +2 -0
  118. package/dist/drift/events.test.d.ts.map +1 -0
  119. package/dist/drift/events.test.js +100 -0
  120. package/dist/embeddings/contract.d.ts +85 -0
  121. package/dist/embeddings/contract.d.ts.map +1 -0
  122. package/dist/embeddings/contract.js +42 -0
  123. package/dist/embeddings/contract.test.d.ts +2 -0
  124. package/dist/embeddings/contract.test.d.ts.map +1 -0
  125. package/dist/embeddings/contract.test.js +30 -0
  126. package/dist/embeddings/gemini.d.ts +110 -0
  127. package/dist/embeddings/gemini.d.ts.map +1 -0
  128. package/dist/embeddings/gemini.js +118 -0
  129. package/dist/embeddings/gemini.test.d.ts +2 -0
  130. package/dist/embeddings/gemini.test.d.ts.map +1 -0
  131. package/dist/embeddings/gemini.test.js +132 -0
  132. package/dist/embeddings/model-registry.d.ts +62 -0
  133. package/dist/embeddings/model-registry.d.ts.map +1 -0
  134. package/dist/embeddings/model-registry.js +78 -0
  135. package/dist/embeddings/model-registry.test.d.ts +2 -0
  136. package/dist/embeddings/model-registry.test.d.ts.map +1 -0
  137. package/dist/embeddings/model-registry.test.js +81 -0
  138. package/dist/embeddings/openai.d.ts +81 -0
  139. package/dist/embeddings/openai.d.ts.map +1 -0
  140. package/dist/embeddings/openai.js +123 -0
  141. package/dist/embeddings/openai.test.d.ts +2 -0
  142. package/dist/embeddings/openai.test.d.ts.map +1 -0
  143. package/dist/embeddings/openai.test.js +164 -0
  144. package/dist/events/taxonomy.d.ts +158 -0
  145. package/dist/events/taxonomy.d.ts.map +1 -0
  146. package/dist/events/taxonomy.js +99 -0
  147. package/dist/events/taxonomy.test.d.ts +2 -0
  148. package/dist/events/taxonomy.test.d.ts.map +1 -0
  149. package/dist/events/taxonomy.test.js +48 -0
  150. package/dist/index.d.ts +27 -0
  151. package/dist/index.d.ts.map +1 -0
  152. package/dist/index.js +39 -0
  153. package/dist/indexer/contract.d.ts +203 -0
  154. package/dist/indexer/contract.d.ts.map +1 -0
  155. package/dist/indexer/contract.js +16 -0
  156. package/dist/indexer/typesense-search-query.d.ts +31 -0
  157. package/dist/indexer/typesense-search-query.d.ts.map +1 -0
  158. package/dist/indexer/typesense-search-query.js +185 -0
  159. package/dist/indexer/typesense.d.ts +105 -0
  160. package/dist/indexer/typesense.d.ts.map +1 -0
  161. package/dist/indexer/typesense.js +394 -0
  162. package/dist/indexer/typesense.test.d.ts +2 -0
  163. package/dist/indexer/typesense.test.d.ts.map +1 -0
  164. package/dist/indexer/typesense.test.js +253 -0
  165. package/dist/overlay/resolver.d.ts +101 -0
  166. package/dist/overlay/resolver.d.ts.map +1 -0
  167. package/dist/overlay/resolver.js +167 -0
  168. package/dist/overlay/resolver.test.d.ts +2 -0
  169. package/dist/overlay/resolver.test.d.ts.map +1 -0
  170. package/dist/overlay/resolver.test.js +179 -0
  171. package/dist/overlay/schema.d.ts +266 -0
  172. package/dist/overlay/schema.d.ts.map +1 -0
  173. package/dist/overlay/schema.js +71 -0
  174. package/dist/provenance.d.ts +2 -0
  175. package/dist/provenance.d.ts.map +1 -0
  176. package/dist/provenance.js +1 -0
  177. package/dist/schema-sourced-entries.d.ts +344 -0
  178. package/dist/schema-sourced-entries.d.ts.map +1 -0
  179. package/dist/schema-sourced-entries.js +75 -0
  180. package/dist/schema.d.ts +21 -0
  181. package/dist/schema.d.ts.map +1 -0
  182. package/dist/schema.js +20 -0
  183. package/dist/search/federate.d.ts +58 -0
  184. package/dist/search/federate.d.ts.map +1 -0
  185. package/dist/search/federate.js +103 -0
  186. package/dist/search/federate.test.d.ts +2 -0
  187. package/dist/search/federate.test.d.ts.map +1 -0
  188. package/dist/search/federate.test.js +146 -0
  189. package/dist/search/rerank.d.ts +77 -0
  190. package/dist/search/rerank.d.ts.map +1 -0
  191. package/dist/search/rerank.js +68 -0
  192. package/dist/search/rerank.test.d.ts +2 -0
  193. package/dist/search/rerank.test.d.ts.map +1 -0
  194. package/dist/search/rerank.test.js +60 -0
  195. package/dist/search/routes.d.ts +144 -0
  196. package/dist/search/routes.d.ts.map +1 -0
  197. package/dist/search/routes.js +288 -0
  198. package/dist/search/routes.test.d.ts +2 -0
  199. package/dist/search/routes.test.d.ts.map +1 -0
  200. package/dist/search/routes.test.js +322 -0
  201. package/dist/search/semantic.d.ts +63 -0
  202. package/dist/search/semantic.d.ts.map +1 -0
  203. package/dist/search/semantic.js +75 -0
  204. package/dist/search/semantic.test.d.ts +2 -0
  205. package/dist/search/semantic.test.d.ts.map +1 -0
  206. package/dist/search/semantic.test.js +143 -0
  207. package/dist/services/build-indexer-document.test.d.ts +2 -0
  208. package/dist/services/build-indexer-document.test.d.ts.map +1 -0
  209. package/dist/services/build-indexer-document.test.js +102 -0
  210. package/dist/services/content-service.d.ts +125 -0
  211. package/dist/services/content-service.d.ts.map +1 -0
  212. package/dist/services/content-service.js +139 -0
  213. package/dist/services/content-service.test.d.ts +2 -0
  214. package/dist/services/content-service.test.d.ts.map +1 -0
  215. package/dist/services/content-service.test.js +322 -0
  216. package/dist/services/indexer-service.d.ts +109 -0
  217. package/dist/services/indexer-service.d.ts.map +1 -0
  218. package/dist/services/indexer-service.js +123 -0
  219. package/dist/services/indexer-service.test.d.ts +2 -0
  220. package/dist/services/indexer-service.test.d.ts.map +1 -0
  221. package/dist/services/indexer-service.test.js +176 -0
  222. package/dist/services/overlay-service.d.ts +108 -0
  223. package/dist/services/overlay-service.d.ts.map +1 -0
  224. package/dist/services/overlay-service.js +211 -0
  225. package/dist/services/overlay-service.test.d.ts +2 -0
  226. package/dist/services/overlay-service.test.d.ts.map +1 -0
  227. package/dist/services/overlay-service.test.js +79 -0
  228. package/dist/services/snapshot-builder.test.d.ts +2 -0
  229. package/dist/services/snapshot-builder.test.d.ts.map +1 -0
  230. package/dist/services/snapshot-builder.test.js +93 -0
  231. package/dist/services/snapshot-service.d.ts +78 -0
  232. package/dist/services/snapshot-service.d.ts.map +1 -0
  233. package/dist/services/snapshot-service.js +165 -0
  234. package/dist/services/sourced-entry-service.d.ts +142 -0
  235. package/dist/services/sourced-entry-service.d.ts.map +1 -0
  236. package/dist/services/sourced-entry-service.js +203 -0
  237. package/dist/services/sourced-entry-service.test.d.ts +10 -0
  238. package/dist/services/sourced-entry-service.test.d.ts.map +1 -0
  239. package/dist/services/sourced-entry-service.test.js +66 -0
  240. package/dist/snapshot/schema.d.ts +362 -0
  241. package/dist/snapshot/schema.d.ts.map +1 -0
  242. package/dist/snapshot/schema.js +102 -0
  243. 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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"}