@seekora-ai/search-sdk 1.0.0 → 1.0.2

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.
@@ -14,6 +14,8 @@ const config_1 = require("./config");
14
14
  const logger_1 = require("./logger");
15
15
  const config_loader_1 = require("./config-loader");
16
16
  const utils_1 = require("./utils");
17
+ const context_collector_1 = require("./context-collector");
18
+ const event_queue_1 = require("./event-queue");
17
19
  /**
18
20
  * Seekora SDK Client
19
21
  *
@@ -21,6 +23,8 @@ const utils_1 = require("./utils");
21
23
  */
22
24
  class SeekoraClient {
23
25
  constructor(config = {}) {
26
+ this.cachedBrowserContext = null;
27
+ this.eventQueue = null;
24
28
  // Load configuration from file, env, and code (in that order)
25
29
  const mergedConfig = (0, config_loader_1.loadConfig)(config);
26
30
  this.storeId = mergedConfig.storeId;
@@ -37,12 +41,28 @@ class SeekoraClient {
37
41
  this.anonId = config.anonId || (0, utils_1.getOrCreateAnonId)();
38
42
  this.sessionId = config.sessionId || (0, utils_1.getOrCreateSessionId)();
39
43
  this.autoTrackSearch = config.autoTrackSearch || false;
44
+ // Initialize context collection
45
+ this.enableContextCollection = config.enableContextCollection !== false; // Default to true
46
+ this.contextCollector = new context_collector_1.ContextCollector(config.contextCollector);
47
+ // Pre-collect browser context if enabled
48
+ if (this.enableContextCollection) {
49
+ this.collectContextAsync();
50
+ }
51
+ // Initialize event queue if enabled
52
+ this.enableEventQueue = config.enableEventQueue || false;
53
+ if (this.enableEventQueue) {
54
+ this.eventQueue = new event_queue_1.EventQueue(config.eventQueue);
55
+ this.eventQueue.setLogger(this.logger);
56
+ this.eventQueue.setSender(this.createQueueSender());
57
+ }
40
58
  // Log identifier initialization
41
59
  this.logger.verbose('Client identifiers initialized', {
42
60
  hasUserId: !!this.userId,
43
61
  anonId: this.anonId.substring(0, 8) + '...', // Log partial ID for privacy
44
62
  sessionId: this.sessionId.substring(0, 8) + '...',
45
- autoTrackSearch: this.autoTrackSearch
63
+ autoTrackSearch: this.autoTrackSearch,
64
+ enableContextCollection: this.enableContextCollection,
65
+ enableEventQueue: this.enableEventQueue
46
66
  });
47
67
  this.logger.verbose('Initializing SeekoraClient', {
48
68
  storeId: this.storeId,
@@ -69,6 +89,8 @@ class SeekoraClient {
69
89
  this.suggestionsConfigApi = new generated_1.SDKQuerySuggestionsConfigApi(this.config);
70
90
  this.analyticsApi = new generated_1.AnalyticsEventsApi(this.config);
71
91
  this.storesApi = new generated_1.SDKStoreConfigApi(this.config);
92
+ this.documentsApi = new generated_1.SDKDocumentsApi(this.config);
93
+ this.schemaApi = new generated_1.SDKSchemaApi(this.config);
72
94
  this.logger.info('SeekoraClient initialized successfully');
73
95
  }
74
96
  /**
@@ -637,8 +659,383 @@ class SeekoraClient {
637
659
  throw this.handleError(error, 'getConfigSchema');
638
660
  }
639
661
  }
662
+ // ==========================================
663
+ // Document Indexing Methods
664
+ // ==========================================
665
+ /**
666
+ * Index a single document into the store
667
+ *
668
+ * @param document - Document data with optional id
669
+ * @returns IndexDocumentResponse with document id and status
670
+ */
671
+ async indexDocument(document) {
672
+ this.logger.verbose('Indexing document', {
673
+ hasCustomId: !!document.id,
674
+ documentKeys: Object.keys(document.data)
675
+ });
676
+ if (!this.writeSecret) {
677
+ this.logger.error('Write secret required but not provided', {
678
+ operation: 'indexDocument',
679
+ storeId: this.storeId
680
+ });
681
+ throw new Error('Write secret is required for indexDocument');
682
+ }
683
+ try {
684
+ const request = {
685
+ id: document.id,
686
+ data: document.data,
687
+ };
688
+ const response = await this.documentsApi.apiV1StoresXStoreIDDocumentsPost(this.storeId, this.writeSecret, this.storeId, request);
689
+ const responseData = response.data?.data;
690
+ this.logger.info('Document indexed successfully', {
691
+ id: responseData?.id || document.id,
692
+ status: response.status
693
+ });
694
+ return {
695
+ id: responseData?.id || document.id || '',
696
+ success: responseData?.status === 'success' || responseData?.status === 'inserted' || responseData?.status === 'updated' || true,
697
+ message: responseData?.message,
698
+ data: responseData,
699
+ };
700
+ }
701
+ catch (error) {
702
+ this.logger.error('Failed to index document', { error: error.message });
703
+ throw this.handleError(error, 'indexDocument');
704
+ }
705
+ }
706
+ /**
707
+ * Index multiple documents in bulk
708
+ *
709
+ * @param documents - Array of documents with optional actions (insert/update/upsert/delete)
710
+ * @returns BulkIndexResponse with success/error counts
711
+ */
712
+ async indexDocuments(documents) {
713
+ this.logger.verbose('Bulk indexing documents', {
714
+ count: documents.length,
715
+ actions: documents.map(d => d.action || 'upsert')
716
+ });
717
+ if (!this.writeSecret) {
718
+ this.logger.error('Write secret required but not provided', {
719
+ operation: 'indexDocuments',
720
+ storeId: this.storeId
721
+ });
722
+ throw new Error('Write secret is required for indexDocuments');
723
+ }
724
+ try {
725
+ const request = {
726
+ documents: documents.map(doc => ({
727
+ id: doc.id,
728
+ action: doc.action,
729
+ data: doc.data,
730
+ })),
731
+ };
732
+ const response = await this.documentsApi.apiV1StoresXStoreIDDocumentsBulkPost(this.storeId, this.writeSecret, this.storeId, request);
733
+ const responseData = response.data?.data;
734
+ this.logger.info('Bulk document indexing completed', {
735
+ successCount: responseData?.successCount || 0,
736
+ errorCount: responseData?.errorCount || 0,
737
+ status: response.status
738
+ });
739
+ return {
740
+ success_count: responseData?.successCount || documents.length,
741
+ error_count: responseData?.errorCount || 0,
742
+ results: responseData?.results?.map(r => ({
743
+ id: r.id,
744
+ success: r.status === 'success' || r.status === 'inserted' || r.status === 'updated' || !r.error,
745
+ error: r.error,
746
+ })) || documents.map((doc) => ({
747
+ id: doc.id,
748
+ success: true,
749
+ })),
750
+ data: responseData,
751
+ };
752
+ }
753
+ catch (error) {
754
+ this.logger.error('Failed to bulk index documents', {
755
+ count: documents.length,
756
+ error: error.message
757
+ });
758
+ throw this.handleError(error, 'indexDocuments');
759
+ }
760
+ }
761
+ /**
762
+ * Delete a document by ID
763
+ *
764
+ * @param documentId - Document ID to delete
765
+ */
766
+ async deleteDocument(documentId) {
767
+ this.logger.verbose('Deleting document', { documentId });
768
+ if (!this.writeSecret) {
769
+ this.logger.error('Write secret required but not provided', {
770
+ operation: 'deleteDocument',
771
+ storeId: this.storeId
772
+ });
773
+ throw new Error('Write secret is required for deleteDocument');
774
+ }
775
+ try {
776
+ const response = await this.documentsApi.apiV1StoresXStoreIDDocumentsDocumentIDDelete(this.storeId, this.writeSecret, this.storeId, documentId);
777
+ this.logger.info('Document deleted successfully', {
778
+ documentId,
779
+ status: response.status
780
+ });
781
+ }
782
+ catch (error) {
783
+ this.logger.error('Failed to delete document', {
784
+ documentId,
785
+ error: error.message
786
+ });
787
+ throw this.handleError(error, 'deleteDocument');
788
+ }
789
+ }
790
+ // ==================
791
+ // Schema Management
792
+ // ==================
793
+ /**
794
+ * Create or update the collection schema for this store
795
+ *
796
+ * @param request - Schema request with fields and options
797
+ * @returns SchemaResponse with the created/updated schema
798
+ *
799
+ * @example
800
+ * // Create a new schema
801
+ * const schema = await client.createSchema({
802
+ * fields: [
803
+ * { name: 'title', type: 'string' },
804
+ * { name: 'content', type: 'string' },
805
+ * { name: 'url', type: 'string' },
806
+ * { name: 'hierarchy.lvl0', type: 'string', facet: true }
807
+ * ]
808
+ * });
809
+ *
810
+ * @example
811
+ * // Add new fields to existing schema (additive mode)
812
+ * const schema = await client.createSchema({
813
+ * fields: [{ name: 'newField', type: 'string' }],
814
+ * mode: 'additive'
815
+ * });
816
+ *
817
+ * @example
818
+ * // Replace entire schema (destructive, clears all documents)
819
+ * const schema = await client.createSchema({
820
+ * fields: [...],
821
+ * mode: 'replace',
822
+ * confirmDelete: true
823
+ * });
824
+ */
825
+ async createSchema(request) {
826
+ this.logger.verbose('Creating/updating schema', {
827
+ fieldCount: request.fields.length,
828
+ mode: request.mode || 'additive'
829
+ });
830
+ if (!this.writeSecret) {
831
+ this.logger.error('Write secret required but not provided', {
832
+ operation: 'createSchema',
833
+ storeId: this.storeId
834
+ });
835
+ throw new Error('Write secret is required for createSchema');
836
+ }
837
+ try {
838
+ const apiRequest = {
839
+ fields: request.fields.map(f => ({
840
+ name: f.name,
841
+ type: f.type,
842
+ facet: f.facet,
843
+ index: f.index,
844
+ optional: f.optional,
845
+ sort: f.sort,
846
+ infix: f.infix,
847
+ locale: f.locale,
848
+ })),
849
+ mode: request.mode,
850
+ confirm_delete: request.confirmDelete,
851
+ default_sorting_field: request.defaultSortingField,
852
+ enable_nested_fields: request.enableNestedFields,
853
+ };
854
+ const response = await this.schemaApi.apiV1StoresXStoreIDSchemaPost(this.storeId, this.writeSecret, this.storeId, apiRequest);
855
+ const responseData = response.data;
856
+ this.logger.info('Schema created/updated successfully', {
857
+ name: responseData.data?.name,
858
+ fieldCount: responseData.data?.fields?.length
859
+ });
860
+ return {
861
+ name: responseData.data?.name || '',
862
+ fields: (responseData.data?.fields || []).map(f => ({
863
+ name: f.name || '',
864
+ type: f.type || 'string',
865
+ facet: f.facet,
866
+ index: f.index,
867
+ optional: f.optional,
868
+ sort: f.sort,
869
+ infix: f.infix,
870
+ locale: f.locale,
871
+ })),
872
+ defaultSortingField: responseData.data?.default_sorting_field,
873
+ numDocuments: responseData.data?.num_documents || 0,
874
+ createdAt: responseData.data?.created_at,
875
+ };
876
+ }
877
+ catch (error) {
878
+ this.logger.error('Failed to create/update schema', { error: error.message });
879
+ throw this.handleError(error, 'createSchema');
880
+ }
881
+ }
882
+ /**
883
+ * Get the current collection schema for this store
884
+ *
885
+ * @returns SchemaResponse with the current schema
886
+ *
887
+ * @example
888
+ * const schema = await client.getSchema();
889
+ * console.log('Fields:', schema.fields.map(f => f.name));
890
+ * console.log('Documents:', schema.numDocuments);
891
+ */
892
+ async getSchema() {
893
+ this.logger.verbose('Getting schema', { storeId: this.storeId });
894
+ try {
895
+ const response = await this.schemaApi.apiV1StoresXStoreIDSchemaGet(this.storeId, this.readSecret, this.storeId);
896
+ const responseData = response.data;
897
+ this.logger.info('Schema retrieved successfully', {
898
+ name: responseData.data?.name,
899
+ fieldCount: responseData.data?.fields?.length,
900
+ numDocuments: responseData.data?.num_documents
901
+ });
902
+ return {
903
+ name: responseData.data?.name || '',
904
+ fields: (responseData.data?.fields || []).map(f => ({
905
+ name: f.name || '',
906
+ type: f.type || 'string',
907
+ facet: f.facet,
908
+ index: f.index,
909
+ optional: f.optional,
910
+ sort: f.sort,
911
+ infix: f.infix,
912
+ locale: f.locale,
913
+ })),
914
+ defaultSortingField: responseData.data?.default_sorting_field,
915
+ numDocuments: responseData.data?.num_documents || 0,
916
+ createdAt: responseData.data?.created_at,
917
+ };
918
+ }
919
+ catch (error) {
920
+ this.logger.error('Failed to get schema', { error: error.message });
921
+ throw this.handleError(error, 'getSchema');
922
+ }
923
+ }
924
+ /**
925
+ * Clear all documents from the store's collection
926
+ *
927
+ * This deletes all documents but preserves the collection and its schema.
928
+ * Useful for re-indexing data from scratch.
929
+ *
930
+ * @returns ClearDocumentsResponse with count of deleted documents
931
+ *
932
+ * @example
933
+ * const result = await client.clearDocuments();
934
+ * console.log(`Cleared ${result.deletedCount} documents`);
935
+ */
936
+ async clearDocuments() {
937
+ this.logger.verbose('Clearing all documents', { storeId: this.storeId });
938
+ if (!this.writeSecret) {
939
+ this.logger.error('Write secret required but not provided', {
940
+ operation: 'clearDocuments',
941
+ storeId: this.storeId
942
+ });
943
+ throw new Error('Write secret is required for clearDocuments');
944
+ }
945
+ try {
946
+ const response = await this.schemaApi.apiV1StoresXStoreIDDocumentsDelete(this.storeId, this.writeSecret, this.storeId);
947
+ const responseData = response.data;
948
+ this.logger.info('Documents cleared successfully', {
949
+ deletedCount: responseData.data?.deleted_count
950
+ });
951
+ return {
952
+ deletedCount: responseData.data?.deleted_count || 0,
953
+ message: responseData.data?.message || 'Documents cleared',
954
+ };
955
+ }
956
+ catch (error) {
957
+ this.logger.error('Failed to clear documents', { error: error.message });
958
+ throw this.handleError(error, 'clearDocuments');
959
+ }
960
+ }
961
+ /**
962
+ * Asynchronously collect browser context (called during initialization)
963
+ */
964
+ async collectContextAsync() {
965
+ try {
966
+ this.cachedBrowserContext = await this.contextCollector.collect();
967
+ this.logger.verbose('Browser context collected', {
968
+ hasFingerprint: !!this.cachedBrowserContext?.device_fingerprint,
969
+ platform: this.cachedBrowserContext?.platform,
970
+ timezone: this.cachedBrowserContext?.timezone,
971
+ });
972
+ }
973
+ catch (error) {
974
+ this.logger.warn('Failed to collect browser context', { error: error.message });
975
+ }
976
+ }
977
+ /**
978
+ * Create the event sender function for the event queue
979
+ */
980
+ createQueueSender() {
981
+ return async (events) => {
982
+ if (events.length === 1) {
983
+ // Send single event
984
+ await this.sendEventDirect(events[0]);
985
+ }
986
+ else {
987
+ // Send as batch
988
+ await this.sendEventsBatchDirect(events);
989
+ }
990
+ };
991
+ }
992
+ /**
993
+ * Send a single event directly to the backend (bypasses queue)
994
+ */
995
+ async sendEventDirect(payload) {
996
+ const response = await this.analyticsApi.analyticsEventPost(this.storeId, this.readSecret, payload, {
997
+ headers: {
998
+ 'x-storeid': this.storeId,
999
+ 'x-storesecret': this.readSecret,
1000
+ }
1001
+ });
1002
+ if (response.status >= 400) {
1003
+ throw new Error(`Failed to send event: ${response.status}`);
1004
+ }
1005
+ }
640
1006
  /**
641
- * Build event payload with identifiers
1007
+ * Send a batch of events directly to the backend (bypasses queue)
1008
+ */
1009
+ async sendEventsBatchDirect(payloads) {
1010
+ const batchRequest = {
1011
+ events: payloads
1012
+ };
1013
+ const response = await this.analyticsApi.analyticsBatchPost(this.storeId, this.readSecret, batchRequest, {
1014
+ headers: {
1015
+ 'x-storeid': this.storeId,
1016
+ 'x-storesecret': this.readSecret,
1017
+ }
1018
+ });
1019
+ if (response.status >= 400) {
1020
+ throw new Error(`Failed to send batch events: ${response.status}`);
1021
+ }
1022
+ }
1023
+ /**
1024
+ * Get browser context (sync if cached, otherwise returns null)
1025
+ * Use collectBrowserContext() for async collection
1026
+ */
1027
+ getBrowserContext() {
1028
+ return this.cachedBrowserContext;
1029
+ }
1030
+ /**
1031
+ * Collect browser context asynchronously
1032
+ */
1033
+ async collectBrowserContext() {
1034
+ this.cachedBrowserContext = await this.contextCollector.collect();
1035
+ return this.cachedBrowserContext;
1036
+ }
1037
+ /**
1038
+ * Build event payload with identifiers and browser context
642
1039
  * Ensures user_id or anon_id is present, and sets correlation_id/search_id at top level
643
1040
  */
644
1041
  buildEventPayload(event, context) {
@@ -665,6 +1062,53 @@ class SeekoraClient {
665
1062
  if (payload.search_id && payload.metadata && !payload.metadata.search_id) {
666
1063
  payload.metadata.search_id = payload.search_id;
667
1064
  }
1065
+ // Add browser context if enabled and available
1066
+ if (this.enableContextCollection && this.cachedBrowserContext) {
1067
+ const ctx = this.cachedBrowserContext;
1068
+ // Screen/viewport dimensions
1069
+ payload.screen_width = ctx.screen_width;
1070
+ payload.screen_height = ctx.screen_height;
1071
+ payload.viewport_width = ctx.viewport_width;
1072
+ payload.viewport_height = ctx.viewport_height;
1073
+ payload.color_depth = ctx.color_depth;
1074
+ payload.pixel_ratio = ctx.pixel_ratio;
1075
+ // Device fingerprint (if available)
1076
+ if (ctx.device_fingerprint) {
1077
+ payload.device_fingerprint = ctx.device_fingerprint;
1078
+ }
1079
+ // Browser information
1080
+ payload.browser_name = ctx.browser_name;
1081
+ payload.browser_version = ctx.browser_version;
1082
+ payload.browser_language = ctx.browser_language;
1083
+ payload.timezone = ctx.timezone;
1084
+ payload.timezone_offset = ctx.timezone_offset;
1085
+ // Page context
1086
+ payload.page_url = ctx.page_url;
1087
+ payload.page_path = ctx.page_path;
1088
+ payload.page_title = ctx.page_title;
1089
+ payload.page_referrer = ctx.page_referrer;
1090
+ // UTM parameters (if present)
1091
+ if (ctx.utm_source)
1092
+ payload.utm_source = ctx.utm_source;
1093
+ if (ctx.utm_medium)
1094
+ payload.utm_medium = ctx.utm_medium;
1095
+ if (ctx.utm_campaign)
1096
+ payload.utm_campaign = ctx.utm_campaign;
1097
+ if (ctx.utm_term)
1098
+ payload.utm_term = ctx.utm_term;
1099
+ if (ctx.utm_content)
1100
+ payload.utm_content = ctx.utm_content;
1101
+ // Connection info (if available)
1102
+ if (ctx.connection_type)
1103
+ payload.connection_type = ctx.connection_type;
1104
+ if (ctx.connection_effective_type)
1105
+ payload.connection_effective_type = ctx.connection_effective_type;
1106
+ // Platform info
1107
+ payload.platform = ctx.platform;
1108
+ payload.is_mobile = ctx.is_mobile;
1109
+ payload.is_tablet = ctx.is_tablet;
1110
+ payload.is_touch_device = ctx.is_touch_device;
1111
+ }
668
1112
  return payload;
669
1113
  }
670
1114
  /**
@@ -679,6 +1123,15 @@ class SeekoraClient {
679
1123
  hasContext: !!context
680
1124
  });
681
1125
  const payload = this.buildEventPayload(event, context);
1126
+ // Use event queue if enabled
1127
+ if (this.enableEventQueue && this.eventQueue) {
1128
+ this.eventQueue.enqueue(payload);
1129
+ this.logger.verbose('Event queued for sending', {
1130
+ eventName: event.event_name,
1131
+ queueSize: this.eventQueue.size()
1132
+ });
1133
+ return;
1134
+ }
682
1135
  try {
683
1136
  this.logger.verbose('Sending analytics event', {
684
1137
  endpoint: '/api/analytics/event',
@@ -719,13 +1172,22 @@ class SeekoraClient {
719
1172
  this.logger.error('Batch size exceeds maximum', { count: events.length, max: 100 });
720
1173
  throw new Error('Maximum 100 events per batch');
721
1174
  }
1175
+ // Build full event payloads with identifiers
1176
+ const enrichedEvents = events.map(event => this.buildEventPayload(event, context));
1177
+ // Use event queue if enabled
1178
+ if (this.enableEventQueue && this.eventQueue) {
1179
+ this.eventQueue.enqueueBatch(enrichedEvents);
1180
+ this.logger.verbose('Events queued for sending', {
1181
+ count: events.length,
1182
+ queueSize: this.eventQueue.size()
1183
+ });
1184
+ return;
1185
+ }
722
1186
  try {
723
1187
  this.logger.verbose('Sending batch analytics events', {
724
1188
  endpoint: '/api/analytics/batch',
725
1189
  eventCount: events.length
726
1190
  });
727
- // Build full event payloads with identifiers
728
- const enrichedEvents = events.map(event => this.buildEventPayload(event, context));
729
1191
  const batchRequest = {
730
1192
  events: enrichedEvents // Type assertion for extended payloads
731
1193
  };
@@ -947,6 +1409,101 @@ class SeekoraClient {
947
1409
  sessionId: this.sessionId,
948
1410
  };
949
1411
  }
1412
+ /**
1413
+ * Get the event queue instance (if enabled)
1414
+ */
1415
+ getEventQueue() {
1416
+ return this.eventQueue;
1417
+ }
1418
+ /**
1419
+ * Flush the event queue manually
1420
+ * Useful when you want to ensure events are sent before page unload
1421
+ */
1422
+ async flushEventQueue() {
1423
+ if (this.eventQueue) {
1424
+ await this.eventQueue.flush();
1425
+ }
1426
+ }
1427
+ /**
1428
+ * Get event queue statistics
1429
+ */
1430
+ getEventQueueStats() {
1431
+ if (!this.eventQueue) {
1432
+ return null;
1433
+ }
1434
+ return {
1435
+ enabled: this.enableEventQueue,
1436
+ ...this.eventQueue.getStats()
1437
+ };
1438
+ }
1439
+ /**
1440
+ * Identify a user and link their anonymous ID/fingerprint to their user ID
1441
+ * Call this when a user logs in to enable cross-device/session tracking
1442
+ *
1443
+ * @param userId - The authenticated user ID
1444
+ * @param traits - Optional user traits/properties
1445
+ */
1446
+ async identify(userId, traits) {
1447
+ this.logger.verbose('Identifying user', { userId });
1448
+ // Update internal user ID
1449
+ this.userId = userId;
1450
+ // Collect current context for fingerprint
1451
+ const context = this.cachedBrowserContext || await this.collectBrowserContext();
1452
+ const payload = {
1453
+ user_id: userId,
1454
+ anon_id: this.anonId,
1455
+ session_id: this.sessionId,
1456
+ device_fingerprint: context?.device_fingerprint,
1457
+ traits,
1458
+ };
1459
+ try {
1460
+ const response = await axios_1.default.post(`${this.config.basePath}/api/analytics/identify`, payload, {
1461
+ headers: {
1462
+ 'x-storeid': this.storeId,
1463
+ 'x-storesecret': this.readSecret,
1464
+ },
1465
+ timeout: 10000,
1466
+ });
1467
+ this.logger.info('User identified successfully', {
1468
+ userId,
1469
+ linked: {
1470
+ anonId: !!this.anonId,
1471
+ fingerprint: !!context?.device_fingerprint,
1472
+ }
1473
+ });
1474
+ }
1475
+ catch (error) {
1476
+ this.logger.error('Failed to identify user', { userId, error: error.message });
1477
+ // Don't throw - identify failures shouldn't break the app
1478
+ }
1479
+ }
1480
+ /**
1481
+ * Alias an anonymous ID to a user ID
1482
+ * Use this when you want to link an external anonymous ID to a user
1483
+ *
1484
+ * @param anonId - The anonymous ID to alias
1485
+ * @param userId - The user ID to link to
1486
+ */
1487
+ async alias(anonId, userId) {
1488
+ this.logger.verbose('Creating alias', { anonId, userId });
1489
+ const payload = {
1490
+ user_id: userId,
1491
+ anon_id: anonId,
1492
+ };
1493
+ try {
1494
+ await axios_1.default.post(`${this.config.basePath}/api/analytics/identify`, payload, {
1495
+ headers: {
1496
+ 'x-storeid': this.storeId,
1497
+ 'x-storesecret': this.readSecret,
1498
+ },
1499
+ timeout: 10000,
1500
+ });
1501
+ this.logger.info('Alias created successfully', { anonId, userId });
1502
+ }
1503
+ catch (error) {
1504
+ this.logger.error('Failed to create alias', { anonId, userId, error: error.message });
1505
+ }
1506
+ }
950
1507
  /**
951
1508
  * Handle errors consistently
952
1509
  */