@seekora-ai/search-sdk 0.2.21 → 0.2.23

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.js CHANGED
@@ -9,6 +9,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.SeekoraClient = void 0;
11
11
  const generated_1 = require("./generated");
12
+ const analytics_next_1 = require("@segment/analytics-next");
12
13
  const axios_1 = __importDefault(require("axios"));
13
14
  const config_1 = require("./config");
14
15
  const logger_1 = require("./logger");
@@ -16,15 +17,34 @@ const config_loader_1 = require("./config-loader");
16
17
  const utils_1 = require("./utils");
17
18
  const context_collector_1 = require("./context-collector");
18
19
  const event_queue_1 = require("./event-queue");
20
+ const searchId_1 = require("./analytics/searchId");
19
21
  /**
20
22
  * Seekora SDK Client
21
23
  *
22
24
  * Provides a clean, easy-to-use interface for the Seekora Search API
23
25
  */
24
26
  class SeekoraClient {
27
+ /**
28
+ * Lazy-init analytics-next. Returns null when Jitsu config is incomplete
29
+ * (jitsuWriteKey missing) so callers no-op rather than firing events
30
+ * with the wrong credential to the wrong endpoint. SCRUM-259.
31
+ */
32
+ getAnalytics() {
33
+ const writeKey = this.clientConfig.jitsuWriteKey;
34
+ if (!writeKey)
35
+ return null;
36
+ if (!this._analytics) {
37
+ this._analytics = analytics_next_1.AnalyticsBrowser.load({
38
+ writeKey,
39
+ cdnURL: this.clientConfig.jitsuIngestUrl,
40
+ });
41
+ }
42
+ return this._analytics;
43
+ }
25
44
  constructor(config = {}) {
26
45
  this.cachedBrowserContext = null;
27
46
  this.eventQueue = null;
47
+ this._analytics = null;
28
48
  this.clientConfig = config;
29
49
  // Load configuration from file, env, and code (in that order)
30
50
  const mergedConfig = (0, config_loader_1.loadConfig)(config);
@@ -62,34 +82,34 @@ class SeekoraClient {
62
82
  this.enableDeduplication = config.enableDeduplication || false;
63
83
  this.deduplicationWindow = config.deduplicationWindow || 300000; // Default: 5 minutes
64
84
  // Log identifier initialization
65
- this.logger.verbose('Client identifiers initialized', {
85
+ this.logger.verbose("Client identifiers initialized", {
66
86
  hasUserId: !!this.userId,
67
- anonId: this.anonId.substring(0, 8) + '...', // Log partial ID for privacy
68
- sessionId: this.sessionId.substring(0, 8) + '...',
87
+ anonId: this.anonId.substring(0, 8) + "...", // Log partial ID for privacy
88
+ sessionId: this.sessionId.substring(0, 8) + "...",
69
89
  autoTrackSearch: this.autoTrackSearch,
70
90
  enableContextCollection: this.enableContextCollection,
71
91
  enableEventQueue: this.enableEventQueue,
72
92
  enableDeduplication: this.enableDeduplication,
73
- deduplicationWindow: this.deduplicationWindow
93
+ deduplicationWindow: this.deduplicationWindow,
74
94
  });
75
- this.logger.verbose('Initializing SeekoraClient', {
95
+ this.logger.verbose("Initializing SeekoraClient", {
76
96
  storeId: this.storeId,
77
97
  environment: mergedConfig.environment,
78
98
  logLevel,
79
99
  });
80
100
  // Get base URL from environment or config
81
101
  const baseUrl = (0, config_1.getBaseUrl)(mergedConfig.environment, mergedConfig.baseUrl);
82
- this.logger.verbose('Using base URL', { baseUrl });
102
+ this.logger.verbose("Using base URL", { baseUrl });
83
103
  // Create configuration with base URL
84
104
  this.config = new generated_1.Configuration({
85
105
  basePath: baseUrl,
86
106
  baseOptions: {
87
107
  timeout: mergedConfig.timeout || 30000,
88
108
  headers: {
89
- 'x-storeid': this.storeId,
90
- 'x-storesecret': this.readSecret,
91
- }
92
- }
109
+ "x-storeid": this.storeId,
110
+ "x-storesecret": this.readSecret,
111
+ },
112
+ },
93
113
  });
94
114
  // Initialize API clients
95
115
  this.searchApi = new generated_1.SearchApi(this.config);
@@ -97,7 +117,7 @@ class SeekoraClient {
97
117
  this.analyticsApi = new generated_1.AnalyticsEventsApi(this.config);
98
118
  this.storeManagementApi = new generated_1.StoreManagementApi(this.config);
99
119
  this.filtersApi = new generated_1.FiltersApi(this.config);
100
- this.logger.info('SeekoraClient initialized successfully');
120
+ this.logger.info("SeekoraClient initialized successfully");
101
121
  }
102
122
  /**
103
123
  * Search for documents
@@ -111,14 +131,14 @@ class SeekoraClient {
111
131
  */
112
132
  async search(query, options) {
113
133
  // Convert empty query to wildcard
114
- const searchQuery = query.trim() || '*';
134
+ const searchQuery = query.trim() || "*";
115
135
  // Generate correlation_id for this search journey
116
136
  const correlationId = (0, utils_1.generateUUID)();
117
- this.logger.verbose('Executing search', {
137
+ this.logger.verbose("Executing search", {
118
138
  originalQuery: query,
119
139
  searchQuery,
120
140
  correlationId,
121
- options
141
+ options,
122
142
  });
123
143
  const searchRequest = {
124
144
  q: searchQuery,
@@ -162,32 +182,32 @@ class SeekoraClient {
162
182
  synonym_sets: options?.synonym_sets,
163
183
  };
164
184
  // Log search request details (verbose level for debugging)
165
- this.logger.verbose('Search request prepared', {
185
+ this.logger.verbose("Search request prepared", {
166
186
  filter: searchRequest.filter,
167
187
  filterType: typeof searchRequest.filter,
168
- fullRequest: searchRequest
188
+ fullRequest: searchRequest,
169
189
  });
170
190
  try {
171
191
  // Log API request start
172
- this.logger.verbose('Sending search API request', {
173
- endpoint: '/api/v1/search',
174
- method: 'POST',
175
- storeId: this.storeId
192
+ this.logger.verbose("Sending search API request", {
193
+ endpoint: "/api/v1/search",
194
+ method: "POST",
195
+ storeId: this.storeId,
176
196
  });
177
197
  // Build headers with personalization support
178
198
  const headers = {
179
- 'x-storeid': this.storeId,
180
- 'x-storesecret': this.readSecret,
199
+ "x-storeid": this.storeId,
200
+ "x-storesecret": this.readSecret,
181
201
  };
182
202
  // Add personalization headers if available
183
203
  if (this.userId) {
184
- headers['x-user-id'] = this.userId;
204
+ headers["x-user-id"] = this.userId;
185
205
  }
186
206
  if (this.anonId) {
187
- headers['x-anon-id'] = this.anonId;
207
+ headers["x-anon-id"] = this.anonId;
188
208
  }
189
209
  if (this.sessionId) {
190
- headers['x-session-id'] = this.sessionId;
210
+ headers["x-session-id"] = this.sessionId;
191
211
  }
192
212
  // Use POST to avoid URL length limits with complex filters/facets
193
213
  const response = await this.searchApi.v1SearchPost(this.storeId, this.readSecret, {
@@ -201,9 +221,15 @@ class SeekoraClient {
201
221
  widget_mode: searchRequest.widget_mode,
202
222
  include_suggestions: searchRequest.include_suggestions,
203
223
  suggestions_limit: searchRequest.suggestions_limit,
204
- analytics_tags: Array.isArray(searchRequest.analytics_tags) ? searchRequest.analytics_tags : undefined,
205
- stopword_sets: Array.isArray(searchRequest.stopword_sets) ? searchRequest.stopword_sets : undefined,
206
- synonym_sets: Array.isArray(searchRequest.synonym_sets) ? searchRequest.synonym_sets : undefined,
224
+ analytics_tags: Array.isArray(searchRequest.analytics_tags)
225
+ ? searchRequest.analytics_tags
226
+ : undefined,
227
+ stopword_sets: Array.isArray(searchRequest.stopword_sets)
228
+ ? searchRequest.stopword_sets
229
+ : undefined,
230
+ synonym_sets: Array.isArray(searchRequest.synonym_sets)
231
+ ? searchRequest.synonym_sets
232
+ : undefined,
207
233
  search_fields: searchRequest.search_fields,
208
234
  return_fields: searchRequest.return_fields,
209
235
  omit_fields: searchRequest.omit_fields,
@@ -231,40 +257,58 @@ class SeekoraClient {
231
257
  facet_search_text: searchRequest.facet_search_text,
232
258
  }, this.userId, this.anonId, this.sessionId, { headers });
233
259
  // Log API response received
234
- this.logger.verbose('Search API response received', {
260
+ this.logger.verbose("Search API response received", {
235
261
  status: response.status,
236
- hasData: !!response.data
262
+ hasData: !!response.data,
237
263
  });
238
264
  // Extract search results
239
265
  const data = response.data;
240
266
  const facets = data?.data?.facets || [];
241
267
  // Log API response (verbose level for detailed debugging)
242
- this.logger.verbose('API response received', {
268
+ this.logger.verbose("API response received", {
243
269
  status: response.status,
244
270
  totalResults: data?.data?.total_results || 0,
245
271
  resultsCount: data?.data?.results?.length || 0,
246
- facetsType: Array.isArray(facets) ? 'array' : typeof facets,
247
- facetsCount: Array.isArray(facets) ? facets.length : Object.keys(facets || {}).length
272
+ facetsType: Array.isArray(facets) ? "array" : typeof facets,
273
+ facetsCount: Array.isArray(facets)
274
+ ? facets.length
275
+ : Object.keys(facets || {}).length,
248
276
  });
249
277
  // Convert facets array to a more accessible object format
250
278
  const facetsMap = {};
251
279
  if (Array.isArray(facets)) {
252
- this.logger.verbose('Processing facets as array', { facetsCount: facets.length });
280
+ this.logger.verbose("Processing facets as array", {
281
+ facetsCount: facets.length,
282
+ });
253
283
  facets.forEach((facet, index) => {
254
284
  // Handle different facet formats
255
- const fieldName = facet?.field_name || facet?.fieldName || facet?.name || `facet_${index}`;
256
- if (fieldName && (facet?.counts || facet?.values || Array.isArray(facet))) {
285
+ const fieldName = facet?.field_name ||
286
+ facet?.fieldName ||
287
+ facet?.name ||
288
+ `facet_${index}`;
289
+ if (fieldName &&
290
+ (facet?.counts || facet?.values || Array.isArray(facet))) {
257
291
  facetsMap[fieldName] = {
258
292
  field_name: fieldName,
259
- counts: facet.counts || facet.values || (Array.isArray(facet) ? facet : []),
260
- stats: facet.stats || {}
293
+ counts: facet.counts ||
294
+ facet.values ||
295
+ (Array.isArray(facet) ? facet : []),
296
+ stats: facet.stats || {},
261
297
  };
262
- this.logger.verbose('Processed facet', { fieldName, index, counts: facetsMap[fieldName].counts?.length || 0 });
298
+ this.logger.verbose("Processed facet", {
299
+ fieldName,
300
+ index,
301
+ counts: facetsMap[fieldName].counts?.length || 0,
302
+ });
263
303
  }
264
304
  });
265
305
  }
266
- else if (facets && typeof facets === 'object' && !Array.isArray(facets)) {
267
- this.logger.verbose('Processing facets as object', { facetKeys: Object.keys(facets) });
306
+ else if (facets &&
307
+ typeof facets === "object" &&
308
+ !Array.isArray(facets)) {
309
+ this.logger.verbose("Processing facets as object", {
310
+ facetKeys: Object.keys(facets),
311
+ });
268
312
  // If facets is already an object, use it directly but ensure field_name is set
269
313
  Object.keys(facets).forEach((key) => {
270
314
  const facetData = facets[key];
@@ -272,17 +316,29 @@ class SeekoraClient {
272
316
  facetsMap[key] = {
273
317
  field_name: facetData.field_name || key,
274
318
  counts: facetData.counts || facetData.values || [],
275
- stats: facetData.stats || {}
319
+ stats: facetData.stats || {},
276
320
  };
277
321
  }
278
322
  });
279
323
  }
280
- this.logger.verbose('Facets processed', {
324
+ this.logger.verbose("Facets processed", {
281
325
  facetFields: Object.keys(facetsMap),
282
- totalFacetFields: Object.keys(facetsMap).length
326
+ totalFacetFields: Object.keys(facetsMap).length,
283
327
  });
284
328
  // Extract search_id from response (if present)
285
329
  const searchId = data?.data?.search_id;
330
+ // V4 emission module: register the freshly-minted search_id so the
331
+ // 3-layer resolver (in-memory → sessionStorage → URL) returns it on
332
+ // subsequent track() calls within the 30-min TTL window. See
333
+ // src/analytics/searchId.ts + spec Section 6C.
334
+ if (searchId) {
335
+ try {
336
+ (0, searchId_1.setActiveSearchId)(searchId);
337
+ }
338
+ catch {
339
+ // Defensive — search must never fail because of analytics wiring.
340
+ }
341
+ }
286
342
  // Create search context for linking events
287
343
  const context = {
288
344
  correlationId,
@@ -298,9 +354,9 @@ class SeekoraClient {
298
354
  context, // New: search context for event linking
299
355
  facets: facetsMap,
300
356
  facet_counts: facetsMap, // Alias for backward compatibility
301
- ...data
357
+ ...data,
302
358
  };
303
- this.logger.info('Search completed', {
359
+ this.logger.info("Search completed", {
304
360
  query: searchQuery,
305
361
  originalQuery: query,
306
362
  totalResults: results.totalResults,
@@ -311,20 +367,25 @@ class SeekoraClient {
311
367
  if (this.autoTrackSearch) {
312
368
  this.trackSearch({
313
369
  query: searchQuery,
314
- resultsCount: results.totalResults,
315
- context,
370
+ numFound: results.totalResults,
316
371
  }).catch((err) => {
317
372
  // Log but don't fail the search if tracking fails
318
- this.logger.warn('Failed to auto-track search event', { error: err.message });
373
+ this.logger.warn("Failed to auto-track search event", {
374
+ error: err.message,
375
+ });
319
376
  });
320
377
  }
321
378
  // Automatically track search result impressions if enabled
322
- if (this.clientConfig.autoTrackImpressions !== false && results.results && results.results.length > 0) {
379
+ if (this.clientConfig.autoTrackImpressions !== false &&
380
+ results.results &&
381
+ results.results.length > 0) {
323
382
  const delay = this.clientConfig.impressionTrackingDelay || 0;
324
383
  const trackImpressions = () => {
325
384
  this.autoTrackSearchImpressions(results, context).catch((err) => {
326
385
  // Log but don't fail the search if tracking fails
327
- this.logger.warn('Failed to auto-track impressions', { error: err.message });
386
+ this.logger.warn("Failed to auto-track impressions", {
387
+ error: err.message,
388
+ });
328
389
  });
329
390
  };
330
391
  if (delay > 0) {
@@ -356,8 +417,8 @@ class SeekoraClient {
356
417
  method: error.config?.method,
357
418
  };
358
419
  }
359
- this.logger.error('Search failed', errorDetails);
360
- throw this.handleError(error, 'search');
420
+ this.logger.error("Search failed", errorDetails);
421
+ throw this.handleError(error, "search");
361
422
  }
362
423
  }
363
424
  /**
@@ -372,18 +433,18 @@ class SeekoraClient {
372
433
  */
373
434
  async multiSearch(queries) {
374
435
  if (!queries || queries.length === 0) {
375
- throw new Error('At least one search query is required');
436
+ throw new Error("At least one search query is required");
376
437
  }
377
438
  if (queries.length > 10) {
378
- throw new Error('Maximum 10 search queries per request');
439
+ throw new Error("Maximum 10 search queries per request");
379
440
  }
380
- this.logger.verbose('Executing multi-search', {
441
+ this.logger.verbose("Executing multi-search", {
381
442
  queryCount: queries.length,
382
- queries: queries.map(q => q.q),
443
+ queries: queries.map((q) => q.q),
383
444
  });
384
445
  // Build request body matching DataTypes.MultiSearchRequest
385
- const searches = queries.map(q => ({
386
- q: q.q.trim() || '*',
446
+ const searches = queries.map((q) => ({
447
+ q: q.q.trim() || "*",
387
448
  per_page: q.per_page || 10,
388
449
  page: q.page || 1,
389
450
  filter: q.filter || q.filter_by,
@@ -403,15 +464,15 @@ class SeekoraClient {
403
464
  max_facet_values: q.max_facet_values,
404
465
  }));
405
466
  const headers = {
406
- 'x-storeid': this.storeId,
407
- 'x-storesecret': this.readSecret,
467
+ "x-storeid": this.storeId,
468
+ "x-storesecret": this.readSecret,
408
469
  };
409
470
  if (this.userId)
410
- headers['x-user-id'] = this.userId;
471
+ headers["x-user-id"] = this.userId;
411
472
  if (this.anonId)
412
- headers['x-anon-id'] = this.anonId;
473
+ headers["x-anon-id"] = this.anonId;
413
474
  if (this.sessionId)
414
- headers['x-session-id'] = this.sessionId;
475
+ headers["x-session-id"] = this.sessionId;
415
476
  try {
416
477
  const response = await this.searchApi.v1MultiSearchPost(this.storeId, this.readSecret, { searches }, { headers });
417
478
  const data = response.data;
@@ -434,8 +495,12 @@ class SeekoraClient {
434
495
  const facetsMap = {};
435
496
  if (Array.isArray(facets)) {
436
497
  facets.forEach((facet, index) => {
437
- const fieldName = facet?.field_name || facet?.fieldName || facet?.name || `facet_${index}`;
438
- if (fieldName && (facet?.counts || facet?.values || Array.isArray(facet))) {
498
+ const fieldName = facet?.field_name ||
499
+ facet?.fieldName ||
500
+ facet?.name ||
501
+ `facet_${index}`;
502
+ if (fieldName &&
503
+ (facet?.counts || facet?.values || Array.isArray(facet))) {
439
504
  facetsMap[fieldName] = {
440
505
  field_name: fieldName,
441
506
  counts: facet.counts || facet.values || [],
@@ -456,33 +521,39 @@ class SeekoraClient {
456
521
  // Auto-track search event per sub-query
457
522
  if (this.autoTrackSearch) {
458
523
  this.trackSearch({
459
- query: queries[i]?.q || '*',
460
- resultsCount: result.totalResults,
461
- context,
524
+ query: queries[i]?.q || "*",
525
+ numFound: result.totalResults,
462
526
  }).catch((err) => {
463
- this.logger.warn('Failed to auto-track multi-search event', { index: i, error: err.message });
527
+ this.logger.warn("Failed to auto-track multi-search event", {
528
+ index: i,
529
+ error: err.message,
530
+ });
464
531
  });
465
532
  }
466
533
  // Auto-track impressions per sub-query
467
- if (this.clientConfig.autoTrackImpressions !== false && result.results.length > 0) {
534
+ if (this.clientConfig.autoTrackImpressions !== false &&
535
+ result.results.length > 0) {
468
536
  this.autoTrackSearchImpressions(result, context).catch((err) => {
469
- this.logger.warn('Failed to auto-track multi-search impressions', { index: i, error: err.message });
537
+ this.logger.warn("Failed to auto-track multi-search impressions", {
538
+ index: i,
539
+ error: err.message,
540
+ });
470
541
  });
471
542
  }
472
543
  }
473
- this.logger.info('Multi-search completed', {
544
+ this.logger.info("Multi-search completed", {
474
545
  queryCount: queries.length,
475
- totalResults: resultsList.map(r => r.totalResults),
546
+ totalResults: resultsList.map((r) => r.totalResults),
476
547
  });
477
548
  return { results: resultsList };
478
549
  }
479
550
  catch (error) {
480
- this.logger.error('Multi-search failed', {
551
+ this.logger.error("Multi-search failed", {
481
552
  queryCount: queries.length,
482
553
  error: error.message,
483
554
  status: error.response?.status,
484
555
  });
485
- throw this.handleError(error, 'multiSearch');
556
+ throw this.handleError(error, "multiSearch");
486
557
  }
487
558
  }
488
559
  /**
@@ -502,26 +573,26 @@ class SeekoraClient {
502
573
  * @returns Suggestion hits array, or QuerySuggestionsFullResponse when returnFullResponse is true
503
574
  */
504
575
  async getSuggestions(query, options) {
505
- this.logger.verbose('Getting query suggestions', { query, options });
576
+ this.logger.verbose("Getting query suggestions", { query, options });
506
577
  try {
507
578
  const headers = {
508
- 'x-storeid': this.storeId,
509
- 'x-storesecret': this.readSecret,
579
+ "x-storeid": this.storeId,
580
+ "x-storesecret": this.readSecret,
510
581
  };
511
582
  if (this.userId)
512
- headers['x-user-id'] = this.userId;
583
+ headers["x-user-id"] = this.userId;
513
584
  if (this.anonId)
514
- headers['x-anon-id'] = this.anonId;
585
+ headers["x-anon-id"] = this.anonId;
515
586
  if (this.sessionId)
516
- headers['x-session-id'] = this.sessionId;
587
+ headers["x-session-id"] = this.sessionId;
517
588
  const defaults = this.clientConfig?.suggestionsDefaults;
518
- this.logger.verbose('Using GET endpoint', {
519
- endpoint: '/api/v1/suggestions/queries',
589
+ this.logger.verbose("Using GET endpoint", {
590
+ endpoint: "/api/v1/suggestions/queries",
520
591
  query,
521
592
  hasFilteredTabs: !!(options?.filtered_tabs && options.filtered_tabs.length > 0),
522
593
  });
523
594
  const analyticsTags = Array.isArray(options?.analytics_tags)
524
- ? options.analytics_tags.join(',')
595
+ ? options.analytics_tags.join(",")
525
596
  : options?.analytics_tags;
526
597
  // Encode filtered_tabs as JSON string for URL parameter
527
598
  const filteredTabsParam = options?.filtered_tabs
@@ -529,7 +600,9 @@ class SeekoraClient {
529
600
  : undefined;
530
601
  const response = await this.suggestionsApi.v1SuggestionsQueriesGet(this.storeId, this.readSecret, this.userId, this.anonId, this.sessionId, query, // query parameter
531
602
  undefined, // q parameter (alias for query)
532
- options?.hitsPerPage ?? defaults?.hitsPerPage ?? 5, options?.page, analyticsTags, options?.tags_match_mode, options?.include_categories, options?.include_facets, options?.include_dropdown_recommendations ?? defaults?.include_dropdown_recommendations, options?.include_dropdown_product_list ?? defaults?.include_dropdown_product_list, options?.include_filtered_tabs ?? defaults?.include_filtered_tabs, undefined, // include_empty_query_recommendations
603
+ options?.hitsPerPage ?? defaults?.hitsPerPage ?? 5, options?.page, analyticsTags, options?.tags_match_mode, options?.include_categories, options?.include_facets, options?.include_dropdown_recommendations ??
604
+ defaults?.include_dropdown_recommendations, options?.include_dropdown_product_list ??
605
+ defaults?.include_dropdown_product_list, options?.include_filtered_tabs ?? defaults?.include_filtered_tabs, undefined, // include_empty_query_recommendations
533
606
  options?.max_categories, options?.max_facets, options?.min_popularity, (options?.time_range ?? defaults?.time_range), options?.disable_typo_tolerance, filteredTabsParam, // filtered_tabs as JSON string
534
607
  undefined, // userId (using header instead)
535
608
  undefined, // anonId (using header instead)
@@ -538,8 +611,9 @@ class SeekoraClient {
538
611
  const responseData = response.data?.data || response.data;
539
612
  const suggestions = responseData?.results?.[0]?.hits || responseData?.hits || [];
540
613
  if (options?.returnFullResponse) {
541
- const extensions = responseData?.results?.[1]?.extensions ?? responseData?.results?.[0]?.extensions;
542
- this.logger.info('Query suggestions retrieved (GET, full response)', {
614
+ const extensions = responseData?.results?.[1]?.extensions ??
615
+ responseData?.results?.[0]?.extensions;
616
+ this.logger.info("Query suggestions retrieved (GET, full response)", {
543
617
  query,
544
618
  count: suggestions.length,
545
619
  status: response.status,
@@ -551,7 +625,7 @@ class SeekoraClient {
551
625
  raw: responseData,
552
626
  };
553
627
  }
554
- this.logger.info('Query suggestions retrieved (GET)', {
628
+ this.logger.info("Query suggestions retrieved (GET)", {
555
629
  query,
556
630
  count: suggestions.length,
557
631
  status: response.status,
@@ -559,8 +633,11 @@ class SeekoraClient {
559
633
  return suggestions;
560
634
  }
561
635
  catch (error) {
562
- this.logger.error('Failed to get suggestions', { query, error: error.message });
563
- throw this.handleError(error, 'getSuggestions');
636
+ this.logger.error("Failed to get suggestions", {
637
+ query,
638
+ error: error.message,
639
+ });
640
+ throw this.handleError(error, "getSuggestions");
564
641
  }
565
642
  }
566
643
  /**
@@ -568,27 +645,27 @@ class SeekoraClient {
568
645
  * Uses GET /api/v1/stores/{storeId}/query-suggestions/config via StoreManagementApi.
569
646
  */
570
647
  async getSuggestionsConfig() {
571
- this.logger.verbose('Getting suggestions configuration');
648
+ this.logger.verbose("Getting suggestions configuration");
572
649
  try {
573
650
  const headers = {
574
- 'x-storeid': this.storeId,
575
- 'x-storesecret': this.readSecret,
651
+ "x-storeid": this.storeId,
652
+ "x-storesecret": this.readSecret,
576
653
  };
577
654
  try {
578
655
  const response = await this.storeManagementApi.apiV1StoresQuerySuggestionsConfigGet(this.storeId, this.readSecret, { headers });
579
656
  const data = response.data?.data;
580
657
  const config = data?.metadata?.config ?? data?.config ?? data;
581
- this.logger.info('Suggestions configuration retrieved', {
658
+ this.logger.info("Suggestions configuration retrieved", {
582
659
  configKeys: config ? Object.keys(config) : [],
583
660
  status: response.status,
584
661
  });
585
662
  return config;
586
663
  }
587
664
  catch (e) {
588
- this.logger.verbose('Store config endpoint failed, trying legacy endpoint', { message: e?.message });
665
+ this.logger.verbose("Store config endpoint failed, trying legacy endpoint", { message: e?.message });
589
666
  const response = await this.suggestionsApi.v1SuggestionsConfigGet(this.storeId, this.readSecret, { headers });
590
667
  const config = response.data?.data;
591
- this.logger.info('Suggestions configuration retrieved (legacy)', {
668
+ this.logger.info("Suggestions configuration retrieved (legacy)", {
592
669
  configKeys: config ? Object.keys(config) : [],
593
670
  status: response.status,
594
671
  });
@@ -596,8 +673,10 @@ class SeekoraClient {
596
673
  }
597
674
  }
598
675
  catch (error) {
599
- this.logger.error('Failed to get suggestions config', { error: error.message });
600
- throw this.handleError(error, 'getSuggestionsConfig');
676
+ this.logger.error("Failed to get suggestions config", {
677
+ error: error.message,
678
+ });
679
+ throw this.handleError(error, "getSuggestionsConfig");
601
680
  }
602
681
  }
603
682
  /**
@@ -605,37 +684,37 @@ class SeekoraClient {
605
684
  * Returns store search configuration and onboarding status
606
685
  */
607
686
  async getConfig() {
608
- this.logger.verbose('Getting store configuration');
687
+ this.logger.verbose("Getting store configuration");
609
688
  try {
610
- this.logger.verbose('Fetching store configuration', {
611
- endpoint: `/api/v1/stores/${this.storeId}/config`
689
+ this.logger.verbose("Fetching store configuration", {
690
+ endpoint: `/api/v1/stores/${this.storeId}/config`,
612
691
  });
613
692
  // Build headers with personalization support
614
693
  const headers = {
615
- 'x-storeid': this.storeId,
616
- 'x-storesecret': this.readSecret,
694
+ "x-storeid": this.storeId,
695
+ "x-storesecret": this.readSecret,
617
696
  };
618
697
  // Add personalization headers if available
619
698
  if (this.userId) {
620
- headers['x-user-id'] = this.userId;
699
+ headers["x-user-id"] = this.userId;
621
700
  }
622
701
  if (this.anonId) {
623
- headers['x-anon-id'] = this.anonId;
702
+ headers["x-anon-id"] = this.anonId;
624
703
  }
625
704
  if (this.sessionId) {
626
- headers['x-session-id'] = this.sessionId;
705
+ headers["x-session-id"] = this.sessionId;
627
706
  }
628
707
  const response = await this.storeManagementApi.apiV1StoresConfigGet(this.storeId, this.readSecret, { headers });
629
708
  const config = response.data?.data;
630
- this.logger.info('Store configuration retrieved', {
709
+ this.logger.info("Store configuration retrieved", {
631
710
  status: response.status,
632
- hasConfig: !!config
711
+ hasConfig: !!config,
633
712
  });
634
713
  return config;
635
714
  }
636
715
  catch (error) {
637
- this.logger.error('Failed to get store config', { error: error.message });
638
- throw this.handleError(error, 'getConfig');
716
+ this.logger.error("Failed to get store config", { error: error.message });
717
+ throw this.handleError(error, "getConfig");
639
718
  }
640
719
  }
641
720
  /**
@@ -648,7 +727,7 @@ class SeekoraClient {
648
727
  * @returns Store information object
649
728
  */
650
729
  async getStoreInfo() {
651
- this.logger.verbose('Getting store information');
730
+ this.logger.verbose("Getting store information");
652
731
  try {
653
732
  const configResponse = await this.getConfig();
654
733
  // Extract store info from config response
@@ -690,51 +769,55 @@ class SeekoraClient {
690
769
  storeInfo.config = config;
691
770
  }
692
771
  }
693
- this.logger.info('Store information retrieved', {
772
+ this.logger.info("Store information retrieved", {
694
773
  storeId: storeInfo.storeId,
695
- storeName: storeInfo.storeName
774
+ storeName: storeInfo.storeName,
696
775
  });
697
776
  return storeInfo;
698
777
  }
699
778
  catch (error) {
700
- this.logger.error('Failed to get store info', { error: error.message });
701
- throw this.handleError(error, 'getStoreInfo');
779
+ this.logger.error("Failed to get store info", { error: error.message });
780
+ throw this.handleError(error, "getStoreInfo");
702
781
  }
703
782
  }
704
783
  /**
705
784
  * Update store configuration (requires write secret)
706
785
  */
707
786
  async updateConfig(config) {
708
- this.logger.verbose('Updating store configuration', { configKeys: Object.keys(config) });
787
+ this.logger.verbose("Updating store configuration", {
788
+ configKeys: Object.keys(config),
789
+ });
709
790
  if (!this.writeSecret) {
710
- this.logger.error('Write secret required but not provided', {
711
- operation: 'updateConfig',
712
- storeId: this.storeId
791
+ this.logger.error("Write secret required but not provided", {
792
+ operation: "updateConfig",
793
+ storeId: this.storeId,
713
794
  });
714
- throw new Error('Write secret is required for updateConfig');
795
+ throw new Error("Write secret is required for updateConfig");
715
796
  }
716
797
  try {
717
- this.logger.verbose('Updating store configuration', {
798
+ this.logger.verbose("Updating store configuration", {
718
799
  endpoint: `/api/v1/stores/${this.storeId}/config`,
719
- configKeys: Object.keys(config)
800
+ configKeys: Object.keys(config),
720
801
  });
721
802
  // Build headers with write secret
722
803
  const headers = {
723
- 'x-storeid': this.storeId,
724
- 'x-storesecret': this.readSecret,
725
- 'x-store-write-secret': this.writeSecret,
804
+ "x-storeid": this.storeId,
805
+ "x-storesecret": this.readSecret,
806
+ "x-store-write-secret": this.writeSecret,
726
807
  };
727
808
  const response = await this.storeManagementApi.apiV1StoresConfigPut(this.storeId, this.writeSecret, config, { headers });
728
809
  const updatedConfig = response.data?.data;
729
- this.logger.info('Store configuration updated successfully', {
810
+ this.logger.info("Store configuration updated successfully", {
730
811
  status: response.status,
731
- hasConfig: !!updatedConfig
812
+ hasConfig: !!updatedConfig,
732
813
  });
733
814
  return updatedConfig;
734
815
  }
735
816
  catch (error) {
736
- this.logger.error('Failed to update store config', { error: error.message });
737
- throw this.handleError(error, 'updateConfig');
817
+ this.logger.error("Failed to update store config", {
818
+ error: error.message,
819
+ });
820
+ throw this.handleError(error, "updateConfig");
738
821
  }
739
822
  }
740
823
  /**
@@ -742,38 +825,44 @@ class SeekoraClient {
742
825
  * Uses PUT /api/v1/stores/{xStoreID}/query-suggestions/config via StoreManagementApi.
743
826
  */
744
827
  async updateQuerySuggestionsConfig(config) {
745
- this.logger.verbose('Updating query suggestions configuration', { configKeys: Object.keys(config) });
828
+ this.logger.verbose("Updating query suggestions configuration", {
829
+ configKeys: Object.keys(config),
830
+ });
746
831
  if (!this.writeSecret) {
747
- this.logger.error('Write secret required but not provided', {
748
- operation: 'updateQuerySuggestionsConfig',
832
+ this.logger.error("Write secret required but not provided", {
833
+ operation: "updateQuerySuggestionsConfig",
749
834
  storeId: this.storeId,
750
835
  });
751
- throw new Error('Write secret is required for updateQuerySuggestionsConfig');
836
+ throw new Error("Write secret is required for updateQuerySuggestionsConfig");
752
837
  }
753
838
  try {
754
839
  const headers = {
755
- 'x-storeid': this.storeId,
756
- 'x-store-write-secret': this.writeSecret,
757
- 'Content-Type': 'application/json',
840
+ "x-storeid": this.storeId,
841
+ "x-store-write-secret": this.writeSecret,
842
+ "Content-Type": "application/json",
758
843
  };
759
844
  try {
760
845
  const response = await this.storeManagementApi.apiV1StoresQuerySuggestionsConfigPut(this.storeId, this.writeSecret, config, { headers });
761
846
  const data = response.data?.data;
762
847
  const updatedConfig = data?.metadata?.config ?? data?.config ?? data;
763
- this.logger.info('Query suggestions configuration updated successfully', {
848
+ this.logger.info("Query suggestions configuration updated successfully", {
764
849
  status: response.status,
765
850
  hasConfig: !!updatedConfig,
766
851
  });
767
852
  return updatedConfig;
768
853
  }
769
854
  catch (apiError) {
770
- this.logger.verbose('PUT via generated API failed, using direct HTTP', { message: apiError?.message });
771
- const axiosInstance = this.storeManagementApi.axios ?? this.suggestionsApi.axios ?? axios_1.default;
772
- const baseUrl = this.config.basePath ?? 'https://api.seekora.com/api';
855
+ this.logger.verbose("PUT via generated API failed, using direct HTTP", {
856
+ message: apiError?.message,
857
+ });
858
+ const axiosInstance = this.storeManagementApi.axios ??
859
+ this.suggestionsApi.axios ??
860
+ axios_1.default;
861
+ const baseUrl = this.config.basePath ?? "https://api.seekora.com/api";
773
862
  const response = await axiosInstance.put(`${baseUrl}/v1/stores/${this.storeId}/query-suggestions/config`, config, { headers });
774
863
  const data = response.data?.data;
775
864
  const updatedConfig = data?.metadata?.config ?? data?.config ?? data;
776
- this.logger.info('Query suggestions configuration updated successfully', {
865
+ this.logger.info("Query suggestions configuration updated successfully", {
777
866
  status: response.status,
778
867
  hasConfig: !!updatedConfig,
779
868
  });
@@ -781,29 +870,35 @@ class SeekoraClient {
781
870
  }
782
871
  }
783
872
  catch (error) {
784
- this.logger.error('Failed to update query suggestions config', { error: error.message });
785
- throw this.handleError(error, 'updateQuerySuggestionsConfig');
873
+ this.logger.error("Failed to update query suggestions config", {
874
+ error: error.message,
875
+ });
876
+ throw this.handleError(error, "updateQuerySuggestionsConfig");
786
877
  }
787
878
  }
788
879
  /**
789
880
  * Get configuration schema
790
881
  */
791
882
  async getConfigSchema() {
792
- this.logger.verbose('Getting configuration schema');
883
+ this.logger.verbose("Getting configuration schema");
793
884
  try {
794
885
  const response = await this.storeManagementApi.apiV1StoresConfigSchemaGet(this.storeId, this.readSecret, {
795
886
  headers: {
796
- 'x-storeid': this.storeId,
797
- 'x-storesecret': this.readSecret,
798
- }
887
+ "x-storeid": this.storeId,
888
+ "x-storesecret": this.readSecret,
889
+ },
799
890
  });
800
891
  const schema = response.data?.data;
801
- this.logger.verbose('Configuration schema retrieved', { schemaKeys: schema ? Object.keys(schema) : [] });
892
+ this.logger.verbose("Configuration schema retrieved", {
893
+ schemaKeys: schema ? Object.keys(schema) : [],
894
+ });
802
895
  return schema;
803
896
  }
804
897
  catch (error) {
805
- this.logger.error('Failed to get config schema', { error: error.message });
806
- throw this.handleError(error, 'getConfigSchema');
898
+ this.logger.error("Failed to get config schema", {
899
+ error: error.message,
900
+ });
901
+ throw this.handleError(error, "getConfigSchema");
807
902
  }
808
903
  }
809
904
  // ==========================================
@@ -816,16 +911,16 @@ class SeekoraClient {
816
911
  * @returns IndexDocumentResponse with document id and status
817
912
  */
818
913
  async indexDocument(document) {
819
- this.logger.verbose('Indexing document', {
914
+ this.logger.verbose("Indexing document", {
820
915
  hasCustomId: !!document.id,
821
- documentKeys: Object.keys(document.data)
916
+ documentKeys: Object.keys(document.data),
822
917
  });
823
918
  if (!this.writeSecret) {
824
- this.logger.error('Write secret required but not provided', {
825
- operation: 'indexDocument',
826
- storeId: this.storeId
919
+ this.logger.error("Write secret required but not provided", {
920
+ operation: "indexDocument",
921
+ storeId: this.storeId,
827
922
  });
828
- throw new Error('Write secret is required for indexDocument');
923
+ throw new Error("Write secret is required for indexDocument");
829
924
  }
830
925
  try {
831
926
  const request = {
@@ -834,20 +929,23 @@ class SeekoraClient {
834
929
  };
835
930
  const response = await this.storeManagementApi.apiV1StoresDocumentsPost(this.storeId, this.writeSecret, request);
836
931
  const responseData = response.data?.data;
837
- this.logger.info('Document indexed successfully', {
932
+ this.logger.info("Document indexed successfully", {
838
933
  id: responseData?.id || document.id,
839
- status: response.status
934
+ status: response.status,
840
935
  });
841
936
  return {
842
- id: responseData?.id || document.id || '',
843
- success: responseData?.status === 'success' || responseData?.status === 'inserted' || responseData?.status === 'updated' || true,
937
+ id: responseData?.id || document.id || "",
938
+ success: responseData?.status === "success" ||
939
+ responseData?.status === "inserted" ||
940
+ responseData?.status === "updated" ||
941
+ true,
844
942
  message: responseData?.message,
845
943
  data: responseData,
846
944
  };
847
945
  }
848
946
  catch (error) {
849
- this.logger.error('Failed to index document', { error: error.message });
850
- throw this.handleError(error, 'indexDocument');
947
+ this.logger.error("Failed to index document", { error: error.message });
948
+ throw this.handleError(error, "indexDocument");
851
949
  }
852
950
  }
853
951
  /**
@@ -857,20 +955,20 @@ class SeekoraClient {
857
955
  * @returns BulkIndexResponse with success/error counts
858
956
  */
859
957
  async indexDocuments(documents) {
860
- this.logger.verbose('Bulk indexing documents', {
958
+ this.logger.verbose("Bulk indexing documents", {
861
959
  count: documents.length,
862
- actions: documents.map(d => d.action || 'upsert')
960
+ actions: documents.map((d) => d.action || "upsert"),
863
961
  });
864
962
  if (!this.writeSecret) {
865
- this.logger.error('Write secret required but not provided', {
866
- operation: 'indexDocuments',
867
- storeId: this.storeId
963
+ this.logger.error("Write secret required but not provided", {
964
+ operation: "indexDocuments",
965
+ storeId: this.storeId,
868
966
  });
869
- throw new Error('Write secret is required for indexDocuments');
967
+ throw new Error("Write secret is required for indexDocuments");
870
968
  }
871
969
  try {
872
970
  const request = {
873
- documents: documents.map(doc => ({
971
+ documents: documents.map((doc) => ({
874
972
  id: doc.id,
875
973
  action: doc.action,
876
974
  data: doc.data,
@@ -878,31 +976,35 @@ class SeekoraClient {
878
976
  };
879
977
  const response = await this.storeManagementApi.apiV1StoresDocumentsBulkPost(this.storeId, this.writeSecret, request);
880
978
  const responseData = response.data?.data;
881
- this.logger.info('Bulk document indexing completed', {
979
+ this.logger.info("Bulk document indexing completed", {
882
980
  successCount: responseData?.successCount || 0,
883
981
  errorCount: responseData?.errorCount || 0,
884
- status: response.status
982
+ status: response.status,
885
983
  });
886
984
  return {
887
985
  success_count: responseData?.successCount || documents.length,
888
986
  error_count: responseData?.errorCount || 0,
889
- results: responseData?.results?.map(r => ({
987
+ results: responseData?.results?.map((r) => ({
890
988
  id: r.id,
891
- success: r.status === 'success' || r.status === 'inserted' || r.status === 'updated' || !r.error,
989
+ success: r.status === "success" ||
990
+ r.status === "inserted" ||
991
+ r.status === "updated" ||
992
+ !r.error,
892
993
  error: r.error,
893
- })) || documents.map((doc) => ({
894
- id: doc.id,
895
- success: true,
896
- })),
994
+ })) ||
995
+ documents.map((doc) => ({
996
+ id: doc.id,
997
+ success: true,
998
+ })),
897
999
  data: responseData,
898
1000
  };
899
1001
  }
900
1002
  catch (error) {
901
- this.logger.error('Failed to bulk index documents', {
1003
+ this.logger.error("Failed to bulk index documents", {
902
1004
  count: documents.length,
903
- error: error.message
1005
+ error: error.message,
904
1006
  });
905
- throw this.handleError(error, 'indexDocuments');
1007
+ throw this.handleError(error, "indexDocuments");
906
1008
  }
907
1009
  }
908
1010
  /**
@@ -911,27 +1013,27 @@ class SeekoraClient {
911
1013
  * @param documentId - Document ID to delete
912
1014
  */
913
1015
  async deleteDocument(documentId) {
914
- this.logger.verbose('Deleting document', { documentId });
1016
+ this.logger.verbose("Deleting document", { documentId });
915
1017
  if (!this.writeSecret) {
916
- this.logger.error('Write secret required but not provided', {
917
- operation: 'deleteDocument',
918
- storeId: this.storeId
1018
+ this.logger.error("Write secret required but not provided", {
1019
+ operation: "deleteDocument",
1020
+ storeId: this.storeId,
919
1021
  });
920
- throw new Error('Write secret is required for deleteDocument');
1022
+ throw new Error("Write secret is required for deleteDocument");
921
1023
  }
922
1024
  try {
923
1025
  const response = await this.storeManagementApi.apiV1StoresDocumentsDocumentIDDelete(this.storeId, this.writeSecret, documentId);
924
- this.logger.info('Document deleted successfully', {
1026
+ this.logger.info("Document deleted successfully", {
925
1027
  documentId,
926
- status: response.status
1028
+ status: response.status,
927
1029
  });
928
1030
  }
929
1031
  catch (error) {
930
- this.logger.error('Failed to delete document', {
1032
+ this.logger.error("Failed to delete document", {
931
1033
  documentId,
932
- error: error.message
1034
+ error: error.message,
933
1035
  });
934
- throw this.handleError(error, 'deleteDocument');
1036
+ throw this.handleError(error, "deleteDocument");
935
1037
  }
936
1038
  }
937
1039
  // ==================
@@ -970,20 +1072,20 @@ class SeekoraClient {
970
1072
  * });
971
1073
  */
972
1074
  async createSchema(request) {
973
- this.logger.verbose('Creating/updating schema', {
1075
+ this.logger.verbose("Creating/updating schema", {
974
1076
  fieldCount: request.fields.length,
975
- mode: request.mode || 'additive'
1077
+ mode: request.mode || "additive",
976
1078
  });
977
1079
  if (!this.writeSecret) {
978
- this.logger.error('Write secret required but not provided', {
979
- operation: 'createSchema',
980
- storeId: this.storeId
1080
+ this.logger.error("Write secret required but not provided", {
1081
+ operation: "createSchema",
1082
+ storeId: this.storeId,
981
1083
  });
982
- throw new Error('Write secret is required for createSchema');
1084
+ throw new Error("Write secret is required for createSchema");
983
1085
  }
984
1086
  try {
985
1087
  const apiRequest = {
986
- fields: request.fields.map(f => ({
1088
+ fields: request.fields.map((f) => ({
987
1089
  name: f.name,
988
1090
  type: f.type,
989
1091
  facet: f.facet,
@@ -1000,15 +1102,15 @@ class SeekoraClient {
1000
1102
  };
1001
1103
  const response = await this.storeManagementApi.apiV1StoresSchemaPost(this.storeId, this.writeSecret, apiRequest);
1002
1104
  const responseData = response.data;
1003
- this.logger.info('Schema created/updated successfully', {
1105
+ this.logger.info("Schema created/updated successfully", {
1004
1106
  name: responseData.data?.name,
1005
- fieldCount: responseData.data?.fields?.length
1107
+ fieldCount: responseData.data?.fields?.length,
1006
1108
  });
1007
1109
  return {
1008
- name: responseData.data?.name || '',
1009
- fields: (responseData.data?.fields || []).map(f => ({
1010
- name: f.name || '',
1011
- type: f.type || 'string',
1110
+ name: responseData.data?.name || "",
1111
+ fields: (responseData.data?.fields || []).map((f) => ({
1112
+ name: f.name || "",
1113
+ type: f.type || "string",
1012
1114
  facet: f.facet,
1013
1115
  index: f.index,
1014
1116
  optional: f.optional,
@@ -1022,8 +1124,10 @@ class SeekoraClient {
1022
1124
  };
1023
1125
  }
1024
1126
  catch (error) {
1025
- this.logger.error('Failed to create/update schema', { error: error.message });
1026
- throw this.handleError(error, 'createSchema');
1127
+ this.logger.error("Failed to create/update schema", {
1128
+ error: error.message,
1129
+ });
1130
+ throw this.handleError(error, "createSchema");
1027
1131
  }
1028
1132
  }
1029
1133
  /**
@@ -1037,20 +1141,20 @@ class SeekoraClient {
1037
1141
  * console.log('Documents:', schema.numDocuments);
1038
1142
  */
1039
1143
  async getSchema() {
1040
- this.logger.verbose('Getting schema', { storeId: this.storeId });
1144
+ this.logger.verbose("Getting schema", { storeId: this.storeId });
1041
1145
  try {
1042
1146
  const response = await this.storeManagementApi.apiV1StoresSchemaGet(this.storeId, this.readSecret);
1043
1147
  const responseData = response.data;
1044
- this.logger.info('Schema retrieved successfully', {
1148
+ this.logger.info("Schema retrieved successfully", {
1045
1149
  name: responseData.data?.name,
1046
1150
  fieldCount: responseData.data?.fields?.length,
1047
- numDocuments: responseData.data?.num_documents
1151
+ numDocuments: responseData.data?.num_documents,
1048
1152
  });
1049
1153
  return {
1050
- name: responseData.data?.name || '',
1051
- fields: (responseData.data?.fields || []).map(f => ({
1052
- name: f.name || '',
1053
- type: f.type || 'string',
1154
+ name: responseData.data?.name || "",
1155
+ fields: (responseData.data?.fields || []).map((f) => ({
1156
+ name: f.name || "",
1157
+ type: f.type || "string",
1054
1158
  facet: f.facet,
1055
1159
  index: f.index,
1056
1160
  optional: f.optional,
@@ -1064,8 +1168,8 @@ class SeekoraClient {
1064
1168
  };
1065
1169
  }
1066
1170
  catch (error) {
1067
- this.logger.error('Failed to get schema', { error: error.message });
1068
- throw this.handleError(error, 'getSchema');
1171
+ this.logger.error("Failed to get schema", { error: error.message });
1172
+ throw this.handleError(error, "getSchema");
1069
1173
  }
1070
1174
  }
1071
1175
  /**
@@ -1081,28 +1185,28 @@ class SeekoraClient {
1081
1185
  * console.log(`Cleared ${result.deletedCount} documents`);
1082
1186
  */
1083
1187
  async clearDocuments() {
1084
- this.logger.verbose('Clearing all documents', { storeId: this.storeId });
1188
+ this.logger.verbose("Clearing all documents", { storeId: this.storeId });
1085
1189
  if (!this.writeSecret) {
1086
- this.logger.error('Write secret required but not provided', {
1087
- operation: 'clearDocuments',
1088
- storeId: this.storeId
1190
+ this.logger.error("Write secret required but not provided", {
1191
+ operation: "clearDocuments",
1192
+ storeId: this.storeId,
1089
1193
  });
1090
- throw new Error('Write secret is required for clearDocuments');
1194
+ throw new Error("Write secret is required for clearDocuments");
1091
1195
  }
1092
1196
  try {
1093
1197
  const response = await this.storeManagementApi.apiV1StoresDocumentsDelete(this.storeId, this.writeSecret);
1094
1198
  const responseData = response.data;
1095
- this.logger.info('Documents cleared successfully', {
1096
- deletedCount: responseData.data?.deleted_count
1199
+ this.logger.info("Documents cleared successfully", {
1200
+ deletedCount: responseData.data?.deleted_count,
1097
1201
  });
1098
1202
  return {
1099
1203
  deletedCount: responseData.data?.deleted_count || 0,
1100
- message: responseData.data?.message || 'Documents cleared',
1204
+ message: responseData.data?.message || "Documents cleared",
1101
1205
  };
1102
1206
  }
1103
1207
  catch (error) {
1104
- this.logger.error('Failed to clear documents', { error: error.message });
1105
- throw this.handleError(error, 'clearDocuments');
1208
+ this.logger.error("Failed to clear documents", { error: error.message });
1209
+ throw this.handleError(error, "clearDocuments");
1106
1210
  }
1107
1211
  }
1108
1212
  /**
@@ -1111,14 +1215,16 @@ class SeekoraClient {
1111
1215
  async collectContextAsync() {
1112
1216
  try {
1113
1217
  this.cachedBrowserContext = await this.contextCollector.collect();
1114
- this.logger.verbose('Browser context collected', {
1218
+ this.logger.verbose("Browser context collected", {
1115
1219
  hasFingerprint: !!this.cachedBrowserContext?.device_fingerprint,
1116
1220
  platform: this.cachedBrowserContext?.platform,
1117
1221
  timezone: this.cachedBrowserContext?.timezone,
1118
1222
  });
1119
1223
  }
1120
1224
  catch (error) {
1121
- this.logger.warn('Failed to collect browser context', { error: error.message });
1225
+ this.logger.warn("Failed to collect browser context", {
1226
+ error: error.message,
1227
+ });
1122
1228
  }
1123
1229
  }
1124
1230
  /**
@@ -1142,9 +1248,9 @@ class SeekoraClient {
1142
1248
  async sendEventDirect(payload) {
1143
1249
  const response = await this.analyticsApi.analyticsEventPost(this.storeId, this.readSecret, payload, {
1144
1250
  headers: {
1145
- 'x-storeid': this.storeId,
1146
- 'x-storesecret': this.readSecret,
1147
- }
1251
+ "x-storeid": this.storeId,
1252
+ "x-storesecret": this.readSecret,
1253
+ },
1148
1254
  });
1149
1255
  if (response.status >= 400) {
1150
1256
  throw new Error(`Failed to send event: ${response.status}`);
@@ -1155,13 +1261,13 @@ class SeekoraClient {
1155
1261
  */
1156
1262
  async sendEventsBatchDirect(payloads) {
1157
1263
  const batchRequest = {
1158
- events: payloads
1264
+ events: payloads,
1159
1265
  };
1160
1266
  const response = await this.analyticsApi.analyticsBatchPost(this.storeId, this.readSecret, batchRequest, {
1161
1267
  headers: {
1162
- 'x-storeid': this.storeId,
1163
- 'x-storesecret': this.readSecret,
1164
- }
1268
+ "x-storeid": this.storeId,
1269
+ "x-storesecret": this.readSecret,
1270
+ },
1165
1271
  });
1166
1272
  if (response.status >= 400) {
1167
1273
  throw new Error(`Failed to send batch events: ${response.status}`);
@@ -1176,14 +1282,14 @@ class SeekoraClient {
1176
1282
  if (items.length === 0) {
1177
1283
  return;
1178
1284
  }
1179
- this.logger.verbose('Auto-tracking search impressions', {
1285
+ this.logger.verbose("Auto-tracking search impressions", {
1180
1286
  itemCount: items.length,
1181
1287
  query: searchResponse.query || searchResponse.data?.data?.query,
1182
- searchId: context.searchId
1288
+ searchId: context.searchId,
1183
1289
  });
1184
1290
  // Create batch impression events
1185
1291
  const impressionEvents = items.map((item, index) => ({
1186
- event_name: 'view',
1292
+ event_name: "view",
1187
1293
  event_id: (0, utils_1.generateUUID)(),
1188
1294
  event_ts: new Date().toISOString(),
1189
1295
  // Link to search
@@ -1199,16 +1305,18 @@ class SeekoraClient {
1199
1305
  position: index + 1,
1200
1306
  query: searchResponse.query || searchResponse.data?.data?.query,
1201
1307
  // Optional metadata
1202
- analytics_tags: ['source:search', 'auto_tracked:true']
1308
+ analytics_tags: ["source:search", "auto_tracked:true"],
1203
1309
  }));
1204
1310
  try {
1205
1311
  await this.trackEvents(impressionEvents);
1206
- this.logger.debug('Auto-tracked search impressions', { count: impressionEvents.length });
1312
+ this.logger.debug("Auto-tracked search impressions", {
1313
+ count: impressionEvents.length,
1314
+ });
1207
1315
  }
1208
1316
  catch (error) {
1209
- this.logger.error('Failed to auto-track impressions', {
1317
+ this.logger.error("Failed to auto-track impressions", {
1210
1318
  error: error.message,
1211
- itemCount: items.length
1319
+ itemCount: items.length,
1212
1320
  });
1213
1321
  throw error;
1214
1322
  }
@@ -1245,19 +1353,22 @@ class SeekoraClient {
1245
1353
  // Format: userKey_eventType_itemId_timestampWindow
1246
1354
  // Item ID is optional (not all events have items)
1247
1355
  const parts = [
1248
- userId.replace(/[^a-zA-Z0-9]/g, '_'), // Sanitize user ID
1249
- eventType.replace(/[^a-zA-Z0-9]/g, '_'), // Sanitize event type
1250
- itemId ? itemId.replace(/[^a-zA-Z0-9]/g, '_') : 'null',
1251
- timestamp.toString()
1356
+ userId.replace(/[^a-zA-Z0-9]/g, "_"), // Sanitize user ID
1357
+ eventType.replace(/[^a-zA-Z0-9]/g, "_"), // Sanitize event type
1358
+ itemId ? itemId.replace(/[^a-zA-Z0-9]/g, "_") : "null",
1359
+ timestamp.toString(),
1252
1360
  ];
1253
- return parts.join('_');
1361
+ return parts.join("_");
1254
1362
  }
1255
1363
  /**
1256
1364
  * Build event payload with identifiers and browser context
1257
1365
  * Ensures user_id or anon_id is present, and sets correlation_id/search_id at top level
1258
1366
  */
1259
1367
  buildEventPayload(event, context) {
1260
- const anonId = event.anonymous_id ?? event.anon_id ?? context?.anonId ?? this.anonId;
1368
+ const anonId = event.anonymous_id ??
1369
+ event.anon_id ??
1370
+ context?.anonId ??
1371
+ this.anonId;
1261
1372
  const payload = {
1262
1373
  ...event,
1263
1374
  // Set identifiers from context or fall back to client defaults
@@ -1265,14 +1376,18 @@ class SeekoraClient {
1265
1376
  anon_id: anonId,
1266
1377
  session_id: event.session_id || context?.sessionId || this.sessionId,
1267
1378
  // Set correlation_id at top level if provided (per identifier spec)
1268
- correlation_id: event.correlation_id || context?.correlationId,
1379
+ correlation_id: event.correlation_id ||
1380
+ context?.correlationId,
1269
1381
  // Set search_id at top level (not just in metadata) if provided (per identifier spec)
1270
1382
  search_id: event.search_id || context?.searchId,
1271
1383
  // V3 analytics: event timestamp (ISO) and anonymous_id alias for v3 backend compatibility
1272
1384
  event_ts: event.event_ts ?? new Date().toISOString(),
1273
1385
  anonymous_id: anonId,
1274
- orgcode: event.orgcode ?? context?.orgcode,
1275
- xstoreid: event.xstoreid ?? context?.xstoreId ?? this.storeId,
1386
+ orgcode: event.orgcode ??
1387
+ context?.orgcode,
1388
+ xstoreid: event.xstoreid ??
1389
+ context?.xstoreId ??
1390
+ this.storeId,
1276
1391
  };
1277
1392
  // Ensure either user_id or anon_id is present
1278
1393
  if (!payload.user_id && !payload.anon_id) {
@@ -1343,21 +1458,22 @@ class SeekoraClient {
1343
1458
  // Generate insert_id for deduplication (industry standard: Segment/Amplitude pattern)
1344
1459
  // Always enable for purchase events (revenue must be accurate)
1345
1460
  // Optional for other events based on config
1346
- const isPurchaseEvent = payload.event_name === 'conversion' &&
1347
- (payload.conversion_type === 'purchase' || payload.conversion_type === 'checkout_completed');
1461
+ const isPurchaseEvent = payload.event_name === "conversion" &&
1462
+ (payload.conversion_type === "purchase" ||
1463
+ payload.conversion_type === "checkout_completed");
1348
1464
  if (this.enableDeduplication || isPurchaseEvent) {
1349
1465
  const userKey = payload.user_id || payload.anon_id || this.anonId;
1350
- const eventType = payload.event_name || 'unknown';
1466
+ const eventType = payload.event_name || "unknown";
1351
1467
  const itemId = payload.clicked_item_id;
1352
1468
  // Use longer window for purchase events (30 days)
1353
1469
  const window = isPurchaseEvent ? 2592000000 : this.deduplicationWindow;
1354
1470
  payload.insert_id = this.generateInsertId(eventType, itemId, userKey, window);
1355
- this.logger.verbose('Generated insert_id for deduplication', {
1471
+ this.logger.verbose("Generated insert_id for deduplication", {
1356
1472
  event_name: eventType,
1357
1473
  item_id: itemId,
1358
1474
  insert_id: payload.insert_id,
1359
1475
  is_purchase: isPurchaseEvent,
1360
- window_ms: window
1476
+ window_ms: window,
1361
1477
  });
1362
1478
  }
1363
1479
  return payload;
@@ -1369,41 +1485,44 @@ class SeekoraClient {
1369
1485
  * @param context - Optional search context for linking events to searches
1370
1486
  */
1371
1487
  async trackEvent(event, context) {
1372
- this.logger.verbose('Tracking analytics event', {
1488
+ this.logger.verbose("Tracking analytics event", {
1373
1489
  eventName: event.event_name,
1374
- hasContext: !!context
1490
+ hasContext: !!context,
1375
1491
  });
1376
1492
  const payload = this.buildEventPayload(event, context);
1377
1493
  // Use event queue if enabled
1378
1494
  if (this.enableEventQueue && this.eventQueue) {
1379
1495
  this.eventQueue.enqueue(payload);
1380
- this.logger.verbose('Event queued for sending', {
1496
+ this.logger.verbose("Event queued for sending", {
1381
1497
  eventName: event.event_name,
1382
- queueSize: this.eventQueue.size()
1498
+ queueSize: this.eventQueue.size(),
1383
1499
  });
1384
1500
  return;
1385
1501
  }
1386
1502
  try {
1387
- this.logger.verbose('Sending analytics event', {
1388
- endpoint: '/api/analytics/event',
1389
- eventName: event.event_name
1503
+ this.logger.verbose("Sending analytics event", {
1504
+ endpoint: "/api/analytics/event",
1505
+ eventName: event.event_name,
1390
1506
  });
1391
1507
  // Cast to DataTypesEventPayload for API call (backend accepts extended fields)
1392
1508
  const response = await this.analyticsApi.analyticsEventPost(this.storeId, this.readSecret, payload, // Type assertion needed as generated types may not include correlation_id/search_id yet
1393
1509
  {
1394
1510
  headers: {
1395
- 'x-storeid': this.storeId,
1396
- 'x-storesecret': this.readSecret,
1397
- }
1511
+ "x-storeid": this.storeId,
1512
+ "x-storesecret": this.readSecret,
1513
+ },
1398
1514
  });
1399
- this.logger.verbose('Analytics event tracked successfully', {
1515
+ this.logger.verbose("Analytics event tracked successfully", {
1400
1516
  eventName: event.event_name,
1401
- status: response.status
1517
+ status: response.status,
1402
1518
  });
1403
1519
  }
1404
1520
  catch (error) {
1405
- this.logger.error('Failed to track event', { eventName: event.event_name, error: error.message });
1406
- throw this.handleError(error, 'trackEvent');
1521
+ this.logger.error("Failed to track event", {
1522
+ eventName: event.event_name,
1523
+ error: error.message,
1524
+ });
1525
+ throw this.handleError(error, "trackEvent");
1407
1526
  }
1408
1527
  }
1409
1528
  /**
@@ -1415,67 +1534,113 @@ class SeekoraClient {
1415
1534
  * @param events - Array of event payloads (should have identifiers already set)
1416
1535
  */
1417
1536
  async trackEvents(events, context) {
1418
- this.logger.verbose('Tracking batch analytics events', {
1537
+ this.logger.verbose("Tracking batch analytics events", {
1419
1538
  count: events.length,
1420
- hasContext: !!context
1539
+ hasContext: !!context,
1421
1540
  });
1422
1541
  if (events.length > 100) {
1423
- this.logger.error('Batch size exceeds maximum', { count: events.length, max: 100 });
1424
- throw new Error('Maximum 100 events per batch');
1542
+ this.logger.error("Batch size exceeds maximum", {
1543
+ count: events.length,
1544
+ max: 100,
1545
+ });
1546
+ throw new Error("Maximum 100 events per batch");
1425
1547
  }
1426
1548
  // Build full event payloads with identifiers
1427
- const enrichedEvents = events.map(event => this.buildEventPayload(event, context));
1549
+ const enrichedEvents = events.map((event) => this.buildEventPayload(event, context));
1428
1550
  // Use event queue if enabled
1429
1551
  if (this.enableEventQueue && this.eventQueue) {
1430
1552
  this.eventQueue.enqueueBatch(enrichedEvents);
1431
- this.logger.verbose('Events queued for sending', {
1553
+ this.logger.verbose("Events queued for sending", {
1432
1554
  count: events.length,
1433
- queueSize: this.eventQueue.size()
1555
+ queueSize: this.eventQueue.size(),
1434
1556
  });
1435
1557
  return;
1436
1558
  }
1437
1559
  try {
1438
- this.logger.verbose('Sending batch analytics events', {
1439
- endpoint: '/api/analytics/batch',
1440
- eventCount: events.length
1560
+ this.logger.verbose("Sending batch analytics events", {
1561
+ endpoint: "/api/analytics/batch",
1562
+ eventCount: events.length,
1441
1563
  });
1442
1564
  const batchRequest = {
1443
- events: enrichedEvents // Type assertion for extended payloads
1565
+ events: enrichedEvents, // Type assertion for extended payloads
1444
1566
  };
1445
1567
  const response = await this.analyticsApi.analyticsBatchPost(this.storeId, this.readSecret, batchRequest, {
1446
1568
  headers: {
1447
- 'x-storeid': this.storeId,
1448
- 'x-storesecret': this.readSecret,
1449
- }
1569
+ "x-storeid": this.storeId,
1570
+ "x-storesecret": this.readSecret,
1571
+ },
1450
1572
  });
1451
- this.logger.info('Batch analytics events tracked successfully', {
1573
+ this.logger.info("Batch analytics events tracked successfully", {
1452
1574
  count: events.length,
1453
- status: response.status
1575
+ status: response.status,
1454
1576
  });
1455
1577
  }
1456
1578
  catch (error) {
1457
- this.logger.error('Failed to track batch events', { count: events.length, error: error.message });
1458
- throw this.handleError(error, 'trackEvents');
1579
+ this.logger.error("Failed to track batch events", {
1580
+ count: events.length,
1581
+ error: error.message,
1582
+ });
1583
+ throw this.handleError(error, "trackEvents");
1459
1584
  }
1460
1585
  }
1461
1586
  /**
1462
- * Track a search event
1587
+ * Track a search event (Segment "Products Searched" spec)
1463
1588
  *
1464
1589
  * @param params - Search tracking parameters
1465
1590
  */
1466
1591
  async trackSearch(params) {
1467
- this.logger.verbose('Tracking search event', {
1468
- query: params.query,
1469
- resultsCount: params.resultsCount,
1470
- hasContext: !!params.context
1471
- });
1472
- await this.trackEvent({
1473
- event_name: 'search.performed',
1592
+ const analytics = this.getAnalytics();
1593
+ if (!analytics)
1594
+ return;
1595
+ const properties = {
1474
1596
  query: params.query,
1475
- results_count: params.resultsCount,
1476
- analytics_tags: params.analyticsTags,
1477
- metadata: params.metadata,
1478
- }, params.context);
1597
+ num_found: params.numFound ?? params.resultsCount ?? 0,
1598
+ search_id: params.searchId,
1599
+ latency_ms: params.latencyMs,
1600
+ engine: params.engine,
1601
+ results: (params.results || []).map((r, i) => ({
1602
+ product_id: r.id,
1603
+ sku: r.sku,
1604
+ category: r.category,
1605
+ position: i + 1,
1606
+ })),
1607
+ };
1608
+ await analytics.track("Products Searched", properties);
1609
+ }
1610
+ /**
1611
+ * Track a product click event (Segment "Product Clicked" spec)
1612
+ *
1613
+ * @param params - Product click parameters
1614
+ */
1615
+ async trackProductClick(params) {
1616
+ const analytics = this.getAnalytics();
1617
+ if (!analytics)
1618
+ return;
1619
+ const properties = {
1620
+ product_id: params.productId,
1621
+ sku: params.sku,
1622
+ position: params.position,
1623
+ search_id: params.searchId,
1624
+ };
1625
+ await analytics.track("Product Clicked", properties);
1626
+ }
1627
+ /**
1628
+ * Track a product impression event (Segment "Product Viewed" spec)
1629
+ *
1630
+ * @param params - Product impression parameters
1631
+ */
1632
+ async trackProductImpression(params) {
1633
+ const analytics = this.getAnalytics();
1634
+ if (!analytics)
1635
+ return;
1636
+ const properties = {
1637
+ product_id: params.productId,
1638
+ sku: params.sku,
1639
+ position: params.position,
1640
+ search_id: params.searchId,
1641
+ category: params.category,
1642
+ };
1643
+ await analytics.track("Product Viewed", properties);
1479
1644
  }
1480
1645
  /**
1481
1646
  * Track an impression event
@@ -1483,13 +1648,13 @@ class SeekoraClient {
1483
1648
  * @param params - Impression tracking parameters
1484
1649
  */
1485
1650
  async trackImpression(params) {
1486
- this.logger.verbose('Tracking impression event', {
1651
+ this.logger.verbose("Tracking impression event", {
1487
1652
  itemId: params.itemId,
1488
1653
  position: params.position,
1489
- hasContext: !!params.context
1654
+ hasContext: !!params.context,
1490
1655
  });
1491
1656
  await this.trackEvent({
1492
- event_name: 'impression',
1657
+ event_name: "impression",
1493
1658
  clicked_item_id: params.itemId,
1494
1659
  metadata: {
1495
1660
  position: params.position,
@@ -1500,17 +1665,22 @@ class SeekoraClient {
1500
1665
  }, params.context);
1501
1666
  }
1502
1667
  async trackClick(itemId, position, contextOrSearchId) {
1503
- this.logger.verbose('Tracking click event', {
1668
+ this.logger.verbose("Tracking click event", {
1504
1669
  itemId,
1505
1670
  position,
1506
- hasContext: !!contextOrSearchId
1671
+ hasContext: !!contextOrSearchId,
1507
1672
  });
1508
1673
  // Handle backward compatibility: if third param is string, treat as searchId
1509
- const context = typeof contextOrSearchId === 'string'
1510
- ? { correlationId: '', searchId: contextOrSearchId, anonId: this.anonId, sessionId: this.sessionId }
1674
+ const context = typeof contextOrSearchId === "string"
1675
+ ? {
1676
+ correlationId: "",
1677
+ searchId: contextOrSearchId,
1678
+ anonId: this.anonId,
1679
+ sessionId: this.sessionId,
1680
+ }
1511
1681
  : contextOrSearchId;
1512
1682
  await this.trackEvent({
1513
- event_name: 'product_click',
1683
+ event_name: "product_click",
1514
1684
  clicked_item_id: itemId,
1515
1685
  position,
1516
1686
  metadata: {
@@ -1519,32 +1689,42 @@ class SeekoraClient {
1519
1689
  }, context);
1520
1690
  }
1521
1691
  async trackView(itemId, contextOrSearchId) {
1522
- this.logger.verbose('Tracking view event', {
1692
+ this.logger.verbose("Tracking view event", {
1523
1693
  itemId,
1524
- hasContext: !!contextOrSearchId
1694
+ hasContext: !!contextOrSearchId,
1525
1695
  });
1526
1696
  // Handle backward compatibility: if second param is string, treat as searchId
1527
- const context = typeof contextOrSearchId === 'string'
1528
- ? { correlationId: '', searchId: contextOrSearchId, anonId: this.anonId, sessionId: this.sessionId }
1697
+ const context = typeof contextOrSearchId === "string"
1698
+ ? {
1699
+ correlationId: "",
1700
+ searchId: contextOrSearchId,
1701
+ anonId: this.anonId,
1702
+ sessionId: this.sessionId,
1703
+ }
1529
1704
  : contextOrSearchId;
1530
1705
  await this.trackEvent({
1531
- event_name: 'view',
1706
+ event_name: "view",
1532
1707
  clicked_item_id: itemId,
1533
1708
  }, context);
1534
1709
  }
1535
1710
  async trackConversion(itemId, value, currency, contextOrSearchId) {
1536
- this.logger.verbose('Tracking conversion event', {
1711
+ this.logger.verbose("Tracking conversion event", {
1537
1712
  itemId,
1538
1713
  value,
1539
1714
  currency,
1540
- hasContext: !!contextOrSearchId
1715
+ hasContext: !!contextOrSearchId,
1541
1716
  });
1542
1717
  // Handle backward compatibility: if fourth param is string, treat as searchId
1543
- const context = typeof contextOrSearchId === 'string'
1544
- ? { correlationId: '', searchId: contextOrSearchId, anonId: this.anonId, sessionId: this.sessionId }
1718
+ const context = typeof contextOrSearchId === "string"
1719
+ ? {
1720
+ correlationId: "",
1721
+ searchId: contextOrSearchId,
1722
+ anonId: this.anonId,
1723
+ sessionId: this.sessionId,
1724
+ }
1545
1725
  : contextOrSearchId;
1546
1726
  await this.trackEvent({
1547
- event_name: 'conversion',
1727
+ event_name: "conversion",
1548
1728
  clicked_item_id: itemId,
1549
1729
  value,
1550
1730
  currency,
@@ -1558,9 +1738,9 @@ class SeekoraClient {
1558
1738
  * @param context - Optional search context for linking events to searches
1559
1739
  */
1560
1740
  async trackCustom(eventName, payload = {}, context) {
1561
- this.logger.verbose('Tracking custom event', {
1741
+ this.logger.verbose("Tracking custom event", {
1562
1742
  eventName,
1563
- hasContext: !!context
1743
+ hasContext: !!context,
1564
1744
  });
1565
1745
  await this.trackEvent({
1566
1746
  ...payload,
@@ -1579,20 +1759,20 @@ class SeekoraClient {
1579
1759
  * @param params.searchContext - Search context for attribution
1580
1760
  */
1581
1761
  async trackAddToCart(params) {
1582
- this.logger.verbose('Tracking add to cart event', {
1762
+ this.logger.verbose("Tracking add to cart event", {
1583
1763
  itemId: params.itemId,
1584
1764
  quantity: params.quantity,
1585
- value: params.value
1765
+ value: params.value,
1586
1766
  });
1587
1767
  await this.trackEvent({
1588
- event_name: 'conversion',
1589
- conversion_type: 'add_to_cart',
1768
+ event_name: "conversion",
1769
+ conversion_type: "add_to_cart",
1590
1770
  clicked_item_id: params.itemId,
1591
1771
  quantity: params.quantity || 1,
1592
1772
  value: params.value,
1593
- currency: params.currency || 'USD',
1773
+ currency: params.currency || "USD",
1594
1774
  position: params.position,
1595
- analytics_tags: ['action:add_to_cart'],
1775
+ analytics_tags: ["action:add_to_cart"],
1596
1776
  }, params.searchContext);
1597
1777
  }
1598
1778
  /**
@@ -1604,15 +1784,15 @@ class SeekoraClient {
1604
1784
  * @param params.searchContext - Search context for attribution
1605
1785
  */
1606
1786
  async trackAddToWishlist(params) {
1607
- this.logger.verbose('Tracking add to wishlist event', {
1608
- itemId: params.itemId
1787
+ this.logger.verbose("Tracking add to wishlist event", {
1788
+ itemId: params.itemId,
1609
1789
  });
1610
1790
  await this.trackEvent({
1611
- event_name: 'conversion',
1612
- conversion_type: 'wishlist',
1791
+ event_name: "conversion",
1792
+ conversion_type: "wishlist",
1613
1793
  clicked_item_id: params.itemId,
1614
1794
  position: params.position,
1615
- analytics_tags: ['action:add_to_wishlist'],
1795
+ analytics_tags: ["action:add_to_wishlist"],
1616
1796
  }, params.searchContext);
1617
1797
  }
1618
1798
  /**
@@ -1625,21 +1805,21 @@ class SeekoraClient {
1625
1805
  * @param params.searchContext - Search context for attribution
1626
1806
  */
1627
1807
  async trackShare(params) {
1628
- this.logger.verbose('Tracking share event', {
1808
+ this.logger.verbose("Tracking share event", {
1629
1809
  itemId: params.itemId,
1630
- shareMethod: params.shareMethod
1810
+ shareMethod: params.shareMethod,
1631
1811
  });
1632
1812
  await this.trackEvent({
1633
- event_name: 'custom',
1813
+ event_name: "custom",
1634
1814
  clicked_item_id: params.itemId,
1635
1815
  position: params.position,
1636
1816
  analytics_tags: [
1637
- 'action:share',
1638
- `share_method:${params.shareMethod || 'unknown'}`
1817
+ "action:share",
1818
+ `share_method:${params.shareMethod || "unknown"}`,
1639
1819
  ],
1640
1820
  custom_json: JSON.stringify({
1641
1821
  share_method: params.shareMethod,
1642
- action_type: 'share'
1822
+ action_type: "share",
1643
1823
  }),
1644
1824
  }, params.searchContext);
1645
1825
  }
@@ -1651,37 +1831,42 @@ class SeekoraClient {
1651
1831
  * @returns Validation result with success status and any error messages
1652
1832
  */
1653
1833
  async validateEvent(event) {
1654
- this.logger.verbose('Validating event payload', { eventName: event.event_name });
1834
+ this.logger.verbose("Validating event payload", {
1835
+ eventName: event.event_name,
1836
+ });
1655
1837
  try {
1656
- this.logger.verbose('Validating event payload', {
1657
- endpoint: '/api/analytics/validate',
1658
- eventName: event.event_name
1838
+ this.logger.verbose("Validating event payload", {
1839
+ endpoint: "/api/analytics/validate",
1840
+ eventName: event.event_name,
1659
1841
  });
1660
1842
  const payload = this.buildEventPayload(event);
1661
1843
  const response = await this.analyticsApi.analyticsValidatePost(this.storeId, this.readSecret, payload, {
1662
1844
  headers: {
1663
- 'x-storeid': this.storeId,
1664
- 'x-storesecret': this.readSecret,
1665
- }
1845
+ "x-storeid": this.storeId,
1846
+ "x-storesecret": this.readSecret,
1847
+ },
1666
1848
  });
1667
1849
  const result = response.data?.data || response.data;
1668
1850
  const isValid = result?.valid !== false && response.status === 200;
1669
- this.logger.verbose('Event validation result', {
1851
+ this.logger.verbose("Event validation result", {
1670
1852
  valid: isValid,
1671
1853
  eventName: event.event_name,
1672
- message: result?.message
1854
+ message: result?.message,
1673
1855
  });
1674
1856
  return {
1675
1857
  valid: isValid,
1676
- message: result?.message || (isValid ? 'Event is valid' : 'Event validation failed'),
1858
+ message: result?.message ||
1859
+ (isValid ? "Event is valid" : "Event validation failed"),
1677
1860
  errors: result?.errors,
1678
1861
  };
1679
1862
  }
1680
1863
  catch (error) {
1681
- this.logger.error('Event validation failed', { error: error.message });
1864
+ this.logger.error("Event validation failed", { error: error.message });
1682
1865
  return {
1683
1866
  valid: false,
1684
- message: error.response?.data?.message || error.message || 'Validation request failed',
1867
+ message: error.response?.data?.message ||
1868
+ error.message ||
1869
+ "Validation request failed",
1685
1870
  errors: error.response?.data?.errors || error.response?.data,
1686
1871
  };
1687
1872
  }
@@ -1693,21 +1878,23 @@ class SeekoraClient {
1693
1878
  * @returns Event schema with field definitions, types, and validation rules
1694
1879
  */
1695
1880
  async getEventSchema() {
1696
- this.logger.verbose('Getting event schema');
1881
+ this.logger.verbose("Getting event schema");
1697
1882
  try {
1698
1883
  const response = await this.analyticsApi.analyticsSchemaGet(this.storeId, this.readSecret, {
1699
1884
  headers: {
1700
- 'x-storeid': this.storeId,
1701
- 'x-storesecret': this.readSecret,
1702
- }
1885
+ "x-storeid": this.storeId,
1886
+ "x-storesecret": this.readSecret,
1887
+ },
1703
1888
  });
1704
1889
  const schema = response.data?.data || response.data;
1705
- this.logger.verbose('Event schema retrieved', { schemaKeys: schema ? Object.keys(schema) : [] });
1890
+ this.logger.verbose("Event schema retrieved", {
1891
+ schemaKeys: schema ? Object.keys(schema) : [],
1892
+ });
1706
1893
  return schema;
1707
1894
  }
1708
1895
  catch (error) {
1709
- this.logger.error('Failed to get event schema', { error: error.message });
1710
- throw this.handleError(error, 'getEventSchema');
1896
+ this.logger.error("Failed to get event schema", { error: error.message });
1897
+ throw this.handleError(error, "getEventSchema");
1711
1898
  }
1712
1899
  }
1713
1900
  /**
@@ -1717,14 +1904,14 @@ class SeekoraClient {
1717
1904
  */
1718
1905
  setUserId(userId) {
1719
1906
  this.userId = userId;
1720
- this.logger.info('User ID updated', { userId });
1907
+ this.logger.info("User ID updated", { userId });
1721
1908
  }
1722
1909
  /**
1723
1910
  * Clear user ID (call this when user logs out)
1724
1911
  */
1725
1912
  clearUserId() {
1726
1913
  this.userId = undefined;
1727
- this.logger.info('User ID cleared');
1914
+ this.logger.info("User ID cleared");
1728
1915
  }
1729
1916
  /**
1730
1917
  * Get current identifiers
@@ -1744,7 +1931,7 @@ class SeekoraClient {
1744
1931
  setAbTest(abTestId, abVariant) {
1745
1932
  this.abTestId = abTestId;
1746
1933
  this.abVariant = abVariant;
1747
- this.logger.verbose('A/B test fields updated', { abTestId, abVariant });
1934
+ this.logger.verbose("A/B test fields updated", { abTestId, abVariant });
1748
1935
  }
1749
1936
  /**
1750
1937
  * Get current A/B test fields
@@ -1776,7 +1963,7 @@ class SeekoraClient {
1776
1963
  }
1777
1964
  return {
1778
1965
  enabled: this.enableEventQueue,
1779
- ...this.eventQueue.getStats()
1966
+ ...this.eventQueue.getStats(),
1780
1967
  };
1781
1968
  }
1782
1969
  /**
@@ -1787,38 +1974,12 @@ class SeekoraClient {
1787
1974
  * @param traits - Optional user traits/properties
1788
1975
  */
1789
1976
  async identify(userId, traits) {
1790
- this.logger.verbose('Identifying user', { userId });
1791
- // Update internal user ID
1977
+ this.logger.verbose("Identifying user", { userId });
1792
1978
  this.userId = userId;
1793
- // Collect current context for fingerprint
1794
- const context = this.cachedBrowserContext || await this.collectBrowserContext();
1795
- const payload = {
1796
- user_id: userId,
1797
- anon_id: this.anonId,
1798
- session_id: this.sessionId,
1799
- device_fingerprint: context?.device_fingerprint,
1800
- traits,
1801
- };
1802
- try {
1803
- const response = await axios_1.default.post(`${this.config.basePath}/api/analytics/identify`, payload, {
1804
- headers: {
1805
- 'x-storeid': this.storeId,
1806
- 'x-storesecret': this.readSecret,
1807
- },
1808
- timeout: 10000,
1809
- });
1810
- this.logger.info('User identified successfully', {
1811
- userId,
1812
- linked: {
1813
- anonId: !!this.anonId,
1814
- fingerprint: !!context?.device_fingerprint,
1815
- }
1816
- });
1817
- }
1818
- catch (error) {
1819
- this.logger.error('Failed to identify user', { userId, error: error.message });
1820
- // Don't throw - identify failures shouldn't break the app
1821
- }
1979
+ const analytics = this.getAnalytics();
1980
+ if (!analytics)
1981
+ return;
1982
+ await analytics.identify(userId, traits);
1822
1983
  }
1823
1984
  /**
1824
1985
  * Alias an anonymous ID to a user ID
@@ -1828,7 +1989,7 @@ class SeekoraClient {
1828
1989
  * @param userId - The user ID to link to
1829
1990
  */
1830
1991
  async alias(anonId, userId) {
1831
- this.logger.verbose('Creating alias', { anonId, userId });
1992
+ this.logger.verbose("Creating alias", { anonId, userId });
1832
1993
  const payload = {
1833
1994
  user_id: userId,
1834
1995
  anon_id: anonId,
@@ -1836,15 +1997,19 @@ class SeekoraClient {
1836
1997
  try {
1837
1998
  await axios_1.default.post(`${this.config.basePath}/api/analytics/identify`, payload, {
1838
1999
  headers: {
1839
- 'x-storeid': this.storeId,
1840
- 'x-storesecret': this.readSecret,
2000
+ "x-storeid": this.storeId,
2001
+ "x-storesecret": this.readSecret,
1841
2002
  },
1842
2003
  timeout: 10000,
1843
2004
  });
1844
- this.logger.info('Alias created successfully', { anonId, userId });
2005
+ this.logger.info("Alias created successfully", { anonId, userId });
1845
2006
  }
1846
2007
  catch (error) {
1847
- this.logger.error('Failed to create alias', { anonId, userId, error: error.message });
2008
+ this.logger.error("Failed to create alias", {
2009
+ anonId,
2010
+ userId,
2011
+ error: error.message,
2012
+ });
1848
2013
  }
1849
2014
  }
1850
2015
  /**
@@ -1857,25 +2022,29 @@ class SeekoraClient {
1857
2022
  const message = data?.message || `API request failed with status ${status}`;
1858
2023
  // Log based on status code
1859
2024
  if (status >= 500) {
1860
- this.logger.error('API server error', { operation, status, message });
2025
+ this.logger.error("API server error", { operation, status, message });
1861
2026
  }
1862
2027
  else if (status === 401 || status === 403) {
1863
- this.logger.error('Authentication error', { operation, status, message });
2028
+ this.logger.error("Authentication error", {
2029
+ operation,
2030
+ status,
2031
+ message,
2032
+ });
1864
2033
  }
1865
2034
  else if (status === 429) {
1866
- this.logger.warn('Rate limit exceeded', { operation, status, message });
2035
+ this.logger.warn("Rate limit exceeded", { operation, status, message });
1867
2036
  }
1868
2037
  else {
1869
- this.logger.error('API request failed', { operation, status, message });
2038
+ this.logger.error("API request failed", { operation, status, message });
1870
2039
  }
1871
2040
  return new Error(`[${operation}] ${message} (${status})`);
1872
2041
  }
1873
2042
  else if (error.request) {
1874
- this.logger.error('Network error', { operation, error: error.message });
2043
+ this.logger.error("Network error", { operation, error: error.message });
1875
2044
  return new Error(`[${operation}] Network error: ${error.message}`);
1876
2045
  }
1877
2046
  else {
1878
- this.logger.error('Request error', { operation, error: error.message });
2047
+ this.logger.error("Request error", { operation, error: error.message });
1879
2048
  return new Error(`[${operation}] ${error.message}`);
1880
2049
  }
1881
2050
  }
@@ -1897,20 +2066,20 @@ class SeekoraClient {
1897
2066
  * @returns Filter values with counts grouped by field
1898
2067
  */
1899
2068
  async getFilters(options) {
1900
- this.logger.verbose('Getting filters', { options });
2069
+ this.logger.verbose("Getting filters", { options });
1901
2070
  try {
1902
- const response = await this.filtersApi.v1FiltersGet(this.storeId, this.readSecret, options?.q, options?.filter, options?.facetBy, options?.maxFacetValues, options?.disjunctiveFacets?.join(','));
2071
+ const response = await this.filtersApi.v1FiltersGet(this.storeId, this.readSecret, options?.q, options?.filter, options?.facetBy, options?.maxFacetValues, options?.disjunctiveFacets?.join(","));
1903
2072
  const wrapper = response.data;
1904
2073
  const data = wrapper?.data || response.data;
1905
- this.logger.info('Filters retrieved successfully', {
2074
+ this.logger.info("Filters retrieved successfully", {
1906
2075
  filterCount: data?.filters?.length,
1907
2076
  totalResults: data?.total_results,
1908
2077
  });
1909
2078
  return data;
1910
2079
  }
1911
2080
  catch (error) {
1912
- this.logger.error('Failed to get filters', { error: error.message });
1913
- throw this.handleError(error, 'getFilters');
2081
+ this.logger.error("Failed to get filters", { error: error.message });
2082
+ throw this.handleError(error, "getFilters");
1914
2083
  }
1915
2084
  }
1916
2085
  /**
@@ -1922,20 +2091,23 @@ class SeekoraClient {
1922
2091
  * @returns Matching facet values with counts
1923
2092
  */
1924
2093
  async searchFacetValues(facetName, options) {
1925
- this.logger.verbose('Searching facet values', { facetName, options });
2094
+ this.logger.verbose("Searching facet values", { facetName, options });
1926
2095
  try {
1927
2096
  const response = await this.filtersApi.v1FiltersFacetNameValuesGet(this.storeId, this.readSecret, facetName, options.facetQuery, options?.q, options?.filter, options?.maxValues);
1928
2097
  const wrapper = response.data;
1929
2098
  const data = wrapper?.data || response.data;
1930
- this.logger.info('Facet values retrieved', {
2099
+ this.logger.info("Facet values retrieved", {
1931
2100
  facetName,
1932
2101
  valueCount: data?.values?.length,
1933
2102
  });
1934
2103
  return data;
1935
2104
  }
1936
2105
  catch (error) {
1937
- this.logger.error('Failed to search facet values', { facetName, error: error.message });
1938
- throw this.handleError(error, 'searchFacetValues');
2106
+ this.logger.error("Failed to search facet values", {
2107
+ facetName,
2108
+ error: error.message,
2109
+ });
2110
+ throw this.handleError(error, "searchFacetValues");
1939
2111
  }
1940
2112
  }
1941
2113
  /**
@@ -1946,20 +2118,22 @@ class SeekoraClient {
1946
2118
  * @returns Filter field metadata with types, default facets, and max values
1947
2119
  */
1948
2120
  async getFiltersSchema() {
1949
- this.logger.verbose('Getting filters schema');
2121
+ this.logger.verbose("Getting filters schema");
1950
2122
  try {
1951
2123
  const response = await this.filtersApi.v1FiltersSchemaGet(this.storeId, this.readSecret);
1952
2124
  const wrapper = response.data;
1953
2125
  const data = wrapper?.data || response.data;
1954
- this.logger.info('Filters schema retrieved', {
2126
+ this.logger.info("Filters schema retrieved", {
1955
2127
  fieldCount: data?.fields?.length,
1956
2128
  defaultFacets: data?.default_facets?.length,
1957
2129
  });
1958
2130
  return data;
1959
2131
  }
1960
2132
  catch (error) {
1961
- this.logger.error('Failed to get filters schema', { error: error.message });
1962
- throw this.handleError(error, 'getFiltersSchema');
2133
+ this.logger.error("Failed to get filters schema", {
2134
+ error: error.message,
2135
+ });
2136
+ throw this.handleError(error, "getFiltersSchema");
1963
2137
  }
1964
2138
  }
1965
2139
  }