@seekora-ai/ui-sdk-core 0.2.23 → 0.2.25

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/dist/index.d.ts CHANGED
@@ -323,8 +323,11 @@ declare class SearchStateManager {
323
323
  private state;
324
324
  private listeners;
325
325
  private debounceTimer;
326
+ private notifyScheduled;
326
327
  private autoSearch;
327
328
  private debounceMs;
329
+ private searchCoalesceTimer;
330
+ private searchCoalesceResolvers;
328
331
  private defaultSearchOptions;
329
332
  private keepResultsOnClear;
330
333
  private abTestId?;
@@ -345,6 +348,7 @@ declare class SearchStateManager {
345
348
  setSortBy(sortBy: string, triggerSearch?: boolean): void;
346
349
  setItemsPerPage(itemsPerPage: number, triggerSearch?: boolean): void;
347
350
  search(additionalOptions?: Partial<SearchOptions>): Promise<SearchResponse | null>;
351
+ private _executeSearch;
348
352
  private buildSearchOptions;
349
353
  private debouncedSearch;
350
354
  subscribe(listener: (state: SearchState) => void): () => void;
package/dist/index.esm.js CHANGED
@@ -1392,6 +1392,9 @@ class SearchStateManager {
1392
1392
  constructor(config) {
1393
1393
  this.listeners = [];
1394
1394
  this.debounceTimer = null;
1395
+ this.notifyScheduled = false;
1396
+ this.searchCoalesceTimer = null;
1397
+ this.searchCoalesceResolvers = [];
1395
1398
  this.client = config.client;
1396
1399
  this.autoSearch = config.autoSearch !== false;
1397
1400
  this.debounceMs = config.debounceMs || 300;
@@ -1536,13 +1539,27 @@ class SearchStateManager {
1536
1539
  this.debouncedSearch();
1537
1540
  }
1538
1541
  }
1539
- // Manual search trigger
1542
+ // Manual search trigger — coalesces rapid calls within 10ms into a single API request
1540
1543
  async search(additionalOptions) {
1541
1544
  // Clear debounce timer if exists
1542
1545
  if (this.debounceTimer) {
1543
1546
  clearTimeout(this.debounceTimer);
1544
1547
  this.debounceTimer = null;
1545
1548
  }
1549
+ return new Promise((resolve, reject) => {
1550
+ this.searchCoalesceResolvers.push({ resolve, reject });
1551
+ if (this.searchCoalesceTimer) {
1552
+ clearTimeout(this.searchCoalesceTimer);
1553
+ }
1554
+ this.searchCoalesceTimer = setTimeout(() => {
1555
+ this.searchCoalesceTimer = null;
1556
+ const resolvers = [...this.searchCoalesceResolvers];
1557
+ this.searchCoalesceResolvers = [];
1558
+ this._executeSearch(additionalOptions).then((result) => resolvers.forEach(r => r.resolve(result)), (err) => resolvers.forEach(r => r.reject(err)));
1559
+ }, 10);
1560
+ });
1561
+ }
1562
+ async _executeSearch(additionalOptions) {
1546
1563
  this.setState({ loading: true, error: null });
1547
1564
  try {
1548
1565
  const searchOptions = this.buildSearchOptions(additionalOptions);
@@ -1639,19 +1656,27 @@ class SearchStateManager {
1639
1656
  this.state = { ...this.state, ...updates };
1640
1657
  this.notifyListeners();
1641
1658
  }
1642
- // Notify all listeners of state changes
1659
+ // Notify all listeners of state changes, batched via microtask.
1660
+ // Multiple synchronous mutations (e.g. addRefinement + page reset)
1661
+ // coalesce into a single listener notification.
1643
1662
  notifyListeners() {
1644
- const state = this.getState();
1645
- this.listeners.forEach(listener => {
1646
- try {
1647
- listener(state);
1648
- }
1649
- catch (err) {
1650
- const error = err instanceof Error ? err : new Error(String(err));
1651
- log.error('SearchStateManager: Error in listener', {
1652
- error: error.message,
1653
- });
1654
- }
1663
+ if (this.notifyScheduled)
1664
+ return;
1665
+ this.notifyScheduled = true;
1666
+ queueMicrotask(() => {
1667
+ this.notifyScheduled = false;
1668
+ const state = this.getState();
1669
+ this.listeners.forEach(listener => {
1670
+ try {
1671
+ listener(state);
1672
+ }
1673
+ catch (err) {
1674
+ const error = err instanceof Error ? err : new Error(String(err));
1675
+ log.error('SearchStateManager: Error in listener', {
1676
+ error: error.message,
1677
+ });
1678
+ }
1679
+ });
1655
1680
  });
1656
1681
  }
1657
1682
  /** Explicitly clear results (bypasses keepResultsOnClear) */
@@ -1697,10 +1722,13 @@ class SearchStateManager {
1697
1722
  async fetchFilters(options) {
1698
1723
  log.verbose('SearchStateManager: Fetching filters', { options });
1699
1724
  try {
1700
- const filterString = this.buildFilterString();
1725
+ // Do NOT pass refinement-based filters to the Filters API.
1726
+ // Facets should be generated from the search query only, not narrowed
1727
+ // by active filter selections. This keeps facet options stable when
1728
+ // users toggle filters (same behaviour as performSimplifiedFacetSearch
1729
+ // in the search API).
1701
1730
  const response = await this.client.getFilters({
1702
1731
  q: this.state.query || undefined,
1703
- filter: filterString || undefined,
1704
1732
  ...options,
1705
1733
  });
1706
1734
  log.info('SearchStateManager: Filters fetched', {