@nuskin/react-search-sdk 1.0.0-comx-2233.1

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/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @nuskin/react-search-sdk
2
+
3
+ Business logic, hooks, and API services for the NuSkin search experience.
4
+
5
+ - **Phase 0 (in progress)** — Lift-and-shift of the existing Equinox typeahead search from `header-mfe` into a reusable SDK.
6
+ - **Phase 1** — AI-enabled conversational search (NuI chat + product recommendations). See `react-search-components/docs/AI-Search-Design-Document.md`.
7
+
8
+ This package is a pure TypeScript library (no JSX, no framework coupling). UI lives in [`@nuskin/react-search-components`](../react-search-components).
9
+
10
+ ## Requirements
11
+
12
+ - Node.js `>=22.0.0 <23.0.0`
13
+ - React `^18.2.0` (peer)
14
+
15
+ ## Scripts
16
+
17
+ | Script | Purpose |
18
+ | ------------------ | ---------------------------------------------------- |
19
+ | `yarn build` | Build CJS + ESM + `.d.ts` bundle via tsup. |
20
+ | `yarn test` | Run Jest with coverage (80% threshold). |
21
+ | `yarn types:check` | Type-check without emitting. |
22
+ | `yarn lint` | ESLint on `src/**/*.{ts,tsx}`. |
23
+ | `yarn validate` | Full pipeline: format → lint → types → test → build. |
24
+
25
+ ## Public API
26
+
27
+ See [`src/index.ts`](src/index.ts). The API surface grows as each Phase 0 module lands.
@@ -0,0 +1,513 @@
1
+ import React from 'react';
2
+ import * as zustand_middleware from 'zustand/middleware';
3
+ import * as zustand from 'zustand';
4
+
5
+ /**
6
+ * Shared TypeScript types for the search SDK.
7
+ *
8
+ * Kept intentionally permissive at the boundary (`unknown`, `[key: string]: unknown`)
9
+ * to preserve the drop-in behavior of the ported JavaScript service. Downstream
10
+ * consumers can narrow at their integration point.
11
+ */
12
+
13
+ /** Normalised locale parts as consumed by the SDK. */
14
+ interface LocaleParts {
15
+ /** ISO 3166-1 alpha-2, upper-case. Example: `"US"`. */
16
+ country: string;
17
+ /** ISO 639-1, lower-case. Example: `"en"`. */
18
+ language: string;
19
+ /** Full Contentstack-style locale string. Example: `"US-en"`. */
20
+ locale: string;
21
+ }
22
+ /**
23
+ * Shape returned by the market-config lookup. Only the fields the SDK reads
24
+ * are typed; consumers may pass through additional properties.
25
+ */
26
+ interface MarketConfig {
27
+ api_gateway_path?: string;
28
+ store_id?: string | number;
29
+ [key: string]: unknown;
30
+ }
31
+ /** Callback the consumer wires in to resolve market config on demand. */
32
+ type GetMarketConfig = (context: {
33
+ locationLike: LocationLike | null;
34
+ country: string;
35
+ }) => Promise<MarketConfig | null | undefined>;
36
+ /**
37
+ * The subset of the DOM `Location` interface the SDK depends on. Every
38
+ * property is optional so tests can pass minimal fakes.
39
+ */
40
+ interface LocationLike {
41
+ origin?: string;
42
+ hostname?: string;
43
+ pathname?: string;
44
+ search?: string;
45
+ href?: string;
46
+ assign?: (url: string) => void;
47
+ }
48
+ /**
49
+ * A raw suggestion / option payload used for navigation. Fields mirror the
50
+ * shapes the original service defensively read from — none are required.
51
+ */
52
+ interface SuggestionOption {
53
+ slug?: string | undefined;
54
+ productSlug?: string | undefined;
55
+ product_slug?: string | undefined;
56
+ href?: string | undefined;
57
+ url?: string | undefined;
58
+ value?: string | undefined;
59
+ label?: string | undefined;
60
+ identifier?: string | undefined;
61
+ [key: string]: unknown;
62
+ }
63
+ /** Response shape returned by `fetchSuggestions`. */
64
+ interface SuggestionsResponse {
65
+ /** Suggestions array. May be empty when the API returns no matches. */
66
+ suggestions: unknown[];
67
+ /** "Did you mean?" alternate-query suggestions. */
68
+ didYouMean: unknown[];
69
+ /** Total match count reported by the API. */
70
+ totalResultsCount: number;
71
+ /** Original product array from the catalog API. */
72
+ product?: unknown;
73
+ /** Pageable metadata from the catalog API. */
74
+ pageableInfo?: {
75
+ totalCount?: number;
76
+ } & Record<string, unknown>;
77
+ /** Original snake_case field name from some API variants. */
78
+ did_you_mean?: unknown;
79
+ [key: string]: unknown;
80
+ }
81
+ /** Copy strings surfaced to the input chrome. */
82
+ interface SearchChromeCopy {
83
+ placeholder: string;
84
+ inputAriaLabel: string;
85
+ }
86
+ /** Mapped product type after normalisation. */
87
+ type ProductType = 'bundle' | 'single';
88
+ /**
89
+ * A catalog suggestion item produced by `mapCatalogProductToSuggestion`.
90
+ * All navigation fields are preserved from the original service shape.
91
+ */
92
+ interface CatalogSuggestionItem {
93
+ id: string;
94
+ identifier: string;
95
+ sku: string;
96
+ label: string;
97
+ value: string;
98
+ imageURL: string;
99
+ thumbnail: string;
100
+ productType: ProductType;
101
+ product_type: ProductType;
102
+ outOfStock: boolean;
103
+ productStatus: string | undefined;
104
+ status: string | undefined;
105
+ cartActionDisabled: boolean;
106
+ customProductType: string;
107
+ slug: string | null;
108
+ productSlug: string | null;
109
+ url: string | null;
110
+ href: string | null;
111
+ keywords: string;
112
+ ingredientTerms: string[];
113
+ }
114
+ /**
115
+ * A "Did You Mean?" item produced by `normalizeDidYouMean`. Additional
116
+ * fields from the raw API response are preserved via index signature.
117
+ */
118
+ interface DidYouMeanItem {
119
+ id: string;
120
+ value: string;
121
+ suggestionType: 'did-you-mean';
122
+ [key: string]: unknown;
123
+ }
124
+ /** Options accepted by `useSearchSuggestions`. */
125
+ interface UseSearchSuggestionsOptions {
126
+ /** The `SearchService` instance created by `createSearchService`. */
127
+ service: SearchService;
128
+ /**
129
+ * When `false` the hook clears state and skips all API calls.
130
+ * Useful for suppressing suggestions while the user is typing a question.
131
+ * Defaults to `true`.
132
+ */
133
+ enabled?: boolean;
134
+ }
135
+ /** State and actions returned by `useSearchSuggestions`. */
136
+ interface UseSearchSuggestionsResult {
137
+ /**
138
+ * Resets the query to `""` and clears all suggestions state.
139
+ * Call when the user clicks the Clear button.
140
+ */
141
+ clearQuery: () => void;
142
+ /**
143
+ * `true` after a fetch completes with an empty result set.
144
+ * Use this to show a "No suggestions" message.
145
+ */
146
+ emptyFetched: boolean;
147
+ /** `true` when the server has more matches than `MAX_VISIBLE_SUGGESTIONS`. */
148
+ hasMoreResults: boolean;
149
+ /** `true` when the trimmed query is non-empty. */
150
+ hasQuery: boolean;
151
+ /** Normalised "Did You Mean?" items from the API. */
152
+ didYouMean: DidYouMeanItem[];
153
+ /** `true` while a debounced fetch is in flight. */
154
+ loading: boolean;
155
+ /** The current query string (unmodified, as typed by the user). */
156
+ query: string;
157
+ /** Raw React state setter — prefer `updateQuery` for controlled inputs. */
158
+ setQuery: React.Dispatch<React.SetStateAction<string>>;
159
+ /** Normalised product suggestion items from the catalog API. */
160
+ suggestions: CatalogSuggestionItem[];
161
+ /** Total match count reported by the Equinox API. */
162
+ totalResultsCount: number;
163
+ /**
164
+ * Preferred setter — updates `query`, optimistically sets `loading`, and
165
+ * clears `emptyFetched` before the debounce fires.
166
+ */
167
+ updateQuery: (query: string) => void;
168
+ }
169
+ /** Options accepted by `createSearchService`. */
170
+ interface SearchServiceOptions {
171
+ /** Custom `fetch` implementation. Defaults to the global `fetch` when available. */
172
+ fetchImpl?: typeof fetch | null | undefined;
173
+ /** DOM `Location`-like object. Defaults to `window.location` in the browser. */
174
+ location?: LocationLike | null | undefined;
175
+ /** Storage-like object. Defaults to `window.sessionStorage` in the browser. */
176
+ storage?: Storage | null | undefined;
177
+ /**
178
+ * Explicit suggestions URL or path. When provided, overrides the market-config
179
+ * lookup. Absolute URLs are used as-is; paths are resolved against
180
+ * `location.origin`.
181
+ */
182
+ suggestionsApi?: string | undefined;
183
+ /** Explicit Equinox store ID. When provided, overrides the market-config lookup. */
184
+ storeId?: string | number | undefined;
185
+ /** ISO 3166-1 alpha-2 country code. Normalised to upper-case. */
186
+ country?: string | undefined;
187
+ /** ISO 639-1 language code. Normalised to lower-case. */
188
+ language?: string | undefined;
189
+ /** Full Contentstack-style locale string. Falls back to `"UNKNOWN"`. */
190
+ locale?: string | undefined;
191
+ /** Placeholder text for the expanded input. */
192
+ searchPlaceholder?: string | undefined;
193
+ /** Aria-label for the expanded input. */
194
+ inputAriaLabel?: string | undefined;
195
+ /**
196
+ * Async callback resolving to a `MarketConfig`. Required for automatic
197
+ * suggestion-URL/storeId resolution when `suggestionsApi`/`storeId` are
198
+ * not passed explicitly.
199
+ */
200
+ getMarketConfig?: GetMarketConfig | undefined;
201
+ /**
202
+ * Optional callback invoked when a suggestion action (cart icon click) is activated.
203
+ * Allows consumers to implement custom behavior (e.g., add-to-cart) before navigation.
204
+ * If this returns `true`, navigation is skipped; if `false` or undefined, normal navigation proceeds.
205
+ */
206
+ onSuggestionAction?: (option: SuggestionOption) => Promise<boolean> | boolean | undefined;
207
+ }
208
+ /** Public API returned by `createSearchService`. Behaviour matches header-mfe. */
209
+ interface SearchService {
210
+ /** Returns fallback placeholder + aria-label copy. */
211
+ getSearchChromeCopy: () => Promise<SearchChromeCopy>;
212
+ /** Fetches typeahead suggestions from the Equinox catalog. */
213
+ fetchSuggestions: (searchTerm: string) => Promise<SuggestionsResponse>;
214
+ /**
215
+ * Records the query in recent searches, then navigates to the option URL
216
+ * or to the fallback search-results path. Returns the URL that was used.
217
+ */
218
+ submitProductSearch: (query: string, option?: SuggestionOption) => string | undefined;
219
+ /**
220
+ * Follows the suggestion's URL if one is resolvable; otherwise submits
221
+ * the suggestion's textual value as a product search.
222
+ */
223
+ activateSuggestionAction: (option: SuggestionOption) => string | undefined;
224
+ /** Returns the navigable URL for a suggestion option (or `""`). */
225
+ getSuggestionHref: (option: SuggestionOption) => string;
226
+ /** Navigates to the search-results page for `query`. */
227
+ viewMoreResults: (input: {
228
+ query: string;
229
+ }) => string | undefined;
230
+ /** Returns the persisted recent searches for the current locale bucket. */
231
+ getRecentSearches: () => string[];
232
+ /** Removes all persisted recent searches for the current locale bucket. */
233
+ clearRecentSearches: () => void;
234
+ }
235
+
236
+ /**
237
+ * SearchApiService — Phase 0 lift-and-shift of
238
+ * `header-mfe/src/services/HeaderSearchService.js`.
239
+ *
240
+ * Behaviour is intentionally preserved verbatim: same URL formats, same
241
+ * `sessionStorage` key format, same fetch parameter shape, same fallback
242
+ * values. See the ported source for context.
243
+ *
244
+ * Two responsibilities were intentionally moved OUT of the SDK to preserve
245
+ * the SDK-narrow boundary:
246
+ * 1. Market-config resolution (`equinoxMarketsConfig.js` — depends on
247
+ * `@nuskin/configuration-sdk`). Consumers inject a `getMarketConfig`
248
+ * callback if they want automatic URL/store-id resolution.
249
+ * 2. Locale resolution from the browser URL (`getLocale`). Consumers must
250
+ * pass `country`/`language` explicitly.
251
+ */
252
+
253
+ /**
254
+ * Replaces `{api_name}` in a market-config gateway path with the given API
255
+ * name, then trims a single trailing slash. Ported from the equivalent helper
256
+ * in `equinoxMarketsConfig.js` so the SDK has no runtime dependency on it.
257
+ */
258
+ declare function resolveApiGatewayPath(apiGatewayPath: string | undefined | null, apiName?: string): string;
259
+ /**
260
+ * Creates a search service instance. All options are optional; sensible
261
+ * fallbacks are used when the browser environment is absent (SSR).
262
+ */
263
+ declare function createSearchService(options?: SearchServiceOptions): SearchService;
264
+
265
+ /**
266
+ * ChatService — two-step NuSkin AI chat API.
267
+ *
268
+ * Step 1: POST /chat/conversations → receives conversation_id
269
+ * Step 2: POST /chat/messages → SSE stream parsed with eventsource-parser
270
+ *
271
+ * Products are embedded in AI response content as ```products [...] ``` code blocks.
272
+ */
273
+ interface ChatProduct {
274
+ sku: string;
275
+ name: string;
276
+ category?: string;
277
+ pv?: number;
278
+ retail_price?: number;
279
+ description?: string;
280
+ image_url?: string;
281
+ badge?: string;
282
+ [key: string]: unknown;
283
+ }
284
+ interface ChatMessageResult {
285
+ summary: string;
286
+ products: ChatProduct[];
287
+ /** Raw accumulated content including markdown, for downstream rendering. */
288
+ rawContent: string;
289
+ }
290
+ interface ChatServiceOptions {
291
+ /** POST endpoint for creating a conversation. */
292
+ conversationsUrl?: string;
293
+ /** POST endpoint for sending a message (SSE response). */
294
+ messagesUrl?: string;
295
+ /** X-Tenant-Id header value (used for conversations call). */
296
+ tenantId?: string;
297
+ /** Returns the current Bearer token, or null if unauthenticated. */
298
+ getAuthToken?: () => string | null;
299
+ /** Override fetch for testing. */
300
+ fetchImpl?: typeof fetch | null;
301
+ }
302
+ interface ChatService {
303
+ /** Creates a conversation and returns the numeric conversation ID. */
304
+ createConversation: (title?: string) => Promise<number>;
305
+ /** Sends a message and streams back a parsed result. */
306
+ sendMessage: (conversationId: number, content: string, onChunk?: (type: string, text: string) => void) => Promise<ChatMessageResult>;
307
+ }
308
+ declare function createChatService(options: ChatServiceOptions): ChatService;
309
+
310
+ /**
311
+ * useSearchSuggestions — Phase 0 lift-and-shift of
312
+ * `header-mfe/src/components/search/auto-complete/hooks/useAutoComplete.js`
313
+ * (`useAutoCompleteSuggestions` export).
314
+ *
315
+ * Behaviour is preserved verbatim:
316
+ * - 220 ms debounce (DEBOUNCE_MS constant — do not change)
317
+ * - Request-ID stale-response guard (replaces AbortController in the original)
318
+ * - `MAX_VISIBLE_SUGGESTIONS` cap applied before setting state
319
+ * - `didYouMean` items take precedence: when present, `suggestions` is set
320
+ * to `[]` and the DYM items are used as the visible list
321
+ * - `emptyFetched` is only set to `true` when BOTH arrays are empty after
322
+ * a successful fetch
323
+ * - `enabled = false` clears state without firing an API call
324
+ *
325
+ * Keyboard navigation (`useAutoCompleteKeyboard`) and all display-data helpers
326
+ * live in `react-search-components` — they depend on DOM refs and component props.
327
+ */
328
+
329
+ /**
330
+ * Manages debounced typeahead suggestions from the Equinox catalog API.
331
+ *
332
+ * @example
333
+ * ```tsx
334
+ * const service = createSearchService({ country, language, locale, suggestionsApi, storeId });
335
+ * const { suggestions, didYouMean, loading, updateQuery, clearQuery } =
336
+ * useSearchSuggestions({ service });
337
+ * ```
338
+ */
339
+ declare function useSearchSuggestions({ service, enabled, }: UseSearchSuggestionsOptions): UseSearchSuggestionsResult;
340
+
341
+ interface SearchHistoryEntry {
342
+ query: string;
343
+ isAI: boolean;
344
+ /** Serialized AI result payload; consumers cast to their concrete result type */
345
+ aiResult?: unknown;
346
+ timestamp: number;
347
+ }
348
+ interface SearchHistoryState {
349
+ entries: SearchHistoryEntry[];
350
+ /** Save a full entry (AI or non-AI); deduplicates by query (case-insensitive). */
351
+ addEntry: (entry: Omit<SearchHistoryEntry, 'timestamp'>) => void;
352
+ /** Convenience alias — saves a plain non-AI query entry. */
353
+ addQuery: (query: string) => void;
354
+ removeEntry: (query: string) => void;
355
+ clearAll: () => void;
356
+ }
357
+ declare const useSearchHistoryStore: zustand.UseBoundStore<Omit<zustand.StoreApi<SearchHistoryState>, "persist"> & {
358
+ persist: {
359
+ setOptions: (options: Partial<zustand_middleware.PersistOptions<SearchHistoryState, unknown>>) => void;
360
+ clearStorage: () => void;
361
+ rehydrate: () => Promise<void> | void;
362
+ hasHydrated: () => boolean;
363
+ onHydrate: (fn: (state: SearchHistoryState) => void) => () => void;
364
+ onFinishHydration: (fn: (state: SearchHistoryState) => void) => () => void;
365
+ getOptions: () => Partial<zustand_middleware.PersistOptions<SearchHistoryState, unknown>>;
366
+ };
367
+ }>;
368
+
369
+ interface UseSearchHistoryResult {
370
+ entries: SearchHistoryEntry[];
371
+ /** Derived string list of queries (backward compat) */
372
+ queries: string[];
373
+ addEntry: (entry: Omit<SearchHistoryEntry, 'timestamp'>) => void;
374
+ /** @deprecated prefer addEntry */
375
+ addQuery: (query: string) => void;
376
+ removeEntry: (query: string) => void;
377
+ clearAll: () => void;
378
+ }
379
+ /** React hook that subscribes to the tab-scoped search history store. */
380
+ declare function useSearchHistory(): UseSearchHistoryResult;
381
+
382
+ interface SpeechRecognitionErrorEventLike {
383
+ error: string;
384
+ }
385
+ interface UseVoiceSearchOptions {
386
+ lang?: string;
387
+ onResult: (transcript: string) => void;
388
+ onError?: (error: SpeechRecognitionErrorEventLike) => void;
389
+ }
390
+ interface UseVoiceSearchResult {
391
+ listening: boolean;
392
+ supported: boolean;
393
+ start: () => void;
394
+ stop: () => void;
395
+ }
396
+ declare function useVoiceSearch({ lang, onResult, onError, }: UseVoiceSearchOptions): UseVoiceSearchResult;
397
+
398
+ /**
399
+ * Catalog-product normalisation helpers — Phase 0 lift-and-shift of the
400
+ * relevant portions of
401
+ * `header-mfe/src/components/search/auto-complete/autoComplete.helpers.js`.
402
+ *
403
+ * Only the data-processing helpers are included here. UI-specific helpers
404
+ * (keyboard event handlers, ARIA attribute computation, display-data
405
+ * extraction) live in `react-search-components` because they depend on DOM
406
+ * refs and component-level props.
407
+ *
408
+ * Key difference from the original:
409
+ * `resolveSuggestionImageSrc` returns `""` when the source is absent
410
+ * instead of falling back to a bundled PNG asset. Components apply their
411
+ * own `<img onError>` / CSS placeholder; the SDK must not bundle assets.
412
+ */
413
+
414
+ /** Returns `true` when `productStatus` is `"sellable"`. */
415
+ declare function isSellableProductStatus(productStatus: unknown): boolean;
416
+ /**
417
+ * Returns `true` when the suggestion option should not allow add-to-cart
418
+ * (out of stock, discontinued, inactive, etc.).
419
+ */
420
+ declare function isCartActionDisabled(suggestionOption: Record<string, unknown> | null | undefined): boolean;
421
+ /**
422
+ * Returns `true` when the suggestion row should be displayed as
423
+ * non-interactive (out-of-stock only — a subset of `isCartActionDisabled`).
424
+ */
425
+ declare function isSuggestionDisabled(suggestionOption: Record<string, unknown> | null | undefined): boolean;
426
+ /**
427
+ * Reads the first non-empty image URL from a Contentstack / catalog property
428
+ * bag. Returns `""` when no image is found.
429
+ */
430
+ declare function getImageFromProperty(properties?: Record<string, unknown>): string;
431
+ /**
432
+ * Normalises an image source value. Returns `""` when the source is absent or
433
+ * blank. Components are responsible for applying a fallback placeholder image.
434
+ */
435
+ declare function resolveSuggestionImageSrc(imageSrc: unknown): string;
436
+ /**
437
+ * Extracts a flat list of ingredient terms from a Contentstack ingredient
438
+ * structure. Used to populate `CatalogSuggestionItem.ingredientTerms`.
439
+ */
440
+ declare function getIngredientTerms(rawIngredients: unknown): string[];
441
+ /**
442
+ * Maps a raw Equinox catalog product object to the normalised
443
+ * `CatalogSuggestionItem` shape consumed by the UI components.
444
+ * Returns `null` for invalid input so callers can `.filter(Boolean)`.
445
+ */
446
+ declare function mapCatalogProductToSuggestion(product: unknown, index?: number): CatalogSuggestionItem | null;
447
+ /**
448
+ * Normalises the raw `didYouMean` / `did_you_mean` array from the Equinox
449
+ * API into a consistent shape consumed by `useSearchSuggestions`.
450
+ */
451
+ declare function normalizeDidYouMean(didYouMean: unknown): DidYouMeanItem[];
452
+ /**
453
+ * Returns the items that should actually be rendered in the suggestions list.
454
+ * When "Did You Mean?" results are present they take precedence over product
455
+ * suggestions (matching the original header-mfe behaviour).
456
+ */
457
+ declare function getVisibleSuggestions(opts: {
458
+ didYouMean: DidYouMeanItem[];
459
+ suggestions: CatalogSuggestionItem[];
460
+ }): DidYouMeanItem[] | CatalogSuggestionItem[];
461
+ /**
462
+ * Builds the analytics payload emitted when the user clicks "See More Results".
463
+ */
464
+ declare function getSeeMoreResultsPayload(query: string, totalResultsCount: number, suggestions: CatalogSuggestionItem[]): {
465
+ query: string;
466
+ source: string;
467
+ totalResultsCount: number;
468
+ visibleResultsCount: number;
469
+ };
470
+
471
+ /**
472
+ * Constants for the search SDK. Ported from
473
+ * `header-mfe/src/utils/search/search.constants.js` — only the values the
474
+ * SDK itself needs (Phase 0 lift-and-shift). CMS keys, GTM tracking config,
475
+ * and mobile-only placeholder rotation constants stay in header-mfe.
476
+ */
477
+ /** API shopping-context segment used when resolving the Equinox gateway URL. */
478
+ declare const STOREFRONT_SHOPPING_CONTEXT = "storefront";
479
+ /** Catalog suggestions path appended when the market gateway is not already
480
+ * under the storefront shopping context. */
481
+ declare const CATALOG_SUGGESTIONS_PATH = "/storefront/catalogs/search/suggestions";
482
+ /** Prefix for the `sessionStorage` key that persists recent searches.
483
+ * Full key format: `${RECENT_SEARCHES_PREFIX}:{locale}:{COUNTRY}:{lang}`.
484
+ * Must not change — existing sessions rely on this key. */
485
+ declare const RECENT_SEARCHES_PREFIX = "nuskin:header:recentSearches";
486
+ /** Maximum number of recent searches persisted per locale bucket. */
487
+ declare const RECENT_SEARCHES_DEFAULT_LIMIT = 10;
488
+ /** Equinox suggestion pagination — page is 1-indexed. */
489
+ declare const DEFAULT_SUGGESTION_PAGE = 1;
490
+ /** Number of suggestion rows requested from the Equinox catalog per call. */
491
+ declare const DEFAULT_SUGGESTION_SIZE = 6;
492
+ /** Segment used when building the fallback search-results URL. */
493
+ declare const DEFAULT_SEARCH_PATH = "/search";
494
+ /** Debounce for typeahead input → API dispatch. Preserved from header-mfe. */
495
+ declare const DEBOUNCE_MS = 220;
496
+ /** Cap on suggestions rendered in the dropdown, regardless of API response size. */
497
+ declare const MAX_VISIBLE_SUGGESTIONS = 6;
498
+ /** Fallback placeholder shown inside the expanded search input. */
499
+ declare const SEARCH_PLACEHOLDER = "Search by product name, ingredient, benefits, or SKU";
500
+ /** Fallback placeholder shown on the collapsed search trigger. */
501
+ declare const TRIGGER_PLACEHOLDER = "Product Search";
502
+ /**
503
+ * Full path segment for the AI commerce chat SSE endpoint.
504
+ * Appended to the consumer-provided `chatBaseUrl`.
505
+ * Source: https://admin.tst.nuskin.ai/api/analytics/v1/commerce/chat
506
+ */
507
+ declare const DEFAULT_AI_CHAT_BASE_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/chat";
508
+ /** Default POST endpoint for creating an AI chat conversation. */
509
+ declare const DEFAULT_AI_CHAT_CONVERSATIONS_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/chat/conversations";
510
+ /** Default POST endpoint for sending AI chat messages. */
511
+ declare const DEFAULT_AI_CHAT_MESSAGES_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/chat/messages";
512
+
513
+ export { CATALOG_SUGGESTIONS_PATH, type CatalogSuggestionItem, type ChatMessageResult, type ChatProduct, type ChatService, type ChatServiceOptions, DEBOUNCE_MS, DEFAULT_AI_CHAT_BASE_URL, DEFAULT_AI_CHAT_CONVERSATIONS_URL, DEFAULT_AI_CHAT_MESSAGES_URL, DEFAULT_SEARCH_PATH, DEFAULT_SUGGESTION_PAGE, DEFAULT_SUGGESTION_SIZE, type DidYouMeanItem, type GetMarketConfig, type LocaleParts, type LocationLike, MAX_VISIBLE_SUGGESTIONS, type MarketConfig, type ProductType, RECENT_SEARCHES_DEFAULT_LIMIT, RECENT_SEARCHES_PREFIX, SEARCH_PLACEHOLDER, STOREFRONT_SHOPPING_CONTEXT, type SearchChromeCopy, type SearchHistoryEntry, type SearchHistoryState, type SearchHistoryEntry as SearchHistoryStoreEntry, type SearchService, type SearchServiceOptions, type SuggestionOption, type SuggestionsResponse, TRIGGER_PLACEHOLDER, type UseSearchHistoryResult, type UseSearchSuggestionsOptions, type UseSearchSuggestionsResult, type UseVoiceSearchOptions, type UseVoiceSearchResult, createChatService, createSearchService, getImageFromProperty, getIngredientTerms, getSeeMoreResultsPayload, getVisibleSuggestions, isCartActionDisabled, isSellableProductStatus, isSuggestionDisabled, mapCatalogProductToSuggestion, normalizeDidYouMean, resolveApiGatewayPath, resolveSuggestionImageSrc, useSearchHistory, useSearchHistoryStore, useSearchSuggestions, useVoiceSearch };