@askdialog/dialog-sdk 2.11.0 → 2.12.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 (43) hide show
  1. package/README.md +31 -20
  2. package/dist/Dialog.d.ts +12 -21
  3. package/dist/Dialog.d.ts.map +1 -1
  4. package/dist/Dialog.js +25 -51
  5. package/dist/__tests__/dialogSearchAnalytics.spec.js +2 -4
  6. package/dist/__tests__/publicTypes.spec.js +1 -0
  7. package/dist/__tests__/searchController.spec.js +46 -20
  8. package/dist/__tests__/searchControllerAttribution.spec.js +8 -6
  9. package/dist/__tests__/searchImpressions.spec.js +2 -3
  10. package/dist/__tests__/searchService.spec.js +13 -11
  11. package/dist/searchController.d.ts +1 -1
  12. package/dist/searchController.d.ts.map +1 -1
  13. package/dist/searchController.js +41 -64
  14. package/dist/services/search.d.ts +3 -5
  15. package/dist/services/search.d.ts.map +1 -1
  16. package/dist/services/search.js +11 -6
  17. package/dist/types/constructor.d.ts +3 -0
  18. package/dist/types/constructor.d.ts.map +1 -1
  19. package/dist/types/search.d.ts +14 -14
  20. package/dist/types/search.d.ts.map +1 -1
  21. package/dist/types/search.js +1 -1
  22. package/dist/types/searchAnalytics.d.ts +12 -6
  23. package/dist/types/searchAnalytics.d.ts.map +1 -1
  24. package/dist/types/searchAnalytics.js +1 -4
  25. package/dist/types/searchController.d.ts +22 -51
  26. package/dist/types/searchController.d.ts.map +1 -1
  27. package/dist/types/searchController.js +1 -1
  28. package/dist/utils/localization.d.ts +0 -1
  29. package/dist/utils/localization.d.ts.map +1 -1
  30. package/dist/utils/localization.js +0 -11
  31. package/dist/utils/searchControllerAnalytics.d.ts +1 -4
  32. package/dist/utils/searchControllerAnalytics.d.ts.map +1 -1
  33. package/dist/utils/searchControllerAnalytics.js +5 -8
  34. package/dist/utils/searchImpressions.d.ts +4 -4
  35. package/dist/utils/searchImpressions.d.ts.map +1 -1
  36. package/dist/utils/searchImpressions.js +4 -11
  37. package/dist/utils/searchRequests.d.ts +17 -0
  38. package/dist/utils/searchRequests.d.ts.map +1 -0
  39. package/dist/utils/searchRequests.js +36 -0
  40. package/package.json +1 -1
  41. package/dist/utils/searchSections.d.ts +0 -5
  42. package/dist/utils/searchSections.d.ts.map +0 -1
  43. package/dist/utils/searchSections.js +0 -21
@@ -14,7 +14,7 @@ vi.mock("../utils/searchImpressions", () => ({
14
14
  const response = (overrides = {}) => ({
15
15
  results: [
16
16
  {
17
- index: "products_fr",
17
+ index: "products_fr_eur",
18
18
  hits: [
19
19
  { objectID: "p1", url: "https://shop.example/p1" },
20
20
  { objectID: "p2" },
@@ -36,13 +36,14 @@ const trackSelectSearchResult = vi.fn();
36
36
  const navigate = vi.fn();
37
37
  const createController = () => createSearchController({
38
38
  search,
39
+ language: "fr",
40
+ currency: "EUR",
39
41
  analytics: {
40
42
  surface: "search_page",
41
43
  trackViewSearchResults,
42
44
  trackSelectSearchResult,
43
45
  },
44
46
  navigate,
45
- locale: "fr",
46
47
  });
47
48
  beforeEach(() => {
48
49
  vi.useFakeTimers();
@@ -75,7 +76,7 @@ describe("search controller attribution", () => {
75
76
  await settle();
76
77
  expect(tracker.setContext).toHaveBeenCalledWith({
77
78
  query_id: "qid-1",
78
- index: "products_fr",
79
+ index: "products_fr_eur",
79
80
  surface: "search_page",
80
81
  search_type: "lexical",
81
82
  page: 2,
@@ -105,7 +106,7 @@ describe("search controller attribution", () => {
105
106
  await settle();
106
107
  expect(trackViewSearchResults).toHaveBeenCalledWith({
107
108
  query_id: "qid-1",
108
- index: "products_fr",
109
+ index: "products_fr_eur",
109
110
  surface: "search_page",
110
111
  search_type: "lexical",
111
112
  page: 1,
@@ -151,7 +152,7 @@ describe("search controller attribution", () => {
151
152
  });
152
153
  expect(trackSelectSearchResult).toHaveBeenCalledWith({
153
154
  query_id: "qid-1",
154
- index: "products_fr",
155
+ index: "products_fr_eur",
155
156
  surface: "search_page",
156
157
  search_type: "lexical",
157
158
  page: 1,
@@ -201,12 +202,13 @@ describe("search controller attribution", () => {
201
202
  it("returns false when no navigate adapter is configured", async () => {
202
203
  const controller = createSearchController({
203
204
  search,
205
+ language: "fr",
206
+ currency: "EUR",
204
207
  analytics: {
205
208
  surface: "search_page",
206
209
  trackViewSearchResults,
207
210
  trackSelectSearchResult,
208
211
  },
209
- locale: "fr",
210
212
  });
211
213
  search.mockResolvedValue(response());
212
214
  controller.submit("shoes");
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { createSearchImpressionTracker } from "../utils/searchImpressions";
4
4
  const envelope = {
5
5
  query_id: "query-1",
6
- index: "products_fr",
6
+ index: "products_fr_eur",
7
7
  surface: "search_page",
8
8
  search_type: "lexical",
9
9
  page: 1,
@@ -184,8 +184,7 @@ describe("createSearchImpressionTracker", () => {
184
184
  vi.advanceTimersByTime(300);
185
185
  windowListeners.get("pageshow")?.({ persisted: true });
186
186
  intersect(card, 0.6);
187
- // A stale pre-restore timer (200ms left) would record at 200ms and flush
188
- // at 1700ms; a fresh dwell records at 500ms and flushes at 2000ms.
187
+ // Fresh dwell: record at 500ms and flush at 2000ms, not at 200ms and 1700ms.
189
188
  vi.advanceTimersByTime(1700);
190
189
  expect(emit).not.toHaveBeenCalled();
191
190
  vi.advanceTimersByTime(300);
@@ -5,7 +5,7 @@ const API_KEY = "pk_test_abcdef";
5
5
  const request = {
6
6
  requests: [
7
7
  {
8
- indexName: "products_fr",
8
+ indexName: "products_fr_eur",
9
9
  query: "running shoes",
10
10
  page: 2,
11
11
  hitsPerPage: 50,
@@ -15,7 +15,7 @@ const request = {
15
15
  const emptyResponse = {
16
16
  results: [
17
17
  {
18
- index: "products_fr",
18
+ index: "products_fr_eur",
19
19
  hits: [],
20
20
  nbHits: 0,
21
21
  page: 0,
@@ -41,15 +41,17 @@ afterEach(() => {
41
41
  });
42
42
  describe("searchIndexName", () => {
43
43
  it.each([
44
- ["fr-FR", "products_fr"],
45
- ["pt-BR", "products_pt"],
46
- ["fr", "products_fr"],
47
- ["not a locale", "products_not a locale"],
48
- ])("reduces the locale %s to a bare-language index name", (locale, name) => {
49
- expect(searchIndexName("products", locale)).toBe(name);
44
+ ["fr", "EUR", "products_fr_eur"],
45
+ ["pt", "BRL", "products_pt_brl"],
46
+ ["fr", "CAD", "products_fr_cad"],
47
+ ["en", "EUR", "products_en_eur"],
48
+ ])("builds an index name from language %s and currency", (locale, currency, name) => {
49
+ expect(searchIndexName("products", locale, currency)).toBe(name);
50
50
  });
51
51
  it("names every logical index", () => {
52
- expect(searchIndexName("collections", "en-US")).toBe("collections_en");
52
+ expect(searchIndexName("collections", "en", "USD")).toBe("collections_en_usd");
53
+ expect(() => searchIndexName("products", "fr-FR", "EUR")).toThrow(/ISO 639-1/);
54
+ expect(() => searchIndexName("products", "fr", "")).toThrow(/ISO 4217/);
53
55
  });
54
56
  });
55
57
  describe("searchLexical", () => {
@@ -69,11 +71,11 @@ describe("searchLexical", () => {
69
71
  it("keeps optional per-entry fields the caller did not provide off the wire", async () => {
70
72
  fetchMock.mockResolvedValue(jsonResponse(emptyResponse));
71
73
  await searchLexical(API_KEY, {
72
- requests: [{ indexName: "products_fr", query: "running shoes" }],
74
+ requests: [{ indexName: "products_fr_eur", query: "running shoes" }],
73
75
  });
74
76
  const [, init] = fetchMock.mock.calls[0];
75
77
  expect(JSON.parse(init.body)).toEqual({
76
- requests: [{ indexName: "products_fr", query: "running shoes" }],
78
+ requests: [{ indexName: "products_fr_eur", query: "running shoes" }],
77
79
  });
78
80
  });
79
81
  it("resolves the typed response, including empty results", async () => {
@@ -1,3 +1,3 @@
1
1
  import { SearchController, SearchControllerOptions } from "./types/searchController";
2
- export declare function createSearchController({ search, analytics, navigate, debounceMs, hitsPerPage, locale, sections, }: SearchControllerOptions): SearchController;
2
+ export declare function createSearchController({ search, language, currency, analytics, navigate, debounceMs, hitsPerPage, sections, }: SearchControllerOptions): SearchController;
3
3
  //# sourceMappingURL=searchController.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"searchController.d.ts","sourceRoot":"","sources":["../src/searchController.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EAIxB,MAAM,0BAA0B,CAAC;AAqBlC,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,SAAS,EACT,QAAQ,EACR,UAAgC,EAChC,WAAmC,EACnC,MAAM,EACN,QAAa,GACd,EAAE,uBAAuB,GAAG,gBAAgB,CA6L5C"}
1
+ {"version":3,"file":"searchController.d.ts","sourceRoot":"","sources":["../src/searchController.ts"],"names":[],"mappings":"AACA,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EAGxB,MAAM,0BAA0B,CAAC;AAiBlC,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,UAAgB,EAChB,WAAgB,EAChB,QAAa,GACd,EAAE,uBAAuB,GAAG,gBAAgB,CAuK5C"}
@@ -1,16 +1,7 @@
1
- // This controller is one cohesive unit: `selectResult` reports whether the
2
- // navigate adapter handled the transition and the response is split into the
3
- // products entry plus its sections, pushing the file past the default 200-line
4
- // cap — raised modestly rather than split artificially.
5
- /* eslint max-lines: ["error", 240] */
6
1
  import { searchIndexName } from "./services/search";
7
2
  import { SearchStatus, } from "./types/searchController";
8
3
  import { createControllerAnalytics } from "./utils/searchControllerAnalytics";
9
- import { buildSectionQueries, pickSectionResults, } from "./utils/searchSections";
10
- const DEFAULT_DEBOUNCE_MS = 250;
11
- const DEFAULT_HITS_PER_PAGE = 12;
12
- // `SearchRequest` rejects queries under two visible characters (code points).
13
- const MIN_QUERY_CODE_POINTS = 2;
4
+ import { buildSearchRequest, normalizeQuery, readSearchResults, } from "./utils/searchRequests";
14
5
  const INITIAL_STATE = {
15
6
  status: SearchStatus.IDLE,
16
7
  query: "",
@@ -19,18 +10,19 @@ const INITIAL_STATE = {
19
10
  sections: undefined,
20
11
  error: undefined,
21
12
  };
22
- export function createSearchController({ search, analytics, navigate, debounceMs = DEFAULT_DEBOUNCE_MS, hitsPerPage = DEFAULT_HITS_PER_PAGE, locale, sections = [], }) {
23
- const indexName = searchIndexName("products", locale);
24
- const resolveSections = () => typeof sections === "function" ? sections() : sections;
13
+ export function createSearchController({ search, language, currency, analytics, navigate, debounceMs = 250, hitsPerPage = 12, sections = [], }) {
14
+ const requestConfig = {
15
+ indexName: searchIndexName("products", language, currency),
16
+ language,
17
+ currency,
18
+ hitsPerPage,
19
+ };
25
20
  let state = INITIAL_STATE;
26
21
  const listeners = new Set();
27
22
  const controllerAnalytics = createControllerAnalytics(analytics);
28
23
  let debounceTimer;
29
- // Query typed but not yet run: `state.query` only holds committed queries, so
30
- // without this, `setPage()` would cancel the debounce and paginate stale results.
31
24
  let pendingQuery;
32
25
  let abortController;
33
- // Monotonic token: a response only lands if it belongs to the latest request.
34
26
  let requestId = 0;
35
27
  let disposed = false;
36
28
  const setState = (patch) => {
@@ -39,45 +31,40 @@ export function createSearchController({ search, analytics, navigate, debounceMs
39
31
  listener(state);
40
32
  }
41
33
  };
42
- const cancelInFlight = () => {
34
+ const cancelPendingSearch = () => {
43
35
  clearTimeout(debounceTimer);
36
+ pendingQuery = undefined;
37
+ requestId += 1;
44
38
  abortController?.abort();
45
39
  abortController = undefined;
46
40
  };
47
- const buildRequest = (query, page, requested) => ({
48
- requests: [
49
- { indexName, query, page, hitsPerPage },
50
- ...buildSectionQueries(requested, query, locale, hitsPerPage),
51
- ],
52
- });
53
41
  const run = async (query, page) => {
54
- cancelInFlight();
42
+ cancelPendingSearch();
55
43
  abortController = new AbortController();
56
- const id = ++requestId;
44
+ const id = requestId;
57
45
  setState({ status: SearchStatus.LOADING, query, page });
58
46
  try {
59
- const requested = resolveSections();
60
- const response = await search(buildRequest(query, page, requested), {
47
+ const requested = typeof sections === "function" ? sections() : sections;
48
+ const request = buildSearchRequest(query, page, requested, requestConfig);
49
+ const response = await search(request, {
61
50
  signal: abortController.signal,
62
51
  });
63
52
  if (id !== requestId) {
64
- return; // A newer request landed first: this response is stale.
65
- }
66
- const result = response.results.find((entry) => entry.index === indexName);
67
- if (result === undefined) {
68
- throw new Error(`Dialog search returned no ${indexName} entry`);
53
+ return; // Ignore a superseded request.
69
54
  }
70
- controllerAnalytics.onResponse(result);
55
+ const results = readSearchResults(response, requested, requestConfig);
56
+ controllerAnalytics.onResponse(results.response);
71
57
  setState({
72
- status: result.nbHits === 0 ? SearchStatus.EMPTY : SearchStatus.SUCCESS,
73
- response: result,
74
- sections: pickSectionResults(response, requested, locale),
58
+ ...results,
59
+ status: results.response.nbHits === 0
60
+ ? SearchStatus.EMPTY
61
+ : SearchStatus.SUCCESS,
75
62
  error: undefined,
76
63
  });
77
64
  }
78
65
  catch (error) {
79
66
  if (error instanceof DOMException && error.name === "AbortError") {
80
- return; // Superseded by a newer request — never surfaced.
67
+ return; // Cancellation is not a search error.
81
68
  }
82
69
  if (id !== requestId) {
83
70
  return;
@@ -90,33 +77,28 @@ export function createSearchController({ search, analytics, navigate, debounceMs
90
77
  });
91
78
  }
92
79
  };
93
- // Shared by `setQuery`/`submit`: undefined = reset to idle (query too short).
94
- const prepare = (rawQuery) => {
95
- const query = rawQuery.trim();
96
- cancelInFlight();
97
- pendingQuery = undefined;
98
- if ([...query].length < MIN_QUERY_CODE_POINTS) {
99
- requestId += 1; // Invalidate anything in flight.
100
- setState(INITIAL_STATE);
101
- return undefined;
102
- }
103
- return query;
104
- };
105
80
  return {
106
81
  setQuery(rawQuery) {
107
- const query = disposed ? undefined : prepare(rawQuery);
82
+ if (disposed) {
83
+ return;
84
+ }
85
+ cancelPendingSearch();
86
+ const query = normalizeQuery(rawQuery);
108
87
  if (query === undefined) {
88
+ setState(INITIAL_STATE);
109
89
  return;
110
90
  }
111
91
  pendingQuery = query;
112
- debounceTimer = setTimeout(() => {
113
- pendingQuery = undefined;
114
- void run(query, 0);
115
- }, debounceMs);
92
+ debounceTimer = setTimeout(() => void run(query, 0), debounceMs);
116
93
  },
117
94
  submit(rawQuery) {
118
- const query = disposed ? undefined : prepare(rawQuery);
95
+ if (disposed) {
96
+ return;
97
+ }
98
+ cancelPendingSearch();
99
+ const query = normalizeQuery(rawQuery);
119
100
  if (query === undefined) {
101
+ setState(INITIAL_STATE);
120
102
  return;
121
103
  }
122
104
  void run(query, 0);
@@ -126,15 +108,12 @@ export function createSearchController({ search, analytics, navigate, debounceMs
126
108
  return;
127
109
  }
128
110
  if (pendingQuery !== undefined) {
129
- // A query change is pending: the click targeted stale results, so the
130
- // new query supersedes pagination — flush it now instead of debouncing.
131
- const query = pendingQuery;
132
- pendingQuery = undefined;
133
- void run(query, 0);
111
+ // Submit pending input on page zero before allowing pagination.
112
+ void run(pendingQuery, 0);
134
113
  return;
135
114
  }
136
115
  if (state.query === "") {
137
- return; // Pagination requires a committed non-empty query.
116
+ return; // No query to paginate.
138
117
  }
139
118
  void run(state.query, page);
140
119
  },
@@ -173,9 +152,7 @@ export function createSearchController({ search, analytics, navigate, debounceMs
173
152
  getState: () => state,
174
153
  dispose() {
175
154
  disposed = true;
176
- cancelInFlight();
177
- requestId += 1;
178
- pendingQuery = undefined;
155
+ cancelPendingSearch();
179
156
  listeners.clear();
180
157
  controllerAnalytics.dispose();
181
158
  },
@@ -1,8 +1,6 @@
1
1
  import { SearchIndex, SearchOptions, SearchRequest, SearchResponse } from "../types/search";
2
- export declare const searchIndexName: (index: SearchIndex, locale: string) => string;
3
- /**
4
- * One POST per invocation no debounce, cache, retry or request state; the
5
- * caller owns cancellation through `options.signal`.
6
- */
2
+ /** Build an index name from an ISO 639-1 language and ISO 4217 currency. */
3
+ export declare const searchIndexName: (index: SearchIndex, language: string, currency: string) => string;
4
+ /** Send one request. The caller controls cancellation through `options.signal`. */
7
5
  export declare const searchLexical: (apiKey: string, request: SearchRequest, options?: SearchOptions) => Promise<SearchResponse>;
8
6
  //# sourceMappingURL=search.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/services/search.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc,EACf,MAAM,iBAAiB,CAAC;AAMzB,eAAO,MAAM,eAAe,GAAI,OAAO,WAAW,EAAE,QAAQ,MAAM,KAAG,MACzB,CAAC;AAoB7C;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,SAAS,aAAa,EACtB,UAAU,aAAa,KACtB,OAAO,CAAC,cAAc,CAgBxB,CAAC"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/services/search.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc,EACf,MAAM,iBAAiB,CAAC;AAKzB,4EAA4E;AAC5E,eAAO,MAAM,eAAe,GAC1B,OAAO,WAAW,EAClB,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,MASF,CAAC;AAoBF,mFAAmF;AACnF,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,SAAS,aAAa,EACtB,UAAU,aAAa,KACtB,OAAO,CAAC,cAAc,CAgBxB,CAAC"}
@@ -1,9 +1,17 @@
1
1
  import { config } from "../config";
2
2
  import { DialogSearchError } from "../DialogSearchError";
3
- import { toIso639LanguageCode } from "../utils/localization";
4
3
  const SEARCH_PATH = "/public/search/lexical";
5
4
  const API_KEY_HEADER = "x-dialog-api-key";
6
- export const searchIndexName = (index, locale) => `${index}_${toIso639LanguageCode(locale)}`;
5
+ /** Build an index name from an ISO 639-1 language and ISO 4217 currency. */
6
+ export const searchIndexName = (index, language, currency) => {
7
+ if (!/^[a-z]{2}$/.test(language)) {
8
+ throw new Error("Search language must be a lowercase ISO 639-1 code.");
9
+ }
10
+ if (!/^[A-Za-z]{3}$/.test(currency)) {
11
+ throw new Error("Search currency must be an ISO 4217 code.");
12
+ }
13
+ return `${index}_${language}_${currency.toLowerCase()}`;
14
+ };
7
15
  const toSearchError = async (response) => {
8
16
  let body;
9
17
  try {
@@ -18,10 +26,7 @@ const toSearchError = async (response) => {
18
26
  : response.statusText;
19
27
  return new DialogSearchError({ status: response.status, code, message });
20
28
  };
21
- /**
22
- * One POST per invocation — no debounce, cache, retry or request state; the
23
- * caller owns cancellation through `options.signal`.
24
- */
29
+ /** Send one request. The caller controls cancellation through `options.signal`. */
25
30
  export const searchLexical = async (apiKey, request, options) => {
26
31
  const response = await fetch(`${config.monolithApiUrl}${SEARCH_PATH}`, {
27
32
  method: "POST",
@@ -12,7 +12,10 @@ export interface CurrentProduct {
12
12
  }
13
13
  export interface DialogConstructor {
14
14
  apiKey: string;
15
+ /** BCP-47 locale, e.g. `fr-FR`. */
15
16
  locale: string;
17
+ /** Currency (ISO 4217), independent of language. */
18
+ currency: string;
16
19
  /**
17
20
  * Optional ISO 3166 country code used to format prices (e.g. 'FR', 'US').
18
21
  * When provided it takes precedence over the country derived from `locale`.
@@ -1 +1 @@
1
- {"version":3,"file":"constructor.d.ts","sourceRoot":"","sources":["../../src/types/constructor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,EAAE,CACV,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,KACf,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B"}
1
+ {"version":3,"file":"constructor.d.ts","sourceRoot":"","sources":["../../src/types/constructor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,EAAE,CACV,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,KACf,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B"}
@@ -1,38 +1,38 @@
1
- /** Logical indices the public search serves; the wire name adds the locale. */
1
+ /** Supported indices before adding language and currency. */
2
2
  export declare const SEARCH_INDICES: readonly ["products", "collections", "articles", "pages"];
3
3
  export type SearchIndex = (typeof SEARCH_INDICES)[number];
4
4
  export interface SearchQuery {
5
- /** `<index>_<locale>` (e.g. `products_fr`), one shared locale per request; unknown or unserved → 404. */
5
+ /**
6
+ * `<index>_<lang>_<currency>`, e.g. `products_fr_eur`.
7
+ * Use the same language and lowercase ISO 4217 currency for every entry.
8
+ * Unsupported index names return 404.
9
+ */
6
10
  indexName: string;
7
- /** Trimmed server-side; must keep at least two visible characters (code points). */
11
+ /** At least two code points after server-side trimming. */
8
12
  query: string;
9
- /** Zero-indexed results page. Defaults to 0 server-side. */
13
+ /** Zero-based page; server default: 0. */
10
14
  page?: number;
11
- /** Between 1 and 100. Defaults to 20 server-side. */
15
+ /** Page size from 1 to 100; server default: 20. */
12
16
  hitsPerPage?: number;
13
17
  }
14
18
  export interface SearchRequest {
15
19
  requests: SearchQuery[];
16
20
  }
17
21
  export interface SearchOptions {
18
- /** Forwarded to fetch untouched: aborting rejects with the native AbortError. */
22
+ /** Passed to fetch; cancellation rejects with `AbortError`. */
19
23
  signal?: AbortSignal;
20
24
  }
21
25
  export interface SearchPrice {
22
- /** Decimal amount as a string, exactly as indexed (e.g. "24.90"). */
26
+ /** Decimal string, e.g. "24.90". */
23
27
  amount: string;
24
- /** Absent when the price was indexed without a currency. */
28
+ /** Currency code, if provided by the index. */
25
29
  currencyCode?: string;
26
30
  }
27
31
  export interface SearchPriceRange {
28
32
  min: SearchPrice;
29
33
  max: SearchPrice;
30
34
  }
31
- /**
32
- * A flat, storefront-ready record: `objectID` plus the record's attributes.
33
- * Every display field is best-effort — a hit may carry only its id;
34
- * `priceRange` only comes from the products index.
35
- */
35
+ /** Indexed record. Display fields are optional; only products have `priceRange`. */
36
36
  export interface SearchHit {
37
37
  objectID: string;
38
38
  title?: string;
@@ -52,7 +52,7 @@ export interface SearchResult {
52
52
  query: string;
53
53
  queryID: string;
54
54
  }
55
- /** One entry per request entry, in request order. */
55
+ /** Results in request order. */
56
56
  export interface SearchResponse {
57
57
  results: SearchResult[];
58
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/types/search.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,eAAO,MAAM,cAAc,2DAKjB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,MAAM,WAAW,WAAW;IAC1B,yGAAyG;IACzG,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,WAAW,CAAC;IACjB,GAAG,EAAE,WAAW,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/types/search.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,eAAO,MAAM,cAAc,2DAKjB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,WAAW,CAAC;IACjB,GAAG,EAAE,WAAW,CAAC;CAClB;AAED,oFAAoF;AACpF,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,gCAAgC;AAChC,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB"}
@@ -1,4 +1,4 @@
1
- /** Logical indices the public search serves; the wire name adds the locale. */
1
+ /** Supported indices before adding language and currency. */
2
2
  export const SEARCH_INDICES = [
3
3
  "products",
4
4
  "collections",
@@ -4,26 +4,32 @@ export declare const SEARCH_TYPES: readonly ["lexical"];
4
4
  export type SearchType = (typeof SEARCH_TYPES)[number];
5
5
  export interface SearchAnalyticsEnvelope {
6
6
  query_id: string;
7
- /** Public index name the result came from (e.g. `products_fr`) — the Algolia Insights tuple's index. */
7
+ /** Index returned by the API, e.g. `products_fr_eur`. */
8
8
  index: string;
9
- /** Where results are displayed — not the integration technology. */
9
+ /** UI surface displaying the results. */
10
10
  surface: SearchSurface;
11
- /** Retrieval mode that produced the results. */
11
+ /** Search mode that produced the results. */
12
12
  search_type: SearchType;
13
- /** 1-based results page. */
13
+ /** One-based results page. */
14
14
  page: number;
15
- /** Total results for the query, not for the page. */
15
+ /** Total hits across all pages. */
16
16
  total_hits: number;
17
- /** Code points of the trimmed query. */
17
+ /** Number of code points in the trimmed query. */
18
18
  query_length: number;
19
19
  }
20
+ /** Position is one-based across pages: page 2 at 20 hits/page starts at 21. */
20
21
  export interface SearchResultItem {
21
22
  product_id: string;
22
23
  position: number;
23
24
  }
25
+ /**
26
+ * Impressions batched and deduplicated by (query_id, product_id).
27
+ * Empty results use `items: []` and `total_hits: 0`.
28
+ */
24
29
  export interface ViewSearchResultsParams extends SearchAnalyticsEnvelope {
25
30
  items: SearchResultItem[];
26
31
  }
32
+ /** One selected result per event. */
27
33
  export interface SelectSearchResultParams extends SearchAnalyticsEnvelope {
28
34
  items: [SearchResultItem];
29
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"searchAnalytics.d.ts","sourceRoot":"","sources":["../../src/types/searchAnalytics.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,eAAe,uEAKlB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,eAAO,MAAM,YAAY,sBAAuB,CAAC;AAEjD,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,wGAAwG;IACxG,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,OAAO,EAAE,aAAa,CAAC;IACvB,gDAAgD;IAChD,WAAW,EAAE,UAAU,CAAC;IACxB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,YAAY,EAAE,MAAM,CAAC;CACtB;AAID,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAMD,MAAM,WAAW,uBAAwB,SAAQ,uBAAuB;IACtE,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAGD,MAAM,WAAW,wBAAyB,SAAQ,uBAAuB;IACvE,KAAK,EAAE,CAAC,gBAAgB,CAAC,CAAC;CAC3B;AAKD,MAAM,WAAW,6BAA8B,SAAQ,uBAAuB;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,8BACf,SAAQ,wBAAwB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"searchAnalytics.d.ts","sourceRoot":"","sources":["../../src/types/searchAnalytics.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe,uEAKlB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,eAAO,MAAM,YAAY,sBAAuB,CAAC;AAEjD,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,OAAO,EAAE,aAAa,CAAC;IACvB,6CAA6C;IAC7C,WAAW,EAAE,UAAU,CAAC;IACxB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,+EAA+E;AAC/E,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAwB,SAAQ,uBAAuB;IACtE,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAED,qCAAqC;AACrC,MAAM,WAAW,wBAAyB,SAAQ,uBAAuB;IACvE,KAAK,EAAE,CAAC,gBAAgB,CAAC,CAAC;CAC3B;AAGD,MAAM,WAAW,6BAA8B,SAAQ,uBAAuB;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,8BACf,SAAQ,wBAAwB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -1,7 +1,4 @@
1
- // Storefront-search analytics contract (DEC-2448), snake_case end to end:
2
- // these property names ARE the canonical PostHog properties — the host
3
- // bridge forwards them verbatim (no camelCase mapping), so any rename here
4
- // is a breaking change of the cross-repo contract.
1
+ // Property names are forwarded unchanged to analytics; renaming them breaks consumers.
5
2
  export const SEARCH_SURFACES = [
6
3
  "autocomplete",
7
4
  "search_page",