@endora-commerce/mod-search 0.100.0

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 (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/backend/cli/reindex.d.ts +4 -0
  4. package/dist/backend/cli/reindex.d.ts.map +1 -0
  5. package/dist/backend/cli/reindex.js +32 -0
  6. package/dist/backend/cli/reindex.js.map +1 -0
  7. package/dist/backend/entities/search-phrase-record.entity.d.ts +40 -0
  8. package/dist/backend/entities/search-phrase-record.entity.d.ts.map +1 -0
  9. package/dist/backend/entities/search-phrase-record.entity.js +85 -0
  10. package/dist/backend/entities/search-phrase-record.entity.js.map +1 -0
  11. package/dist/backend/index.d.ts +87 -0
  12. package/dist/backend/index.d.ts.map +1 -0
  13. package/dist/backend/index.js +153 -0
  14. package/dist/backend/index.js.map +1 -0
  15. package/dist/backend/plugin.d.ts +133 -0
  16. package/dist/backend/plugin.d.ts.map +1 -0
  17. package/dist/backend/plugin.js +250 -0
  18. package/dist/backend/plugin.js.map +1 -0
  19. package/dist/backend/routes.admin.d.ts +28 -0
  20. package/dist/backend/routes.admin.d.ts.map +1 -0
  21. package/dist/backend/routes.admin.js +33 -0
  22. package/dist/backend/routes.admin.js.map +1 -0
  23. package/dist/backend/routes.public.d.ts +22 -0
  24. package/dist/backend/routes.public.d.ts.map +1 -0
  25. package/dist/backend/routes.public.js +192 -0
  26. package/dist/backend/routes.public.js.map +1 -0
  27. package/dist/backend/services/embedder-config-resolver.d.ts +25 -0
  28. package/dist/backend/services/embedder-config-resolver.d.ts.map +1 -0
  29. package/dist/backend/services/embedder-config-resolver.js +47 -0
  30. package/dist/backend/services/embedder-config-resolver.js.map +1 -0
  31. package/dist/backend/services/llm-toggle.service.d.ts +50 -0
  32. package/dist/backend/services/llm-toggle.service.d.ts.map +1 -0
  33. package/dist/backend/services/llm-toggle.service.js +78 -0
  34. package/dist/backend/services/llm-toggle.service.js.map +1 -0
  35. package/dist/backend/services/search-event-subscriber.d.ts +125 -0
  36. package/dist/backend/services/search-event-subscriber.d.ts.map +1 -0
  37. package/dist/backend/services/search-event-subscriber.js +113 -0
  38. package/dist/backend/services/search-event-subscriber.js.map +1 -0
  39. package/dist/backend/services/search-indexer.d.ts +346 -0
  40. package/dist/backend/services/search-indexer.d.ts.map +1 -0
  41. package/dist/backend/services/search-indexer.js +670 -0
  42. package/dist/backend/services/search-indexer.js.map +1 -0
  43. package/dist/backend/services/search-phrase-recorder.service.d.ts +25 -0
  44. package/dist/backend/services/search-phrase-recorder.service.d.ts.map +1 -0
  45. package/dist/backend/services/search-phrase-recorder.service.js +89 -0
  46. package/dist/backend/services/search-phrase-recorder.service.js.map +1 -0
  47. package/dist/backend/services/search-query-port.d.ts +29 -0
  48. package/dist/backend/services/search-query-port.d.ts.map +1 -0
  49. package/dist/backend/services/search-query-port.js +20 -0
  50. package/dist/backend/services/search-query-port.js.map +1 -0
  51. package/dist/backend/services/search-query.service.d.ts +205 -0
  52. package/dist/backend/services/search-query.service.d.ts.map +1 -0
  53. package/dist/backend/services/search-query.service.js +346 -0
  54. package/dist/backend/services/search-query.service.js.map +1 -0
  55. package/dist/backend/services/search-reindex-worker.d.ts +27 -0
  56. package/dist/backend/services/search-reindex-worker.d.ts.map +1 -0
  57. package/dist/backend/services/search-reindex-worker.js +13 -0
  58. package/dist/backend/services/search-reindex-worker.js.map +1 -0
  59. package/dist/backend/services/search-suggest.service.d.ts +53 -0
  60. package/dist/backend/services/search-suggest.service.d.ts.map +1 -0
  61. package/dist/backend/services/search-suggest.service.js +64 -0
  62. package/dist/backend/services/search-suggest.service.js.map +1 -0
  63. package/dist/backend/services/suggestion-pricing-enricher.d.ts +62 -0
  64. package/dist/backend/services/suggestion-pricing-enricher.d.ts.map +1 -0
  65. package/dist/backend/services/suggestion-pricing-enricher.js +41 -0
  66. package/dist/backend/services/suggestion-pricing-enricher.js.map +1 -0
  67. package/dist/manifest.d.ts +263 -0
  68. package/dist/manifest.d.ts.map +1 -0
  69. package/dist/manifest.js +300 -0
  70. package/dist/manifest.js.map +1 -0
  71. package/dist/migrations/20260501T123145_search_phrase_records_init.d.ts +13 -0
  72. package/dist/migrations/20260501T123145_search_phrase_records_init.d.ts.map +1 -0
  73. package/dist/migrations/20260501T123145_search_phrase_records_init.js +40 -0
  74. package/dist/migrations/20260501T123145_search_phrase_records_init.js.map +1 -0
  75. package/dist/migrations/index.d.ts +27 -0
  76. package/dist/migrations/index.d.ts.map +1 -0
  77. package/dist/migrations/index.js +29 -0
  78. package/dist/migrations/index.js.map +1 -0
  79. package/docs/search.md +284 -0
  80. package/i18n/en.json +10 -0
  81. package/i18n/pl.json +10 -0
  82. package/package.json +72 -0
@@ -0,0 +1,346 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type CatalogAttributeReadPort, type CatalogCategoryReadPort, type CatalogProductReadPort } from '@endora-commerce/contracts';
3
+ import type { SalesChannelMembershipPort } from '@endora-commerce/platform/kernel';
4
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
5
+ /**
6
+ * The document fields a channel index may be sorted on — issue #287.
7
+ *
8
+ * Meilisearch refuses a sort on any field outside `sortableAttributes`, and
9
+ * this indexer never called `updateSortableAttributes`, so the setting was `[]`
10
+ * on all five live indexes and `name:asc` came back as `invalid_search_sort`.
11
+ * Three of the four sorts the listing contract offers were therefore answered
12
+ * by the unreachable-index fallback — correct results, re-read from the very
13
+ * Postgres query the index exists to spare, under a log line that called a
14
+ * healthy engine unavailable.
15
+ *
16
+ * The declaration is single on purpose: this constant is what the indexer
17
+ * applies to every index **and** the alphabet `buildSort` may emit (its return
18
+ * type is built from it). A sort token cannot be added on the query side
19
+ * without appearing here, which is the only structural guarantee that stops
20
+ * the two halves drifting apart again.
21
+ *
22
+ * Every entry costs index size and settings-update time, so this is the sorts
23
+ * the contract actually offers, not a list of fields that might one day be
24
+ * sorted on. `updatedAt` is deliberately absent: no sort token names it, it is
25
+ * the freshness marker rather than an ordering, and it is what `-createdAt`
26
+ * used to be mistranslated into.
27
+ */
28
+ export declare const SORTABLE_ATTRIBUTES: readonly ["createdAt", "name"];
29
+ /** One field of {@link SORTABLE_ATTRIBUTES}. */
30
+ export type SortableAttribute = (typeof SORTABLE_ATTRIBUTES)[number];
31
+ export interface SearchIndexerOptions {
32
+ meilisearchHost?: string;
33
+ meilisearchApiKey?: string;
34
+ /** Override the indexed locale, defaults to en-US. */
35
+ locale?: string;
36
+ /**
37
+ * Feature 061 — the catalog's composed attribute read model (Principle I:
38
+ * replaces the former direct `ProductAttribute` entity find + raw
39
+ * `attribute_options` SQL). Required — the searchable/filterable settings
40
+ * and the option-label aggregation are derived from it.
41
+ *
42
+ * Feature 075, Phase C — typed as the published port rather than `catalog`'s
43
+ * service class, so this module names no file in `catalog`'s directory.
44
+ */
45
+ attributeRead: CatalogAttributeReadPort;
46
+ /**
47
+ * Feature 075, Phase C — the product rows an index document is built from.
48
+ *
49
+ * They used to be `em.find(Product, …)` / `em.findOne(Product, …)` against
50
+ * `catalog`'s table from inside this module, which is the shape Principle
51
+ * XVII cannot gate: deactivation drops no tables, so the indexer kept
52
+ * rewriting Meilisearch documents out of a module an operator had switched
53
+ * off. Through the port the same read answers 503 `MODULE_DISABLED`, which is
54
+ * the binding `catalog` dependency this module's manifest declares.
55
+ */
56
+ products: CatalogProductReadPort;
57
+ /**
58
+ * Feature 075 / D-87 — the `product ↔ category` assignments and the category
59
+ * subtree an index document projects.
60
+ *
61
+ * They were three raw joins over `catalog`'s `product_categories` and
62
+ * `categories` from inside this file. Raw SQL names no import specifier, so
63
+ * Phase C's port conversion walked straight past them and they kept
64
+ * answering with `catalog` switched off, exactly as the entity reads had.
65
+ */
66
+ categories: CatalogCategoryReadPort;
67
+ /**
68
+ * The `sales_channel_products` bridge, through the accessor Principle XII
69
+ * reserves it for. Two statements here read the table directly — the
70
+ * membership of one product, and the assortment of one channel joined to
71
+ * `products` — which is the clause `SalesChannelMembershipPort`'s own doc
72
+ * comment states and the sweep found this module breaking.
73
+ */
74
+ channelMembership: SalesChannelMembershipPort;
75
+ /**
76
+ * How long to wait for one Meilisearch task, in milliseconds, re-read per
77
+ * wait so an operator raising `search.index_task_timeout_seconds` takes
78
+ * effect without a restart — the property `search.reindex_interval_minutes`
79
+ * already has, and for the same reason: the operator who needs to change it
80
+ * is mid-incident.
81
+ *
82
+ * Optional, and this one genuinely is: absent, it answers the manifest
83
+ * default, which is the same number the Setting defaults to. That is not the
84
+ * "optional option that silently produced a lesser module" this module's
85
+ * `backend/index.ts` records six of — there is no lesser behaviour to
86
+ * produce, only the same number reached without a settings round trip. It is
87
+ * what lets a test construct an indexer without composing the settings store.
88
+ */
89
+ resolveTaskTimeoutMs?: () => Promise<number>;
90
+ }
91
+ /**
92
+ * A document carries **no price**, and the omission is deliberate (issue #287).
93
+ *
94
+ * It used to carry `price: number | null`, copied from the legacy
95
+ * `attributes.defaultPrice` — a catalogue attribute, not any price list's
96
+ * figure. Nothing on the query path read it: `listProducts` never asked for it
97
+ * in `attributesToRetrieve`, and hydrates the viewer's price from
98
+ * `price_lists` through the pricing port precisely so the index cannot decide
99
+ * what a buyer pays. What the field did do was sit in every document, already
100
+ * numeric and already called `price`, waiting for the first "sort by price"
101
+ * implementation to reach for it and quietly order a negotiated-price buyer's
102
+ * listing by a legacy attribute.
103
+ *
104
+ * A per-buyer price cannot live here in any case: there is one index per sales
105
+ * channel and one document per product, so pricing the document would multiply
106
+ * the corpus by the customer base. Feature 086 sorts by price without
107
+ * Meilisearch for that reason. The legacy attribute itself is untouched and
108
+ * still indexed under `attributes.defaultPrice`, where its name says what it is.
109
+ */
110
+ export interface IndexedDocument {
111
+ id: string;
112
+ sku: string;
113
+ name: string;
114
+ description: string;
115
+ type: string;
116
+ status: string;
117
+ visibility: string;
118
+ slug: string;
119
+ primaryAssetUrl: string | null;
120
+ categoryIds: string[];
121
+ categorySlugs: string[];
122
+ attributes: Record<string, string | number | boolean | null>;
123
+ /**
124
+ * Feature 012 / FR-035 — for every `isSearchable = true` attribute of
125
+ * a select-style type (`select` / `enum` / `multiselect`), this array
126
+ * carries the resolved per-locale option label(s). The customer can
127
+ * then search for the rendered text they see (e.g. "brass" matches a
128
+ * product whose `material` option is keyed `brass_001` but rendered
129
+ * as "Brass" in their locale). The array is part of Meilisearch's
130
+ * `searchableAttributes` settings, so it participates in lexical +
131
+ * (when enabled) semantic search alongside `name` / `description`.
132
+ */
133
+ searchableOptions: string[];
134
+ /**
135
+ * Epoch milliseconds. Sortable — it is what `-createdAt` means, on both
136
+ * backends of the listing route (issue #287).
137
+ */
138
+ createdAt: number;
139
+ /** Epoch milliseconds. The freshness marker; no sort token names it. */
140
+ updatedAt: number;
141
+ }
142
+ /**
143
+ * The default wait, in milliseconds. Derived from the manifest so the constant
144
+ * and the Setting's default cannot drift into two answers.
145
+ */
146
+ export declare const DEFAULT_INDEX_TASK_TIMEOUT_MS: number;
147
+ /**
148
+ * A Meilisearch task came back in a state that is not `succeeded`.
149
+ *
150
+ * This error exists because `waitForTask` **does not throw for a task that
151
+ * failed** — it resolves, carrying `status: 'failed'` and Meilisearch's own
152
+ * `error` object. Every one of this file's twelve waits discarded that value,
153
+ * so a refused document batch, a rejected settings update and an applied one
154
+ * were the same thing to the caller: `reindexChannel` went on to report a
155
+ * document count for an index Meilisearch had written nothing into.
156
+ */
157
+ export declare class SearchIndexTaskFailed extends Error {
158
+ readonly taskUid: number;
159
+ readonly what: string;
160
+ readonly status: string;
161
+ /** Meilisearch's own error code, e.g. `missing_document_id`. */
162
+ readonly meilisearchCode: string | undefined;
163
+ constructor(args: {
164
+ taskUid: number;
165
+ what: string;
166
+ status: string;
167
+ meilisearchCode?: string | undefined;
168
+ detail?: string | undefined;
169
+ });
170
+ }
171
+ /**
172
+ * The wait expired. The task is **still running** — Meilisearch was never told
173
+ * to stop, because giving up on a wait cancels nothing.
174
+ *
175
+ * It is a separate error from {@link SearchIndexTaskFailed} on purpose, and the
176
+ * separation is the point of the repair rather than a nicety: the two arrive at
177
+ * an operator with opposite remedies. This one says "the queue outlasted the
178
+ * wait — raise `search.index_task_timeout_seconds`, or let the instance drain";
179
+ * the other says "the write was refused, and here is what Meilisearch said".
180
+ * Under the client's 5000 ms default these were one symptom, and it was the
181
+ * benign one that was raised while the malignant one was silent.
182
+ */
183
+ export declare class SearchIndexTaskStillRunning extends Error {
184
+ readonly taskUid: number;
185
+ readonly what: string;
186
+ readonly timeoutMs: number;
187
+ constructor(args: {
188
+ taskUid: number;
189
+ what: string;
190
+ timeoutMs: number;
191
+ });
192
+ }
193
+ /** The slice of the Meilisearch task client this module waits through. */
194
+ interface TaskWaiter {
195
+ waitForTask(taskUid: number, options: {
196
+ timeout: number;
197
+ }): Promise<{
198
+ status: string;
199
+ error?: {
200
+ message?: string;
201
+ code?: string;
202
+ } | null;
203
+ }>;
204
+ }
205
+ /**
206
+ * Wait for one Meilisearch task and give its three outcomes three names.
207
+ *
208
+ * This is the **only** place this module awaits a task, which is what makes the
209
+ * timeout one number rather than eleven literals, and what makes the
210
+ * failed-status check impossible to forget at the thirteenth call site.
211
+ *
212
+ * `tolerate` names Meilisearch error codes that are an expected, benign outcome
213
+ * for **this** wait, and it is deliberately a per-call argument rather than a
214
+ * blanket leniency: the one caller that needs it is `ensureIndex`, whose
215
+ * `index_already_exists` is another process having created the index first —
216
+ * which satisfies the postcondition rather than violating it.
217
+ */
218
+ export declare function awaitIndexTask(tasks: TaskWaiter, task: {
219
+ taskUid: number;
220
+ }, what: string, timeoutMs: number, tolerate?: readonly string[]): Promise<void>;
221
+ export declare class SearchIndexer {
222
+ private readonly client;
223
+ private readonly locale;
224
+ private readonly attributeRead;
225
+ private readonly products;
226
+ private readonly categories;
227
+ private readonly channelMembership;
228
+ private readonly resolveTaskTimeoutMs;
229
+ constructor(options: SearchIndexerOptions);
230
+ /**
231
+ * Every Meilisearch task this module waits for goes through here — see
232
+ * {@link awaitIndexTask} for why the wait has one home and three outcomes.
233
+ */
234
+ private awaitTask;
235
+ /**
236
+ * Drop + recreate the channel index, reapply the searchable / filterable /
237
+ * sortable settings, then push every product visible in that channel.
238
+ * Returns counts so callers can log a summary.
239
+ */
240
+ reindexChannel(em: EntityManager, channel: SalesChannel): Promise<{
241
+ indexUid: string;
242
+ documentCount: number;
243
+ searchableAttributes: string[];
244
+ filterableAttributes: string[];
245
+ sortableAttributes: string[];
246
+ }>;
247
+ /**
248
+ * Incremental upsert of a single product into every channel index that
249
+ * publishes it. Used by the event-driven subscriber. Performs no index
250
+ * wipe and no setting changes — just a per-channel `addDocuments` (Meili
251
+ * upsert semantics) for the channels the product currently belongs to,
252
+ * plus a `deleteDocument` from any channel index it was removed from.
253
+ *
254
+ * Returns the channel codes that were touched so callers can log a
255
+ * one-line summary.
256
+ */
257
+ upsertProduct(em: EntityManager, productId: string): Promise<string[]>;
258
+ /**
259
+ * Feature 068 — re-index every product assigned to a category or any of its
260
+ * descendants. Called on `category.updated.v1`: a renamed or deactivated
261
+ * category changes the `categorySlugs` projection of its products, and a
262
+ * stale projection keeps a hidden category working as a PLP filter.
263
+ *
264
+ * The subtree walk and the de-duplication both belong to `catalog`, which
265
+ * owns `categories` and `product_categories`; this module used to run the
266
+ * recursive query itself. Returns the number of products re-indexed.
267
+ */
268
+ reindexCategorySubtree(em: EntityManager, categoryId: string): Promise<number>;
269
+ /**
270
+ * Drop a product from every channel index. Called on
271
+ * `product.archived.v1` and on hard delete.
272
+ */
273
+ deleteProduct(em: EntityManager, productId: string): Promise<string[]>;
274
+ /**
275
+ * Re-apply searchable + filterable attribute settings on every channel
276
+ * index, derived from the live `product_attributes` rows. Called on
277
+ * `attribute.updated.v1` so a flipped `isFilterable` / `isSearchable`
278
+ * propagates without a full reindex.
279
+ *
280
+ * It reapplies {@link SORTABLE_ATTRIBUTES} too, which is what lets an index
281
+ * built before issue #287 regain `name` sorting without waiting for a full
282
+ * reindex — the documents already carry the field. `createdAt` does not: a
283
+ * field that was never written has to be reindexed in.
284
+ */
285
+ refreshAttributeSettings(em: EntityManager): Promise<string[]>;
286
+ reindexAllChannels(em: EntityManager): Promise<Array<{
287
+ channelCode: string;
288
+ indexUid: string;
289
+ documentCount: number;
290
+ }>>;
291
+ /**
292
+ * Attach a Meilisearch embedder to a single channel index — feature 006 / T026.
293
+ *
294
+ * Wires Meilisearch's hybrid lexical + semantic search ("AI-augmented
295
+ * search"). Once attached, queries automatically blend keyword and
296
+ * vector scoring; the storefront does not change behaviour beyond
297
+ * ranking. We register the embedder under the well-known name
298
+ * `default` so callers do not have to thread the name through.
299
+ *
300
+ * Uses the `openAi` embedder source — accepts a custom `url`, so it
301
+ * works against any OpenAI-compatible endpoint (OpenAI itself, Azure
302
+ * OpenAI, Ollama's OpenAI shim, etc.). When the operator needs a
303
+ * non-OpenAI-shaped provider, the manifest can later expose
304
+ * `search.llm.embedder_source` with a discriminated union; out of
305
+ * scope for the MVP toggle.
306
+ */
307
+ attachEmbedderForChannel(channelCode: string, config: {
308
+ url: string;
309
+ apiKey: string;
310
+ model: string;
311
+ }): Promise<void>;
312
+ /**
313
+ * Detach the embedder from a single channel index — feature 006 / T026.
314
+ * Reverses {@link attachEmbedderForChannel} so the channel falls back
315
+ * to plain lexical ranking.
316
+ */
317
+ detachEmbedderForChannel(channelCode: string): Promise<void>;
318
+ /**
319
+ * Feature 012 / FR-035 — build a per-call lookup of (attributeKey,
320
+ * value) → resolved per-locale option label, restricted to attributes
321
+ * that are isSearchable AND of a select-style type. Used by
322
+ * buildDocument() to fill the `searchableOptions` array on each
323
+ * indexed document so storefront search hits the customer-visible
324
+ * label, not the raw option value.
325
+ *
326
+ * The lookup is constructed in two queries (attribute set + option
327
+ * set) so the per-product loop stays O(1).
328
+ */
329
+ loadSearchableOptionLookup(_em: EntityManager, locale: string): Promise<Map<string, Map<string, string>>>;
330
+ /** Feature 061 — derive per-index searchable/filterable settings from the view. */
331
+ private attributeSettings;
332
+ private ensureIndex;
333
+ /**
334
+ * Every product id the channel carries, not the first page of them.
335
+ * `listEntityIdsForChannel` is paginated and a full reindex wants the whole
336
+ * assortment, so the pages are walked to `total`. `total` is re-read on each
337
+ * call and the loop is bounded by it, so a concurrent membership write cannot
338
+ * spin it — the shape `seo`'s sitemap generator settled on for this read.
339
+ */
340
+ private channelMemberProductIds;
341
+ }
342
+ export declare function indexUidFor(channel: {
343
+ code: string;
344
+ }): string;
345
+ export {};
346
+ //# sourceMappingURL=search-indexer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-indexer.d.ts","sourceRoot":"","sources":["../../../src/backend/services/search-indexer.ts"],"names":[],"mappings":"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"}