@seekora-ai/search-sdk 0.2.8 → 0.2.13

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/client.d.ts CHANGED
@@ -78,6 +78,14 @@ export interface SeekoraClientConfig {
78
78
  * @default 0
79
79
  */
80
80
  impressionTrackingDelay?: number;
81
+ /**
82
+ * A/B test experiment ID to include in all analytics events
83
+ */
84
+ abTestId?: string;
85
+ /**
86
+ * A/B test variant to include in all analytics events
87
+ */
88
+ abVariant?: string;
81
89
  /**
82
90
  * Enable event deduplication (industry standard: Segment, Amplitude pattern)
83
91
  * When enabled, generates insert_id for backend deduplication
@@ -147,6 +155,10 @@ export interface ExtendedEventPayload extends DataTypesEventPayload {
147
155
  insert_id?: string;
148
156
  /** Conversion type for conversion events (add_to_cart, wishlist, purchase, etc.) */
149
157
  conversion_type?: string;
158
+ /** A/B test experiment ID */
159
+ ab_test_id?: string;
160
+ /** A/B test variant */
161
+ ab_variant?: string;
150
162
  }
151
163
  export interface SearchOptions {
152
164
  q: string;
@@ -330,6 +342,8 @@ export declare class SeekoraClient {
330
342
  private clientConfig;
331
343
  private enableDeduplication;
332
344
  private deduplicationWindow;
345
+ private abTestId?;
346
+ private abVariant?;
333
347
  constructor(config?: SeekoraClientConfig);
334
348
  /**
335
349
  * Search for documents
@@ -345,11 +359,17 @@ export declare class SeekoraClient {
345
359
  /**
346
360
  * Get query suggestions
347
361
  *
348
- * Uses POST when filtered_tabs or include_dropdown_recommendations is set (GET does not send include_dropdown_recommendations).
349
- * With returnFullResponse: true returns full shape including extensions (trending_searches, top_searches, related_searches, popular_brands, filtered_tabs).
362
+ * **NEW:** Full GET/POST parity! Automatically chooses the best method:
363
+ * - GET: Default for simple requests, including filtered_tabs with ≤5 tabs (faster, cacheable)
364
+ * - POST: Used for complex filtered_tabs (>5 tabs) or when explicitly requested via options.method = 'POST'
365
+ *
366
+ * With returnFullResponse: true returns full shape including extensions (dropdown_recommendations with trending products, filtered_tabs).
350
367
  *
351
368
  * @param query - Partial query to get suggestions for
352
369
  * @param options - Optional parameters for suggestions
370
+ * @param options.filtered_tabs - Tab configurations (now supported in GET!)
371
+ * @param options.include_dropdown_recommendations - Include rich dropdown data
372
+ * @param options.method - Explicitly set 'POST' to force POST method
353
373
  * @returns Suggestion hits array, or QuerySuggestionsFullResponse when returnFullResponse is true
354
374
  */
355
375
  getSuggestions(query: string, options?: {
@@ -737,6 +757,18 @@ export declare class SeekoraClient {
737
757
  anonId: string;
738
758
  sessionId: string;
739
759
  };
760
+ /**
761
+ * Set A/B test experiment ID and variant for all subsequent analytics events.
762
+ * Call this when experiment assignments are fetched.
763
+ */
764
+ setAbTest(abTestId?: string, abVariant?: string): void;
765
+ /**
766
+ * Get current A/B test fields
767
+ */
768
+ getAbTest(): {
769
+ abTestId?: string;
770
+ abVariant?: string;
771
+ };
740
772
  /**
741
773
  * Get the event queue instance (if enabled)
742
774
  */
package/dist/client.js CHANGED
@@ -42,6 +42,8 @@ class SeekoraClient {
42
42
  this.anonId = config.anonId || (0, utils_1.getOrCreateAnonId)();
43
43
  this.sessionId = config.sessionId || (0, utils_1.getOrCreateSessionId)();
44
44
  this.autoTrackSearch = config.autoTrackSearch || false;
45
+ this.abTestId = config.abTestId;
46
+ this.abVariant = config.abVariant;
45
47
  // Initialize context collection
46
48
  this.enableContextCollection = config.enableContextCollection !== false; // Default to true
47
49
  this.contextCollector = new context_collector_1.ContextCollector(config.contextCollector);
@@ -325,11 +327,17 @@ class SeekoraClient {
325
327
  /**
326
328
  * Get query suggestions
327
329
  *
328
- * Uses POST when filtered_tabs or include_dropdown_recommendations is set (GET does not send include_dropdown_recommendations).
329
- * With returnFullResponse: true returns full shape including extensions (trending_searches, top_searches, related_searches, popular_brands, filtered_tabs).
330
+ * **NEW:** Full GET/POST parity! Automatically chooses the best method:
331
+ * - GET: Default for simple requests, including filtered_tabs with ≤5 tabs (faster, cacheable)
332
+ * - POST: Used for complex filtered_tabs (>5 tabs) or when explicitly requested via options.method = 'POST'
333
+ *
334
+ * With returnFullResponse: true returns full shape including extensions (dropdown_recommendations with trending products, filtered_tabs).
330
335
  *
331
336
  * @param query - Partial query to get suggestions for
332
337
  * @param options - Optional parameters for suggestions
338
+ * @param options.filtered_tabs - Tab configurations (now supported in GET!)
339
+ * @param options.include_dropdown_recommendations - Include rich dropdown data
340
+ * @param options.method - Explicitly set 'POST' to force POST method
333
341
  * @returns Suggestion hits array, or QuerySuggestionsFullResponse when returnFullResponse is true
334
342
  */
335
343
  async getSuggestions(query, options) {
@@ -345,10 +353,14 @@ class SeekoraClient {
345
353
  headers['x-anon-id'] = this.anonId;
346
354
  if (this.sessionId)
347
355
  headers['x-session-id'] = this.sessionId;
348
- const usePost = (options?.filtered_tabs && options.filtered_tabs.length > 0) ||
349
- options?.include_dropdown_recommendations === true ||
350
- options?.include_dropdown_product_list === false ||
351
- options?.include_filtered_tabs === false;
356
+ // Use POST for:
357
+ // - Complex filtered_tabs (5+ tabs or very long JSON would exceed URL limits)
358
+ // - Explicit preference for POST (options.method === 'POST')
359
+ // Use GET for everything else (including simple filtered_tabs)
360
+ const hasComplexFilteredTabs = options?.filtered_tabs &&
361
+ options.filtered_tabs.length > 5;
362
+ const usePost = hasComplexFilteredTabs ||
363
+ options?.method === 'POST'; // Allow explicit POST preference
352
364
  const defaults = this.clientConfig?.suggestionsDefaults;
353
365
  const buildRequestBody = () => {
354
366
  const body = {
@@ -406,15 +418,27 @@ class SeekoraClient {
406
418
  });
407
419
  return suggestions;
408
420
  }
409
- // GET for simple requests (no filtered_tabs, no include_dropdown_recommendations)
421
+ // GET for simple requests and simple filtered_tabs (URL-encoded JSON)
410
422
  this.logger.verbose('Using GET endpoint', {
411
423
  endpoint: '/api/v1/suggestions/queries',
412
424
  query,
425
+ hasFilteredTabs: !!(options?.filtered_tabs && options.filtered_tabs.length > 0),
413
426
  });
414
427
  const analyticsTags = Array.isArray(options?.analytics_tags)
415
428
  ? options.analytics_tags.join(',')
416
429
  : options?.analytics_tags;
417
- const response = await this.suggestionsApi.v1SuggestionsQueriesGet(this.storeId, this.readSecret, this.userId, this.anonId, this.sessionId, query, options?.hitsPerPage || 5, options?.page, analyticsTags, options?.tags_match_mode, options?.include_categories, options?.include_facets, options?.max_categories, options?.max_facets, options?.min_popularity, options?.time_range, options?.disable_typo_tolerance, { headers });
430
+ // Encode filtered_tabs as JSON string for URL parameter
431
+ const filteredTabsParam = options?.filtered_tabs
432
+ ? JSON.stringify(options.filtered_tabs)
433
+ : undefined;
434
+ const response = await this.suggestionsApi.v1SuggestionsQueriesGet(this.storeId, this.readSecret, this.userId, this.anonId, this.sessionId, query, // query parameter
435
+ undefined, // q parameter (alias for query)
436
+ options?.hitsPerPage || 5, options?.page, analyticsTags, options?.tags_match_mode, options?.include_categories, options?.include_facets, options?.include_dropdown_recommendations, options?.include_dropdown_product_list, options?.include_filtered_tabs, undefined, // include_empty_query_recommendations
437
+ options?.max_categories, options?.max_facets, options?.min_popularity, options?.time_range, options?.disable_typo_tolerance, filteredTabsParam, // filtered_tabs as JSON string
438
+ undefined, // userId (using header instead)
439
+ undefined, // anonId (using header instead)
440
+ undefined, // sessionId (using header instead)
441
+ { headers });
418
442
  const responseData = response.data?.data || response.data;
419
443
  const suggestions = responseData?.results?.[0]?.hits || responseData?.hits || [];
420
444
  if (options?.returnFullResponse) {
@@ -1213,6 +1237,13 @@ class SeekoraClient {
1213
1237
  payload.is_tablet = ctx.is_tablet ? 1 : 0;
1214
1238
  payload.is_touch_device = ctx.is_touch_device;
1215
1239
  }
1240
+ // Inject A/B test fields from client config (event-level values take precedence)
1241
+ if (this.abTestId && !payload.ab_test_id) {
1242
+ payload.ab_test_id = this.abTestId;
1243
+ }
1244
+ if (this.abVariant && !payload.ab_variant) {
1245
+ payload.ab_variant = this.abVariant;
1246
+ }
1216
1247
  // Generate insert_id for deduplication (industry standard: Segment/Amplitude pattern)
1217
1248
  // Always enable for purchase events (revenue must be accurate)
1218
1249
  // Optional for other events based on config
@@ -1609,6 +1640,21 @@ class SeekoraClient {
1609
1640
  sessionId: this.sessionId,
1610
1641
  };
1611
1642
  }
1643
+ /**
1644
+ * Set A/B test experiment ID and variant for all subsequent analytics events.
1645
+ * Call this when experiment assignments are fetched.
1646
+ */
1647
+ setAbTest(abTestId, abVariant) {
1648
+ this.abTestId = abTestId;
1649
+ this.abVariant = abVariant;
1650
+ this.logger.verbose('A/B test fields updated', { abTestId, abVariant });
1651
+ }
1652
+ /**
1653
+ * Get current A/B test fields
1654
+ */
1655
+ getAbTest() {
1656
+ return { abTestId: this.abTestId, abVariant: this.abVariant };
1657
+ }
1612
1658
  /**
1613
1659
  * Get the event queue instance (if enabled)
1614
1660
  */