@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,263 @@
1
+ import { type ModuleCliCommand } from '@endora-commerce/contracts';
2
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
3
+ /**
4
+ * Built-in settings manifest for the search module — feature 006.
5
+ *
6
+ * Reserves the `search` group and registers the search knobs
7
+ * (`data-model.md §2`; embedder credentials via feature 058):
8
+ *
9
+ * 1. search.popup.suggestion_count (number 0..50)
10
+ * 2. search.popup.minimum_query_length (number 1..32)
11
+ * 3. search.llm.enabled (boolean)
12
+ * 4. search.llm.embedder_credentials (credential_ref → `llm`: Base URL +
13
+ * API key + model; the single embedder
14
+ * credential source)
15
+ * 5. search.reindex_interval_minutes (number)
16
+ * 6. search.index_task_timeout_seconds (number)
17
+ *
18
+ * The Settings module's value-type registry only recognises a handful of
19
+ * primitives (`string`, `number`, `boolean`, `json`, `string_list`) — range
20
+ * bounds are enforced by the Search module's own LlmToggleService and the
21
+ * suggest endpoint's parser, not by the manifest.
22
+ */
23
+ export declare const SEARCH_SETTING_CODES: {
24
+ readonly POPUP_SUGGESTION_COUNT: "search.popup.suggestion_count";
25
+ readonly POPUP_MINIMUM_QUERY_LENGTH: "search.popup.minimum_query_length";
26
+ readonly LLM_ENABLED: "search.llm.enabled";
27
+ readonly LLM_EMBEDDER_CREDENTIALS: "search.llm.embedder_credentials";
28
+ readonly REINDEX_INTERVAL_MINUTES: "search.reindex_interval_minutes";
29
+ readonly INDEX_TASK_TIMEOUT_SECONDS: "search.index_task_timeout_seconds";
30
+ };
31
+ export declare const DEFAULT_POPUP_SUGGESTION_COUNT = 8;
32
+ export declare const DEFAULT_POPUP_MINIMUM_QUERY_LENGTH = 3;
33
+ export declare const DEFAULT_REINDEX_INTERVAL_MINUTES = 10;
34
+ /**
35
+ * How long the indexer keeps waiting for one Meilisearch task before it gives
36
+ * up and says so — the default of {@link SEARCH_SETTING_CODES.INDEX_TASK_TIMEOUT_SECONDS}.
37
+ *
38
+ * The number this replaces was the `meilisearch` client's own default of 5000
39
+ * ms, taken by omission at nine of the indexer's twelve waits. It is the wrong
40
+ * order of magnitude for the thing being waited on, and the reason is that a
41
+ * task wait is not a measurement of the task: it is
42
+ * `queue ahead of this task` + `this task's own work`, and Meilisearch's task
43
+ * queue is global to the instance. Measured on a developer machine, a
44
+ * one-document write into an empty index expired the 5000 ms wait after 5006 ms
45
+ * with twelve document batches enqueued ahead of it, and then settled
46
+ * `succeeded` 10540 ms later. Nothing about that task was slow.
47
+ *
48
+ * 120 s is therefore sized for the queue rather than for the corpus, and the
49
+ * cost of sizing it generously is close to nil: an expired wait does **not**
50
+ * cancel the Meilisearch task, and a task that genuinely dies comes back
51
+ * `failed` immediately rather than by timing out. So the only thing a short
52
+ * value buys is noticing a task that is stuck forever, and the only thing it
53
+ * costs is aborting a reindex that was going to succeed — in
54
+ * `reindexChannel`'s case between the wipe and the document push, which leaves
55
+ * the channel index empty.
56
+ *
57
+ * An operator whose catalogue or Meilisearch host makes even this too short
58
+ * raises the setting; nothing in this repository can know their corpus size or
59
+ * their index throughput, which is why the number is a Setting and not only
60
+ * this constant.
61
+ */
62
+ export declare const DEFAULT_INDEX_TASK_TIMEOUT_SECONDS = 120;
63
+ /** Module-lifecycle manifest (feature 018). */
64
+ export declare const manifest: {
65
+ id: string;
66
+ name: string;
67
+ version: string;
68
+ dependencies: string[];
69
+ description?: string | undefined;
70
+ acknowledgedDependencies?: {
71
+ moduleId: string;
72
+ port: string;
73
+ reason: string;
74
+ }[] | undefined;
75
+ nonBindingDependencies?: {
76
+ moduleId: string;
77
+ name: string;
78
+ kind: "contributes-to" | "degrades-without" | "refuses-without";
79
+ reason: string;
80
+ whenAbsent?: string | undefined;
81
+ }[] | undefined;
82
+ activation?: {
83
+ settingCode: string;
84
+ default: boolean;
85
+ } | {
86
+ nonDeactivatable: true;
87
+ reason: string;
88
+ } | undefined;
89
+ settings?: {
90
+ moduleCode: string;
91
+ groups: {
92
+ code: string;
93
+ name: string;
94
+ salesChannelCodes?: string[] | undefined;
95
+ isSystemProtected?: boolean | undefined;
96
+ }[];
97
+ settings: {
98
+ code: string;
99
+ name: string;
100
+ valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
101
+ defaultValue: unknown;
102
+ description?: string | undefined;
103
+ groupCode?: string | undefined;
104
+ previousDefaultValues?: unknown[] | undefined;
105
+ salesChannelCodes?: string[] | undefined;
106
+ enumOptions?: string[] | undefined;
107
+ configurationType?: string | undefined;
108
+ hidden?: boolean | undefined;
109
+ }[];
110
+ } | undefined;
111
+ i18n?: {
112
+ bundlesDir: string;
113
+ } | undefined;
114
+ docs?: false | {
115
+ dir: string;
116
+ } | undefined;
117
+ demo?: false | {
118
+ summary: string;
119
+ seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
120
+ reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
121
+ after?: readonly string[] | undefined;
122
+ package?: string | undefined;
123
+ } | undefined;
124
+ actions?: {
125
+ id: string;
126
+ labelKey: string;
127
+ icon: "Search" | "Plus" | "Sparkles" | "Settings" | "Boxes" | "Layers" | "Menu" | "PlusCircle" | "PlusSquare" | "FilePlus" | "FolderPlus" | "Upload" | "FileUp" | "CloudUpload" | "Download" | "FileDown" | "FileText" | "BookOpen" | "Rss" | "Package" | "Tag" | "ShoppingCart" | "Receipt" | "CreditCard" | "Users" | "UserPlus" | "Inbox" | "ListChecks" | "ClipboardList" | "Image" | "Video" | "LayoutDashboard" | "PanelLeft" | "KeyRound" | "ShieldCheck" | "Edit" | "Archive" | "Box" | "Truck" | "CircleDollarSign" | "Activity" | "LineChart" | "Smartphone" | "Webhook" | "Scale" | "PlugZap" | "PercentDiamond" | "Newspaper" | "Languages" | "Eraser" | "Warehouse" | "TrendingDown" | "Bell" | "PackageOpen" | "Building2" | "Store" | "ClipboardCheck";
128
+ targetRoute: string;
129
+ keywords: string[];
130
+ weight: number;
131
+ descriptionKey?: string | undefined;
132
+ requiredPermission?: string | undefined;
133
+ }[] | undefined;
134
+ permissions?: {
135
+ code: string;
136
+ label: string;
137
+ module?: string | undefined;
138
+ description?: string | undefined;
139
+ requires?: string[] | undefined;
140
+ }[] | undefined;
141
+ transactionalEmails?: {
142
+ code: string;
143
+ name: string;
144
+ variables: {
145
+ key: string;
146
+ label: string;
147
+ sampleValue?: string | undefined;
148
+ description?: string | undefined;
149
+ }[];
150
+ description?: string | undefined;
151
+ group?: string | undefined;
152
+ }[] | undefined;
153
+ capabilities?: string[] | undefined;
154
+ exclusiveCapabilities?: {
155
+ key: string;
156
+ errorCode: string;
157
+ }[] | undefined;
158
+ errorCodes?: {
159
+ code: string;
160
+ tokens?: string[] | undefined;
161
+ }[] | undefined;
162
+ blocks?: {
163
+ name: string;
164
+ labelKey: string;
165
+ category: string;
166
+ contexts: ("invoice" | "email" | "cms" | "newsletter")[];
167
+ fields: Record<string, {
168
+ type: "number" | "object" | "uuid" | "select" | "array" | "external" | "text" | "textarea" | "radio" | "richtext";
169
+ label?: string | undefined;
170
+ required?: boolean | undefined;
171
+ options?: {
172
+ label: string;
173
+ value: string | number;
174
+ }[] | undefined;
175
+ refKind?: string | undefined;
176
+ }>;
177
+ descriptionKey?: string | undefined;
178
+ defaultProps?: Record<string, unknown> | undefined;
179
+ responsiveFields?: string[] | undefined;
180
+ previewIcon?: string | undefined;
181
+ weight?: number | undefined;
182
+ }[] | undefined;
183
+ blockCategories?: {
184
+ key: string;
185
+ titleKey: string;
186
+ contexts: ("invoice" | "email" | "cms" | "newsletter")[];
187
+ weight?: number | undefined;
188
+ visible?: boolean | undefined;
189
+ }[] | undefined;
190
+ env?: {
191
+ name: string;
192
+ describes: {
193
+ en: string;
194
+ pl: string;
195
+ };
196
+ requirement: {
197
+ kind: "required";
198
+ } | {
199
+ kind: "requiredWhen";
200
+ input: string;
201
+ equals: string;
202
+ } | {
203
+ kind: "optional";
204
+ without: {
205
+ en: string;
206
+ pl: string;
207
+ };
208
+ };
209
+ secret: boolean;
210
+ generable: boolean;
211
+ owner: {
212
+ kind: "platform";
213
+ } | {
214
+ kind: "application";
215
+ application: "admin" | "backend" | "storefront";
216
+ } | {
217
+ kind: "module";
218
+ moduleId: string;
219
+ };
220
+ consumers: ("admin" | "backend" | "storefront")[];
221
+ addressOf: "admin" | "backend" | "storefront" | null;
222
+ }[] | undefined;
223
+ };
224
+ /** Legacy export retained for backward compatibility. */
225
+ export declare const searchManifest: {
226
+ moduleCode: string;
227
+ groups: {
228
+ code: string;
229
+ name: string;
230
+ salesChannelCodes?: string[] | undefined;
231
+ isSystemProtected?: boolean | undefined;
232
+ }[];
233
+ settings: {
234
+ code: string;
235
+ name: string;
236
+ valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
237
+ defaultValue: unknown;
238
+ description?: string | undefined;
239
+ groupCode?: string | undefined;
240
+ previousDefaultValues?: unknown[] | undefined;
241
+ salesChannelCodes?: string[] | undefined;
242
+ enumOptions?: string[] | undefined;
243
+ configurationType?: string | undefined;
244
+ hidden?: boolean | undefined;
245
+ }[];
246
+ };
247
+ /**
248
+ * The operator command this module declares — feature 080, T042b / D-160.9.
249
+ *
250
+ * It was `scripts/reindex.ts`, which hand-built a second `SearchIndexer` out of
251
+ * five services owned by `catalog` and `custom_fields`. The body is
252
+ * `await import()`ed because this file is imported by the generated manifest
253
+ * index, and through it by every static check script and by
254
+ * `src/db/configured-migrations.ts`; a static import of the indexer would pull a
255
+ * Meilisearch client into all of them.
256
+ *
257
+ * `search` is switchable (`search.enabled` above), so this is also the command
258
+ * that shows what the host does with a switched-off module's declaration: the
259
+ * runner asks `requireModuleEnabled('search')` before it builds a context, and
260
+ * the command does not run.
261
+ */
262
+ export declare const cliCommands: ReadonlyArray<ModuleCliCommand<ModuleContext>>;
263
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,oBAAoB;;;;;;;CAUvB,CAAC;AAEX,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAChD,eAAO,MAAM,kCAAkC,IAAI,CAAC;AACpD,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,kCAAkC,MAAM,CAAC;AA+EtD,+CAA+C;AAC/C,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6InB,CAAC;AAEH,yDAAyD;AACzD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;CAAW,CAAC;AAEvC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,EAAE,aAAa,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAMtE,CAAC"}
@@ -0,0 +1,300 @@
1
+ import { defineModuleManifest, defineModuleSettingsManifest, } from '@endora-commerce/contracts';
2
+ /**
3
+ * Built-in settings manifest for the search module — feature 006.
4
+ *
5
+ * Reserves the `search` group and registers the search knobs
6
+ * (`data-model.md §2`; embedder credentials via feature 058):
7
+ *
8
+ * 1. search.popup.suggestion_count (number 0..50)
9
+ * 2. search.popup.minimum_query_length (number 1..32)
10
+ * 3. search.llm.enabled (boolean)
11
+ * 4. search.llm.embedder_credentials (credential_ref → `llm`: Base URL +
12
+ * API key + model; the single embedder
13
+ * credential source)
14
+ * 5. search.reindex_interval_minutes (number)
15
+ * 6. search.index_task_timeout_seconds (number)
16
+ *
17
+ * The Settings module's value-type registry only recognises a handful of
18
+ * primitives (`string`, `number`, `boolean`, `json`, `string_list`) — range
19
+ * bounds are enforced by the Search module's own LlmToggleService and the
20
+ * suggest endpoint's parser, not by the manifest.
21
+ */
22
+ export const SEARCH_SETTING_CODES = {
23
+ POPUP_SUGGESTION_COUNT: 'search.popup.suggestion_count',
24
+ POPUP_MINIMUM_QUERY_LENGTH: 'search.popup.minimum_query_length',
25
+ LLM_ENABLED: 'search.llm.enabled',
26
+ // Feature 058 — the single embedder credential source: a reusable `llm`
27
+ // credential configuration supplying the embedder URL (Base URL), API key and
28
+ // model.
29
+ LLM_EMBEDDER_CREDENTIALS: 'search.llm.embedder_credentials',
30
+ REINDEX_INTERVAL_MINUTES: 'search.reindex_interval_minutes',
31
+ INDEX_TASK_TIMEOUT_SECONDS: 'search.index_task_timeout_seconds',
32
+ };
33
+ export const DEFAULT_POPUP_SUGGESTION_COUNT = 8;
34
+ export const DEFAULT_POPUP_MINIMUM_QUERY_LENGTH = 3;
35
+ export const DEFAULT_REINDEX_INTERVAL_MINUTES = 10;
36
+ /**
37
+ * How long the indexer keeps waiting for one Meilisearch task before it gives
38
+ * up and says so — the default of {@link SEARCH_SETTING_CODES.INDEX_TASK_TIMEOUT_SECONDS}.
39
+ *
40
+ * The number this replaces was the `meilisearch` client's own default of 5000
41
+ * ms, taken by omission at nine of the indexer's twelve waits. It is the wrong
42
+ * order of magnitude for the thing being waited on, and the reason is that a
43
+ * task wait is not a measurement of the task: it is
44
+ * `queue ahead of this task` + `this task's own work`, and Meilisearch's task
45
+ * queue is global to the instance. Measured on a developer machine, a
46
+ * one-document write into an empty index expired the 5000 ms wait after 5006 ms
47
+ * with twelve document batches enqueued ahead of it, and then settled
48
+ * `succeeded` 10540 ms later. Nothing about that task was slow.
49
+ *
50
+ * 120 s is therefore sized for the queue rather than for the corpus, and the
51
+ * cost of sizing it generously is close to nil: an expired wait does **not**
52
+ * cancel the Meilisearch task, and a task that genuinely dies comes back
53
+ * `failed` immediately rather than by timing out. So the only thing a short
54
+ * value buys is noticing a task that is stuck forever, and the only thing it
55
+ * costs is aborting a reindex that was going to succeed — in
56
+ * `reindexChannel`'s case between the wipe and the document push, which leaves
57
+ * the channel index empty.
58
+ *
59
+ * An operator whose catalogue or Meilisearch host makes even this too short
60
+ * raises the setting; nothing in this repository can know their corpus size or
61
+ * their index throughput, which is why the number is a Setting and not only
62
+ * this constant.
63
+ */
64
+ export const DEFAULT_INDEX_TASK_TIMEOUT_SECONDS = 120;
65
+ const settings = defineModuleSettingsManifest({
66
+ moduleCode: 'search',
67
+ groups: [
68
+ {
69
+ code: 'search',
70
+ name: 'Search',
71
+ },
72
+ ],
73
+ settings: [
74
+ {
75
+ // Feature 073 — the operator's activation control. Platform-wide.
76
+ code: 'search.enabled',
77
+ name: 'Search enabled',
78
+ description: 'Switches product search on or off: the storefront typeahead popup and result feed, the search-phrase analytics ingest, the admin LLM and reindex screens, and the catalogue indexer that keeps Meilisearch in step with product changes. Nothing is dropped — the indexes, the recorded phrases and the LLM configuration stay, and a reindex after switching back restores what changed while it was off.',
79
+ groupCode: 'search',
80
+ valueType: 'boolean',
81
+ defaultValue: true,
82
+ },
83
+ {
84
+ code: SEARCH_SETTING_CODES.POPUP_SUGGESTION_COUNT,
85
+ name: 'Suggestion popup — result count',
86
+ description: 'How many products the storefront shows in the typeahead popup. Set to 0 to suppress the popup; the explicit "Search" action still works.',
87
+ groupCode: 'search',
88
+ valueType: 'number',
89
+ defaultValue: DEFAULT_POPUP_SUGGESTION_COUNT,
90
+ },
91
+ {
92
+ code: SEARCH_SETTING_CODES.POPUP_MINIMUM_QUERY_LENGTH,
93
+ name: 'Suggestion popup — minimum query length',
94
+ description: 'How many characters the customer must type before the typeahead popup is requested. Below the threshold the storefront fires no request and the analytics-record endpoint silently no-ops.',
95
+ groupCode: 'search',
96
+ valueType: 'number',
97
+ defaultValue: DEFAULT_POPUP_MINIMUM_QUERY_LENGTH,
98
+ },
99
+ {
100
+ code: SEARCH_SETTING_CODES.LLM_ENABLED,
101
+ name: 'LLM-augmented search — enabled',
102
+ description: 'When on, Meilisearch runs hybrid lexical + semantic search using the configured embedder. Enabling this requires the three embedder.* values below to be non-empty.',
103
+ groupCode: 'search',
104
+ valueType: 'boolean',
105
+ defaultValue: false,
106
+ },
107
+ {
108
+ code: SEARCH_SETTING_CODES.LLM_EMBEDDER_CREDENTIALS,
109
+ name: 'LLM-augmented search — embedder credentials',
110
+ description: 'Reference a reusable LLM credential configuration (Credentials screen). Its Base URL, API Key and Model provide the embedder endpoint, credential and model. Required to enable LLM-augmented search.',
111
+ groupCode: 'search',
112
+ valueType: 'credential_ref',
113
+ configurationType: 'llm',
114
+ defaultValue: '',
115
+ },
116
+ {
117
+ code: SEARCH_SETTING_CODES.REINDEX_INTERVAL_MINUTES,
118
+ name: 'Product reindex interval (minutes)',
119
+ description: 'How often the background worker rebuilds every sales-channel Meilisearch index so the catalogue stays in sync. Set to 0 to disable the periodic sweep — manual reindexing from the admin remains available.',
120
+ groupCode: 'search',
121
+ valueType: 'number',
122
+ defaultValue: DEFAULT_REINDEX_INTERVAL_MINUTES,
123
+ },
124
+ {
125
+ code: SEARCH_SETTING_CODES.INDEX_TASK_TIMEOUT_SECONDS,
126
+ name: 'Index task timeout (seconds)',
127
+ description: 'How long to wait for one Meilisearch indexing task before reporting that it is still running. Raise it if reindexing a large catalogue, or a Meilisearch instance shared with other work, reports that a task is still running: the wait covers the instance-wide task queue as well as the work itself, and giving up early does not cancel the task. Lower it only to be told sooner that indexing has stopped moving.',
128
+ groupCode: 'search',
129
+ valueType: 'number',
130
+ defaultValue: DEFAULT_INDEX_TASK_TIMEOUT_SECONDS,
131
+ },
132
+ ],
133
+ });
134
+ /** Module-lifecycle manifest (feature 018). */
135
+ export const manifest = defineModuleManifest({
136
+ id: 'search',
137
+ name: 'Search',
138
+ description: 'Per-channel Meilisearch indexes, suggest popup, and optional LLM-augmented search.',
139
+ version: '1.0.0',
140
+ /**
141
+ * What this module needs from the environment (`specs/117-instance-bring-up/`
142
+ * FR-002). Only what it **owns**: its reads of platform-owned names are
143
+ * satisfied by `packages/platform/src/env/index.ts`.
144
+ *
145
+ * Why each of these is not a Setting is its entry in
146
+ * `backend/scripts/ledgers/module-environment-inputs/search.ts`.
147
+ *
148
+ * `MEILISEARCH_URL` is **not** here, and its absence is the rule working
149
+ * rather than an omission. D-229 moved the liveness probe into the platform,
150
+ * and the probe reads that address — so the platform declares it, and
151
+ * `module-declares-a-platform-input` refuses a second description of one
152
+ * variable (D-100). This module still reads it; a read resolves against the
153
+ * platform's declaration as well as its own.
154
+ */
155
+ env: [
156
+ {
157
+ name: 'MEILISEARCH_API_KEY',
158
+ describes: {
159
+ en: 'The key this instance presents to Meilisearch.',
160
+ pl: 'Klucz, którym ta instancja uwierzytelnia się w Meilisearch.',
161
+ },
162
+ requirement: {
163
+ kind: 'optional',
164
+ without: {
165
+ en: 'Meilisearch is addressed with no key, which works only against a server that has none — and such a server can be read and rewritten by anything that can reach it.',
166
+ pl: 'Meilisearch jest odpytywany bez klucza, co działa tylko wobec serwera, który go nie ma — a taki serwer może odczytać i nadpisać wszystko, co zdoła się z nim połączyć.',
167
+ },
168
+ },
169
+ secret: true,
170
+ generable: false,
171
+ owner: { kind: 'module', moduleId: 'search' },
172
+ consumers: ['backend'],
173
+ addressOf: null,
174
+ },
175
+ ],
176
+ // Feature 075, Phase C — `organizations` joins the five that were already
177
+ // here. The typeahead's price resolution needs the buyer's organisation, and
178
+ // this module used to read that row with `em.findOne(Organization, …)`; it
179
+ // now resolves `organizationDetailsPort`, and an undeclared port edge is a
180
+ // build failure rather than a silent one.
181
+ dependencies: [
182
+ 'catalog',
183
+ 'credentials',
184
+ 'organizations',
185
+ 'price_lists',
186
+ 'sales_channels',
187
+ 'settings',
188
+ ],
189
+ settings,
190
+ activation: { settingCode: 'search.enabled', default: true },
191
+ /**
192
+ * Feature 090, Phase 3 — the error codes this module owns.
193
+ *
194
+ * The list is the incumbent prefix chain's *answer* for `search`, copied from
195
+ * the frozen capture at `backend/test/fixtures/error-code-routing/chain-answers.ts`
196
+ * (`grep -oE "^ [A-Z0-9_]+: 'search'," …`). It is a transcription, not a
197
+ * judgement: the migration is answer-preserving over all 289 codes and
198
+ * re-routing is out of scope (`specs/090-module-owned-error-codes/` §6.2, §6.5).
199
+ *
200
+ * **Trap T1 does not bite here, and that was measured rather than assumed.**
201
+ * The chain answers `search` from three prefixes — `QUERY_`, `SEARCH_`,
202
+ * `PHRASE_` — plus a three-member `SEARCH_MISC_ERROR_CODES` set. Every code in
203
+ * the enumeration carrying one of those prefixes routes here (five of them),
204
+ * no earlier rule shadows any of them, and all three misc members survive to
205
+ * this rule. So reading the chain's source would have given the same eight as
206
+ * reading its answer. `inventory` and `assets_library` are where it does bite;
207
+ * this module is the case where the two agree, which is worth recording so the
208
+ * next reader knows the question was asked.
209
+ *
210
+ * **Six of the eight are codes a reader would attribute to `core`.**
211
+ * `LIMIT_OUT_OF_RANGE`, `PHRASE_REQUIRED`, `PHRASE_TOO_LONG`, `QUERY_TOO_LONG`,
212
+ * `QUERY_TOO_SHORT` and `RESULT_COUNT_INVALID` carry no `SEARCH_` prefix and
213
+ * read as generic request-validation refusals; only `SEARCH_BACKEND_UNAVAILABLE`
214
+ * and `LLM_CONFIG_INCOMPLETE` name this module's domain. They belong here
215
+ * because `routes.public.ts` hand-parses `?q=&limit=` precisely to avoid the
216
+ * generic `VALIDATION_FAILED` — its own comment says so — and nothing else in
217
+ * `packages`, `backend/src`, `admin/src` or `storefront/src` names any of the
218
+ * six. Do not read the plain names as a routing accident; they are this
219
+ * module's refusals wearing generic clothes.
220
+ *
221
+ * **The inverse is the larger half: this package raises three codes it does
222
+ * not own, and one of the three is recorded nowhere else.** `routes.public.ts`
223
+ * raises `sales_channels`' `MISSING_SALES_CHANNEL_CONTEXT` (which
224
+ * `sales_channels` recorded in !1128), `llm-toggle.service.ts` raises
225
+ * `settings`' `SETTING_OUT_OF_SCOPE_FOR_CHANNEL` (which `settings` recorded in
226
+ * !1133), and `search-query.service.ts` raises `catalog`'s
227
+ * `FILTER_NOT_ALLOWED` when a requested facet is not a filterable attribute —
228
+ * `catalog` declared that code in !1117 and named no thrower, so this is the
229
+ * first record of it. All three are the D-95.2 rule seen from the thrower's
230
+ * side: routing follows the domain noun, and a channel, a setting scope and an
231
+ * attribute's filterability are not search nouns. None is declared here.
232
+ *
233
+ * **No code is raised by nothing.** All eight have a live raise site. This
234
+ * package throws `HttpError` ten times; seven of the ten raise one of these
235
+ * eight (six in `routes.public.ts`, one in `llm-toggle.service.ts`) and the
236
+ * other three are the foreign codes above — so it contributes nothing to the
237
+ * register entry that splits unraised codes by kind, in any of its five. Three
238
+ * of the eight reach the `throw` through a computed local rather than at the
239
+ * call: `routes.public.ts` picks `PHRASE_TOO_LONG` / `RESULT_COUNT_INVALID` /
240
+ * `PHRASE_REQUIRED` into a `const code` from the first Zod issue and throws
241
+ * that, and all three branches are reachable under
242
+ * `RecordPhraseRequestSchema`. The route carrying them is mounted
243
+ * unconditionally by `plugin.ts`, so the reachability question the register's
244
+ * fifth kind asks — is there an HTTP door — answers yes for every one.
245
+ *
246
+ * Both spellings were searched, which trap T12 asks for: `ERROR_CODES.<CODE>`
247
+ * and the bare quoted literal, over `packages`, `backend/src`, `admin/src` and
248
+ * `storefront/src`. Outside the enumeration in `@endora-commerce/contracts`,
249
+ * the chain's own `SEARCH_MISC_ERROR_CODES` set and this declaration, every
250
+ * occurrence is one of those seven raise sites.
251
+ *
252
+ * No `tokens`, derived rather than assumed. `refusalToken`
253
+ * (`packages/platform/src/http/error-envelope.ts`) reads `details.code` and
254
+ * nothing else; all seven raise sites were read, and the six that pass a
255
+ * fourth argument all pass the Zod-style `Array<{ path, issue }>`, which
256
+ * `refusalToken` returns `null` for by construction. The runbook's §5
257
+ * raise-site scan attributes the tree's ten token-carrying codes over 41 sites
258
+ * to `core`, `invoices` and `carts` and names none of these, and in the other
259
+ * direction the module's own bundles hold eight `errors.<CODE>` sentences and
260
+ * no `errors.<CODE>.<token>` key.
261
+ */
262
+ errorCodes: [
263
+ { code: 'LIMIT_OUT_OF_RANGE' },
264
+ { code: 'LLM_CONFIG_INCOMPLETE' },
265
+ { code: 'PHRASE_REQUIRED' },
266
+ { code: 'PHRASE_TOO_LONG' },
267
+ { code: 'QUERY_TOO_LONG' },
268
+ { code: 'QUERY_TOO_SHORT' },
269
+ { code: 'RESULT_COUNT_INVALID' },
270
+ { code: 'SEARCH_BACKEND_UNAVAILABLE' },
271
+ ],
272
+ i18n: { bundlesDir: 'i18n' },
273
+ docs: { dir: 'docs' },
274
+ permissions: [{ code: 'search:write', label: 'Configure search (LLM / indexing)' }],
275
+ });
276
+ /** Legacy export retained for backward compatibility. */
277
+ export const searchManifest = settings;
278
+ /**
279
+ * The operator command this module declares — feature 080, T042b / D-160.9.
280
+ *
281
+ * It was `scripts/reindex.ts`, which hand-built a second `SearchIndexer` out of
282
+ * five services owned by `catalog` and `custom_fields`. The body is
283
+ * `await import()`ed because this file is imported by the generated manifest
284
+ * index, and through it by every static check script and by
285
+ * `src/db/configured-migrations.ts`; a static import of the indexer would pull a
286
+ * Meilisearch client into all of them.
287
+ *
288
+ * `search` is switchable (`search.enabled` above), so this is also the command
289
+ * that shows what the host does with a switched-off module's declaration: the
290
+ * runner asks `requireModuleEnabled('search')` before it builds a context, and
291
+ * the command does not run.
292
+ */
293
+ export const cliCommands = [
294
+ {
295
+ name: 'reindex',
296
+ summary: "Rebuild every sales channel's Meilisearch index from PostgreSQL.",
297
+ run: async (context) => (await import('./backend/cli/reindex.js')).reindex(context),
298
+ },
299
+ ];
300
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * Search phrase records — feature 006 / T034 / data-model.md §1.1.
4
+ *
5
+ * Single-table migration owned by the search module. The table is
6
+ * append-only; analytics aggregations slice it by sales_channel +
7
+ * normalized phrase + time window.
8
+ */
9
+ export declare class Migration20260501T123145SearchPhraseRecordsInit extends Migration {
10
+ up(): Promise<void>;
11
+ down(): Promise<void>;
12
+ }
13
+ //# sourceMappingURL=20260501T123145_search_phrase_records_init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260501T123145_search_phrase_records_init.d.ts","sourceRoot":"","sources":["../../src/migrations/20260501T123145_search_phrase_records_init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;GAMG;AACH,qBAAa,+CAAgD,SAAQ,SAAS;IAC7D,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IA6BnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrC"}
@@ -0,0 +1,40 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * Search phrase records — feature 006 / T034 / data-model.md §1.1.
4
+ *
5
+ * Single-table migration owned by the search module. The table is
6
+ * append-only; analytics aggregations slice it by sales_channel +
7
+ * normalized phrase + time window.
8
+ */
9
+ export class Migration20260501T123145SearchPhraseRecordsInit extends Migration {
10
+ async up() {
11
+ this.addSql(`
12
+ create table "search_phrase_records" (
13
+ "id" uuid not null,
14
+ "phrase" varchar(512) not null,
15
+ "phrase_normalized" varchar(512) not null,
16
+ "sales_channel_id" uuid not null,
17
+ "result_count" integer not null,
18
+ "recorded_at" timestamptz not null default now(),
19
+ constraint "search_phrase_records_pkey" primary key ("id"),
20
+ constraint "search_phrase_records_sales_channel_fk"
21
+ foreign key ("sales_channel_id") references "sales_channels" ("id")
22
+ on delete restrict
23
+ );
24
+ `);
25
+ // Aggregation query: phrases by channel and frequency over a window.
26
+ this.addSql(`
27
+ create index "idx_search_phrase_records_aggr"
28
+ on "search_phrase_records" ("sales_channel_id", "phrase_normalized", "recorded_at" desc);
29
+ `);
30
+ // Ops dashboards / "most-recent searches".
31
+ this.addSql(`
32
+ create index "idx_search_phrase_records_recent"
33
+ on "search_phrase_records" ("recorded_at" desc);
34
+ `);
35
+ }
36
+ async down() {
37
+ this.addSql('drop table if exists "search_phrase_records" cascade;');
38
+ }
39
+ }
40
+ //# sourceMappingURL=20260501T123145_search_phrase_records_init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260501T123145_search_phrase_records_init.js","sourceRoot":"","sources":["../../src/migrations/20260501T123145_search_phrase_records_init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,OAAO,+CAAgD,SAAQ,SAAS;IACnE,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;KAaX,CAAC,CAAC;QAEH,qEAAqE;QACrE,IAAI,CAAC,MAAM,CAAC;;;KAGX,CAAC,CAAC;QAEH,2CAA2C;QAC3C,IAAI,CAAC,MAAM,CAAC;;;KAGX,CAAC,CAAC;IACL,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CAAC,uDAAuD,CAAC,CAAC;IACvE,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The `./migrations` subpath — every migration class this module owns, as one
3
+ * ordered `migrations` array.
4
+ *
5
+ * The array is what the platform reads when this module is **installed**:
6
+ * `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
7
+ * the package outright when it is absent (D-168).
8
+ *
9
+ * Listed in ascending timestamp, which is the order of this module's own
10
+ * migrations and of nothing else (feature 081): a manifest `dependencies` array
11
+ * is the only thing ordering this block against another module's.
12
+ *
13
+ * The **named** exports stay beside the array, and the asymmetry with
14
+ * `./backend` — which publishes an array and no named class (D-168) — is
15
+ * deliberate. `db/migrations-registry.generated.ts` imports each class by name
16
+ * from this specifier, and a migration class name is contract in a way an entity
17
+ * class name is not: `mikro_orm_migrations` persists it, so it is a string every
18
+ * already-migrated database holds.
19
+ *
20
+ * A class that is in neither the array nor the barrel is a migration that does
21
+ * not run: `migration:pending` reports nothing pending and the first symptom is
22
+ * a query against a table nobody created.
23
+ */
24
+ import { Migration20260501T123145SearchPhraseRecordsInit } from './20260501T123145_search_phrase_records_init.js';
25
+ export declare const migrations: (typeof Migration20260501T123145SearchPhraseRecordsInit)[];
26
+ export { Migration20260501T123145SearchPhraseRecordsInit, };
27
+ //# sourceMappingURL=index.d.ts.map