@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.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/backend/cli/reindex.d.ts +4 -0
- package/dist/backend/cli/reindex.d.ts.map +1 -0
- package/dist/backend/cli/reindex.js +32 -0
- package/dist/backend/cli/reindex.js.map +1 -0
- package/dist/backend/entities/search-phrase-record.entity.d.ts +40 -0
- package/dist/backend/entities/search-phrase-record.entity.d.ts.map +1 -0
- package/dist/backend/entities/search-phrase-record.entity.js +85 -0
- package/dist/backend/entities/search-phrase-record.entity.js.map +1 -0
- package/dist/backend/index.d.ts +87 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +153 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +133 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +250 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +28 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +33 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.public.d.ts +22 -0
- package/dist/backend/routes.public.d.ts.map +1 -0
- package/dist/backend/routes.public.js +192 -0
- package/dist/backend/routes.public.js.map +1 -0
- package/dist/backend/services/embedder-config-resolver.d.ts +25 -0
- package/dist/backend/services/embedder-config-resolver.d.ts.map +1 -0
- package/dist/backend/services/embedder-config-resolver.js +47 -0
- package/dist/backend/services/embedder-config-resolver.js.map +1 -0
- package/dist/backend/services/llm-toggle.service.d.ts +50 -0
- package/dist/backend/services/llm-toggle.service.d.ts.map +1 -0
- package/dist/backend/services/llm-toggle.service.js +78 -0
- package/dist/backend/services/llm-toggle.service.js.map +1 -0
- package/dist/backend/services/search-event-subscriber.d.ts +125 -0
- package/dist/backend/services/search-event-subscriber.d.ts.map +1 -0
- package/dist/backend/services/search-event-subscriber.js +113 -0
- package/dist/backend/services/search-event-subscriber.js.map +1 -0
- package/dist/backend/services/search-indexer.d.ts +346 -0
- package/dist/backend/services/search-indexer.d.ts.map +1 -0
- package/dist/backend/services/search-indexer.js +670 -0
- package/dist/backend/services/search-indexer.js.map +1 -0
- package/dist/backend/services/search-phrase-recorder.service.d.ts +25 -0
- package/dist/backend/services/search-phrase-recorder.service.d.ts.map +1 -0
- package/dist/backend/services/search-phrase-recorder.service.js +89 -0
- package/dist/backend/services/search-phrase-recorder.service.js.map +1 -0
- package/dist/backend/services/search-query-port.d.ts +29 -0
- package/dist/backend/services/search-query-port.d.ts.map +1 -0
- package/dist/backend/services/search-query-port.js +20 -0
- package/dist/backend/services/search-query-port.js.map +1 -0
- package/dist/backend/services/search-query.service.d.ts +205 -0
- package/dist/backend/services/search-query.service.d.ts.map +1 -0
- package/dist/backend/services/search-query.service.js +346 -0
- package/dist/backend/services/search-query.service.js.map +1 -0
- package/dist/backend/services/search-reindex-worker.d.ts +27 -0
- package/dist/backend/services/search-reindex-worker.d.ts.map +1 -0
- package/dist/backend/services/search-reindex-worker.js +13 -0
- package/dist/backend/services/search-reindex-worker.js.map +1 -0
- package/dist/backend/services/search-suggest.service.d.ts +53 -0
- package/dist/backend/services/search-suggest.service.d.ts.map +1 -0
- package/dist/backend/services/search-suggest.service.js +64 -0
- package/dist/backend/services/search-suggest.service.js.map +1 -0
- package/dist/backend/services/suggestion-pricing-enricher.d.ts +62 -0
- package/dist/backend/services/suggestion-pricing-enricher.d.ts.map +1 -0
- package/dist/backend/services/suggestion-pricing-enricher.js +41 -0
- package/dist/backend/services/suggestion-pricing-enricher.js.map +1 -0
- package/dist/manifest.d.ts +263 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +300 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260501T123145_search_phrase_records_init.d.ts +13 -0
- package/dist/migrations/20260501T123145_search_phrase_records_init.d.ts.map +1 -0
- package/dist/migrations/20260501T123145_search_phrase_records_init.js +40 -0
- package/dist/migrations/20260501T123145_search_phrase_records_init.js.map +1 -0
- package/dist/migrations/index.d.ts +27 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +29 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/search.md +284 -0
- package/i18n/en.json +10 -0
- package/i18n/pl.json +10 -0
- 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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"}
|