@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,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"}
|
package/dist/manifest.js
ADDED
|
@@ -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
|