@blotoutio/providers-evo-search-sdk 1.58.0 → 1.59.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/core.cjs.js CHANGED
@@ -656,8 +656,8 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
656
656
  };
657
657
  // Only `/user/*` endpoints return per-user PII (recents, profile-bound
658
658
  // data) and rely on the worker's HMAC-signed cookie. Sending
659
- // `credentials: 'include'` on the public read endpoints (autocomplete,
660
- // search, trending) is pointless and triggers the strict CORS contract
659
+ // `credentials: 'include'` on the public read endpoints (search,
660
+ // trending) is pointless and triggers the strict CORS contract
661
661
  // (`Allow-Credentials: true` + non-wildcard origin echo) on every
662
662
  // response — so default to `omit` and opt in below.
663
663
  const needsCredentials = (path) => path.startsWith('/user/');
@@ -679,12 +679,6 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
679
679
  }
680
680
  return (await response.json());
681
681
  };
682
- const autocomplete = async (query, limit = 8, signal) => {
683
- logger.info(`EvoSearch API: Autocomplete request - query length: ${query.length}, limit: ${limit}`);
684
- const data = await getJSON('/autocomplete', 'Autocomplete', { q: query, limit: String(limit) }, signal);
685
- logger.info(`EvoSearch API: Autocomplete success - ${data.llmSuggestions.length} suggestions, ${data.products.length} products`);
686
- return data;
687
- };
688
682
  const trending = (signal) => {
689
683
  logger.info('EvoSearch API: Fetching trending searches');
690
684
  return getJSON('/trending', 'Trending', undefined, signal);
@@ -693,6 +687,17 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
693
687
  logger.info('EvoSearch API: Fetching ask config');
694
688
  return getJSON('/config', 'Config', undefined, signal);
695
689
  };
690
+ // Fire-and-forget isolate warmer: tells the worker to prime its search
691
+ // index off the request path so the first keystroke doesn't pay the cold
692
+ // prime. Errors are irrelevant — autocomplete just falls back to priming
693
+ // inline like before.
694
+ const warm = () => {
695
+ fetchImpl(getURL('/warm'), {
696
+ method: 'GET',
697
+ headers: getHeaders(),
698
+ credentials: 'omit',
699
+ }).catch(() => undefined);
700
+ };
696
701
  const recents = (signal) => {
697
702
  logger.info('EvoSearch API: Fetching recent searches');
698
703
  return getJSON('/user/recents', 'Recents', undefined, signal);
@@ -724,7 +729,7 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
724
729
  const search = async (query, limit = 8, signal) => {
725
730
  logger.info(`EvoSearch API: Search request - query length: ${query.length}, limit: ${limit}`);
726
731
  const data = await getJSON('/search', 'Search', { q: query, limit: String(limit) }, signal);
727
- logger.info(`EvoSearch API: Search success - ${data.results.length} products`);
732
+ logger.info(`EvoSearch API: Search success - ${data.llmSuggestions.length} suggestions, ${data.products.length} products`);
728
733
  return data;
729
734
  };
730
735
  // SSE consumer for Ask AI. Decodes `event: <type>\ndata: <json>` frames
@@ -793,6 +798,11 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
793
798
  },
794
799
  }),
795
800
  credentials: 'include',
801
+ // Finalize events often fire at widget teardown (disconnectedCallback
802
+ // settles the on-screen query) — i.e. right as the shopper navigates
803
+ // away after a result click. keepalive lets the POST outlive the page;
804
+ // sendBeacon can't carry the identity headers or credentials.
805
+ keepalive: true,
796
806
  })
797
807
  .then(async (response) => {
798
808
  if (!response.ok) {
@@ -820,9 +830,9 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
820
830
  });
821
831
  };
822
832
  return {
823
- autocomplete,
824
833
  trending,
825
834
  config,
835
+ warm,
826
836
  user: {
827
837
  recents,
828
838
  deleteRecent,
package/core.js CHANGED
@@ -657,8 +657,8 @@ var ProvidersEvoSearchSdk = (function () {
657
657
  };
658
658
  // Only `/user/*` endpoints return per-user PII (recents, profile-bound
659
659
  // data) and rely on the worker's HMAC-signed cookie. Sending
660
- // `credentials: 'include'` on the public read endpoints (autocomplete,
661
- // search, trending) is pointless and triggers the strict CORS contract
660
+ // `credentials: 'include'` on the public read endpoints (search,
661
+ // trending) is pointless and triggers the strict CORS contract
662
662
  // (`Allow-Credentials: true` + non-wildcard origin echo) on every
663
663
  // response — so default to `omit` and opt in below.
664
664
  const needsCredentials = (path) => path.startsWith('/user/');
@@ -680,12 +680,6 @@ var ProvidersEvoSearchSdk = (function () {
680
680
  }
681
681
  return (await response.json());
682
682
  };
683
- const autocomplete = async (query, limit = 8, signal) => {
684
- logger.info(`EvoSearch API: Autocomplete request - query length: ${query.length}, limit: ${limit}`);
685
- const data = await getJSON('/autocomplete', 'Autocomplete', { q: query, limit: String(limit) }, signal);
686
- logger.info(`EvoSearch API: Autocomplete success - ${data.llmSuggestions.length} suggestions, ${data.products.length} products`);
687
- return data;
688
- };
689
683
  const trending = (signal) => {
690
684
  logger.info('EvoSearch API: Fetching trending searches');
691
685
  return getJSON('/trending', 'Trending', undefined, signal);
@@ -694,6 +688,17 @@ var ProvidersEvoSearchSdk = (function () {
694
688
  logger.info('EvoSearch API: Fetching ask config');
695
689
  return getJSON('/config', 'Config', undefined, signal);
696
690
  };
691
+ // Fire-and-forget isolate warmer: tells the worker to prime its search
692
+ // index off the request path so the first keystroke doesn't pay the cold
693
+ // prime. Errors are irrelevant — autocomplete just falls back to priming
694
+ // inline like before.
695
+ const warm = () => {
696
+ fetchImpl(getURL('/warm'), {
697
+ method: 'GET',
698
+ headers: getHeaders(),
699
+ credentials: 'omit',
700
+ }).catch(() => undefined);
701
+ };
697
702
  const recents = (signal) => {
698
703
  logger.info('EvoSearch API: Fetching recent searches');
699
704
  return getJSON('/user/recents', 'Recents', undefined, signal);
@@ -725,7 +730,7 @@ var ProvidersEvoSearchSdk = (function () {
725
730
  const search = async (query, limit = 8, signal) => {
726
731
  logger.info(`EvoSearch API: Search request - query length: ${query.length}, limit: ${limit}`);
727
732
  const data = await getJSON('/search', 'Search', { q: query, limit: String(limit) }, signal);
728
- logger.info(`EvoSearch API: Search success - ${data.results.length} products`);
733
+ logger.info(`EvoSearch API: Search success - ${data.llmSuggestions.length} suggestions, ${data.products.length} products`);
729
734
  return data;
730
735
  };
731
736
  // SSE consumer for Ask AI. Decodes `event: <type>\ndata: <json>` frames
@@ -794,6 +799,11 @@ var ProvidersEvoSearchSdk = (function () {
794
799
  },
795
800
  }),
796
801
  credentials: 'include',
802
+ // Finalize events often fire at widget teardown (disconnectedCallback
803
+ // settles the on-screen query) — i.e. right as the shopper navigates
804
+ // away after a result click. keepalive lets the POST outlive the page;
805
+ // sendBeacon can't carry the identity headers or credentials.
806
+ keepalive: true,
797
807
  })
798
808
  .then(async (response) => {
799
809
  if (!response.ok) {
@@ -821,9 +831,9 @@ var ProvidersEvoSearchSdk = (function () {
821
831
  });
822
832
  };
823
833
  return {
824
- autocomplete,
825
834
  trending,
826
835
  config,
836
+ warm,
827
837
  user: {
828
838
  recents,
829
839
  deleteRecent,
package/core.mjs CHANGED
@@ -654,8 +654,8 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
654
654
  };
655
655
  // Only `/user/*` endpoints return per-user PII (recents, profile-bound
656
656
  // data) and rely on the worker's HMAC-signed cookie. Sending
657
- // `credentials: 'include'` on the public read endpoints (autocomplete,
658
- // search, trending) is pointless and triggers the strict CORS contract
657
+ // `credentials: 'include'` on the public read endpoints (search,
658
+ // trending) is pointless and triggers the strict CORS contract
659
659
  // (`Allow-Credentials: true` + non-wildcard origin echo) on every
660
660
  // response — so default to `omit` and opt in below.
661
661
  const needsCredentials = (path) => path.startsWith('/user/');
@@ -677,12 +677,6 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
677
677
  }
678
678
  return (await response.json());
679
679
  };
680
- const autocomplete = async (query, limit = 8, signal) => {
681
- logger.info(`EvoSearch API: Autocomplete request - query length: ${query.length}, limit: ${limit}`);
682
- const data = await getJSON('/autocomplete', 'Autocomplete', { q: query, limit: String(limit) }, signal);
683
- logger.info(`EvoSearch API: Autocomplete success - ${data.llmSuggestions.length} suggestions, ${data.products.length} products`);
684
- return data;
685
- };
686
680
  const trending = (signal) => {
687
681
  logger.info('EvoSearch API: Fetching trending searches');
688
682
  return getJSON('/trending', 'Trending', undefined, signal);
@@ -691,6 +685,17 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
691
685
  logger.info('EvoSearch API: Fetching ask config');
692
686
  return getJSON('/config', 'Config', undefined, signal);
693
687
  };
688
+ // Fire-and-forget isolate warmer: tells the worker to prime its search
689
+ // index off the request path so the first keystroke doesn't pay the cold
690
+ // prime. Errors are irrelevant — autocomplete just falls back to priming
691
+ // inline like before.
692
+ const warm = () => {
693
+ fetchImpl(getURL('/warm'), {
694
+ method: 'GET',
695
+ headers: getHeaders(),
696
+ credentials: 'omit',
697
+ }).catch(() => undefined);
698
+ };
694
699
  const recents = (signal) => {
695
700
  logger.info('EvoSearch API: Fetching recent searches');
696
701
  return getJSON('/user/recents', 'Recents', undefined, signal);
@@ -722,7 +727,7 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
722
727
  const search = async (query, limit = 8, signal) => {
723
728
  logger.info(`EvoSearch API: Search request - query length: ${query.length}, limit: ${limit}`);
724
729
  const data = await getJSON('/search', 'Search', { q: query, limit: String(limit) }, signal);
725
- logger.info(`EvoSearch API: Search success - ${data.results.length} products`);
730
+ logger.info(`EvoSearch API: Search success - ${data.llmSuggestions.length} suggestions, ${data.products.length} products`);
726
731
  return data;
727
732
  };
728
733
  // SSE consumer for Ask AI. Decodes `event: <type>\ndata: <json>` frames
@@ -791,6 +796,11 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
791
796
  },
792
797
  }),
793
798
  credentials: 'include',
799
+ // Finalize events often fire at widget teardown (disconnectedCallback
800
+ // settles the on-screen query) — i.e. right as the shopper navigates
801
+ // away after a result click. keepalive lets the POST outlive the page;
802
+ // sendBeacon can't carry the identity headers or credentials.
803
+ keepalive: true,
794
804
  })
795
805
  .then(async (response) => {
796
806
  if (!response.ok) {
@@ -818,9 +828,9 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
818
828
  });
819
829
  };
820
830
  return {
821
- autocomplete,
822
831
  trending,
823
832
  config,
833
+ warm,
824
834
  user: {
825
835
  recents,
826
836
  deleteRecent,
package/hooks.cjs.js CHANGED
@@ -665,9 +665,6 @@ const processAutocompleteResponse = (response) => {
665
665
  products: response.products.map(mapAPIProductToSDKProduct),
666
666
  };
667
667
  };
668
- const processSearchResponse = (response) => {
669
- return response.results.map(mapAPIProductToSDKProduct);
670
- };
671
668
 
672
669
  const isDevEnv$1 = () => typeof process != 'undefined' && process.env['NODE_ENV'] != 'production';
673
670
  const emptyReturn = (query, setQuery, trackClick) => ({
@@ -707,9 +704,10 @@ const useEvoSearch = (options = {}) => {
707
704
  setError(null);
708
705
  return;
709
706
  }
710
- const wantsSearch = mode === 'search' || mode === 'both';
711
- const wantsAutocomplete = mode === 'autocomplete' || mode === 'both';
712
- if (!wantsAutocomplete) {
707
+ // A single /search call serves both results and suggestions; `mode` only
708
+ // controls whether the suggestions state populates.
709
+ const wantsSuggestions = mode === 'autocomplete' || mode === 'both';
710
+ if (!wantsSuggestions) {
713
711
  setSuggestions([]);
714
712
  }
715
713
  setLoading(true);
@@ -717,28 +715,17 @@ const useEvoSearch = (options = {}) => {
717
715
  const controller = new AbortController();
718
716
  const timer = setTimeout(async () => {
719
717
  try {
720
- const [searchRes, autocompleteRes] = await Promise.all([
721
- wantsSearch ? api.search(query, undefined, controller.signal) : null,
722
- wantsAutocomplete
723
- ? api.autocomplete(query, undefined, controller.signal)
724
- : null,
725
- ]);
726
- if (searchRes) {
727
- setResults(processSearchResponse(searchRes));
728
- }
729
- if (autocompleteRes) {
718
+ const res = await api.search(query, undefined, controller.signal);
719
+ setResults(processAutocompleteResponse(res).products);
720
+ if (wantsSuggestions) {
730
721
  setSuggestions((prev) => {
731
- const next = autocompleteRes.llmSuggestions;
722
+ const next = res.llmSuggestions;
732
723
  if (prev.length === next.length &&
733
724
  prev.every((p, i) => p.phrase === next[i])) {
734
725
  return prev;
735
726
  }
736
727
  return next.map((phrase) => ({ phrase }));
737
728
  });
738
- if (!searchRes) {
739
- const processed = processAutocompleteResponse(autocompleteRes);
740
- setResults(processed.products);
741
- }
742
729
  }
743
730
  }
744
731
  catch (err) {
package/hooks.d.ts CHANGED
@@ -56,14 +56,6 @@ declare module '@blotoutio/providers-evo-search-sdk/hooks' {
56
56
  llmSuggestions: string[]
57
57
  products: AutocompleteAPIProduct[]
58
58
  collections?: AutocompleteAPICollection[]
59
- didYouMean?: string
60
- }
61
-
62
- export type SearchAPIResponse = {
63
- query: string
64
- results: AutocompleteAPIProduct[]
65
- collections?: AutocompleteAPICollection[]
66
- didYouMean?: string
67
59
  }
68
60
 
69
61
  export type RecentsAPIResponse = {
@@ -130,21 +122,17 @@ declare module '@blotoutio/providers-evo-search-sdk/hooks' {
130
122
  | { type: 'error'; message: string }
131
123
 
132
124
  export type EvoSearchAPI = {
133
- autocomplete: (
134
- query: string,
135
- limit?: number,
136
- signal?: AbortSignal
137
- ) => Promise<AutocompleteAPIResponse>
138
125
  search: (
139
126
  query: string,
140
127
  limit?: number,
141
128
  signal?: AbortSignal
142
- ) => Promise<SearchAPIResponse>
129
+ ) => Promise<AutocompleteAPIResponse>
143
130
  trending: (signal?: AbortSignal) => Promise<{
144
131
  trending: string[]
145
132
  recommended_products: AutocompleteAPIProduct[]
146
133
  }>
147
134
  config: (signal?: AbortSignal) => Promise<AskConfigResponse>
135
+ warm: () => void
148
136
  user: EvoSearchUserAPI
149
137
  askStream: (query: string, signal?: AbortSignal) => AsyncIterable<AskEvent>
150
138
  sendEvent: (
package/hooks.mjs CHANGED
@@ -663,9 +663,6 @@ const processAutocompleteResponse = (response) => {
663
663
  products: response.products.map(mapAPIProductToSDKProduct),
664
664
  };
665
665
  };
666
- const processSearchResponse = (response) => {
667
- return response.results.map(mapAPIProductToSDKProduct);
668
- };
669
666
 
670
667
  const isDevEnv$1 = () => typeof process != 'undefined' && process.env['NODE_ENV'] != 'production';
671
668
  const emptyReturn = (query, setQuery, trackClick) => ({
@@ -705,9 +702,10 @@ const useEvoSearch = (options = {}) => {
705
702
  setError(null);
706
703
  return;
707
704
  }
708
- const wantsSearch = mode === 'search' || mode === 'both';
709
- const wantsAutocomplete = mode === 'autocomplete' || mode === 'both';
710
- if (!wantsAutocomplete) {
705
+ // A single /search call serves both results and suggestions; `mode` only
706
+ // controls whether the suggestions state populates.
707
+ const wantsSuggestions = mode === 'autocomplete' || mode === 'both';
708
+ if (!wantsSuggestions) {
711
709
  setSuggestions([]);
712
710
  }
713
711
  setLoading(true);
@@ -715,28 +713,17 @@ const useEvoSearch = (options = {}) => {
715
713
  const controller = new AbortController();
716
714
  const timer = setTimeout(async () => {
717
715
  try {
718
- const [searchRes, autocompleteRes] = await Promise.all([
719
- wantsSearch ? api.search(query, undefined, controller.signal) : null,
720
- wantsAutocomplete
721
- ? api.autocomplete(query, undefined, controller.signal)
722
- : null,
723
- ]);
724
- if (searchRes) {
725
- setResults(processSearchResponse(searchRes));
726
- }
727
- if (autocompleteRes) {
716
+ const res = await api.search(query, undefined, controller.signal);
717
+ setResults(processAutocompleteResponse(res).products);
718
+ if (wantsSuggestions) {
728
719
  setSuggestions((prev) => {
729
- const next = autocompleteRes.llmSuggestions;
720
+ const next = res.llmSuggestions;
730
721
  if (prev.length === next.length &&
731
722
  prev.every((p, i) => p.phrase === next[i])) {
732
723
  return prev;
733
724
  }
734
725
  return next.map((phrase) => ({ phrase }));
735
726
  });
736
- if (!searchRes) {
737
- const processed = processAutocompleteResponse(autocompleteRes);
738
- setResults(processed.products);
739
- }
740
727
  }
741
728
  }
742
729
  catch (err) {