@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.
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ // Canonical V4 emission wrapper around @segment/analytics-next.
3
+ //
4
+ // Every Seekora SDK callsite that emits a clickstream event MUST go through
5
+ // `track()` (or one of the named helpers below). The wrapper guarantees:
6
+ //
7
+ // - Tenant context (orgcode + xstoreid) is stamped on every event so
8
+ // downstream Rotor functions don't have to infer it.
9
+ // - search_id is auto-resolved from the 3-layer chain (in-memory →
10
+ // sessionStorage → URL) when the caller hasn't supplied it. Callers can
11
+ // override by passing an explicit `search_id` in properties.
12
+ //
13
+ // The wrapper is intentionally tiny — type checking lives in the codegen'd
14
+ // AnalyticsEvent union (re-exported from ./events), and validation happens
15
+ // downstream in the Rotor `seekora-event-validate` function. This wrapper
16
+ // is the lightest possible shim so unit-level tests can drive it.
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.trackFilterViewed = exports.trackObjectViewed = exports.trackProductViewed = exports.trackOrderCompleted = exports.trackProductAdded = exports.trackFilterConverted = exports.trackObjectConvertedOutsideSearch = exports.trackObjectConverted = exports.trackObjectClickedOutsideSearch = exports.trackSuggestionClicked = exports.trackSearchRefined = exports.trackSearchEmpty = exports.trackFacetApplied = exports.trackObjectClicked = exports.trackSearchImpression = exports.trackSearchSubmitted = void 0;
19
+ exports.track = track;
20
+ exports.emitFilterConvertedForActiveFilters = emitFilterConvertedForActiveFilters;
21
+ const events_1 = require("./events");
22
+ const searchId_1 = require("./searchId");
23
+ const activeFilters_1 = require("./activeFilters");
24
+ /**
25
+ * Emit a track event with canonical Seekora fields auto-stamped.
26
+ *
27
+ * - Tenant context (orgcode, xstoreid) is always merged in.
28
+ * - search_id is auto-resolved via resolveSearchId() when absent. Callers
29
+ * that already know the search_id should pass it explicitly to skip the
30
+ * resolver work.
31
+ */
32
+ function track(ctx, event, properties = {}) {
33
+ const enriched = {
34
+ ...properties,
35
+ orgcode: ctx.orgcode,
36
+ xstoreid: ctx.xstoreid,
37
+ };
38
+ // Auto-resolve search_id when the caller didn't supply a non-empty
39
+ // value. Treating `search_id: undefined` (or `null`/`""`) as "supplied"
40
+ // would skip the 3-layer resolver and the event would land in
41
+ // ClickHouse with no search_id -- breaking funnel attribution for
42
+ // every Object Clicked that came from a ProductCard which doesn't pass
43
+ // searchId explicitly (the common case). The `in` operator alone
44
+ // returns true for `{search_id: undefined}` because the key is
45
+ // present, so check the value too.
46
+ const explicit = enriched.search_id;
47
+ if (explicit == null || explicit === "") {
48
+ const sid = (0, searchId_1.resolveSearchId)();
49
+ if (sid) {
50
+ enriched.search_id = sid;
51
+ }
52
+ else {
53
+ // Drop the empty key entirely so analytics-next doesn't ship a
54
+ // `"search_id": null` that downstream queries can confuse with
55
+ // an explicit "no-attribution" intent.
56
+ delete enriched.search_id;
57
+ }
58
+ }
59
+ ctx.analytics.track(event, enriched);
60
+ }
61
+ // --- Named helpers --------------------------------------------------------
62
+ // Thin sugar so callsites don't have to import EVENT_NAMES alongside track().
63
+ const trackSearchSubmitted = (ctx, p) => track(ctx, events_1.EVENT_NAMES.SearchSubmitted, p);
64
+ exports.trackSearchSubmitted = trackSearchSubmitted;
65
+ const trackSearchImpression = (ctx, p) => track(ctx, events_1.EVENT_NAMES.SearchImpression, p);
66
+ exports.trackSearchImpression = trackSearchImpression;
67
+ const trackObjectClicked = (ctx, p) => track(ctx, events_1.EVENT_NAMES.ObjectClicked, p);
68
+ exports.trackObjectClicked = trackObjectClicked;
69
+ const trackFacetApplied = (ctx, p) => track(ctx, events_1.EVENT_NAMES.FacetApplied, p);
70
+ exports.trackFacetApplied = trackFacetApplied;
71
+ const trackSearchEmpty = (ctx, p) => track(ctx, events_1.EVENT_NAMES.SearchEmpty, p);
72
+ exports.trackSearchEmpty = trackSearchEmpty;
73
+ const trackSearchRefined = (ctx, p) => track(ctx, events_1.EVENT_NAMES.SearchRefined, p);
74
+ exports.trackSearchRefined = trackSearchRefined;
75
+ const trackSuggestionClicked = (ctx, p) => track(ctx, events_1.EVENT_NAMES.SuggestionClicked, p);
76
+ exports.trackSuggestionClicked = trackSuggestionClicked;
77
+ const trackObjectClickedOutsideSearch = (ctx, p) => track(ctx, events_1.EVENT_NAMES.ObjectClickedOutsideSearch, p);
78
+ exports.trackObjectClickedOutsideSearch = trackObjectClickedOutsideSearch;
79
+ const trackObjectConverted = (ctx, p) => track(ctx, events_1.EVENT_NAMES.ObjectConverted, p);
80
+ exports.trackObjectConverted = trackObjectConverted;
81
+ const trackObjectConvertedOutsideSearch = (ctx, p) => track(ctx, events_1.EVENT_NAMES.ObjectConvertedOutsideSearch, p);
82
+ exports.trackObjectConvertedOutsideSearch = trackObjectConvertedOutsideSearch;
83
+ const trackFilterConverted = (ctx, p) => track(ctx, events_1.EVENT_NAMES.FilterConverted, p);
84
+ exports.trackFilterConverted = trackFilterConverted;
85
+ /**
86
+ * Emit one `Filter Converted` event per currently-active filter.
87
+ *
88
+ * Algolia hybrid pattern: a single conversion (addToCart / purchase /
89
+ * wishlist) gets attributed to EVERY filter that was active in the
90
+ * 30-min attribution window — one event per filter so funnel queries
91
+ * can answer "which filter values converted at what rate".
92
+ *
93
+ * `baseProps` carry the conversion metadata that's identical across
94
+ * all of the emitted events (conversion_type, value, currency, plus
95
+ * any extras the caller wants threaded through). Each emit then
96
+ * stamps the per-filter `filter_attribute` + `filter_value`.
97
+ *
98
+ * If no filters are active (empty session-state OR every filter
99
+ * expired), this is a no-op — the regular Object Converted / Product
100
+ * Added event still goes out (the caller's responsibility), but no
101
+ * Filter Converted gets emitted.
102
+ *
103
+ * Returns the number of events emitted, which is handy for unit tests
104
+ * and for callers that want to log "attributed N filters".
105
+ */
106
+ function emitFilterConvertedForActiveFilters(ctx, baseProps = {}) {
107
+ const filters = (0, activeFilters_1.getActiveFilters)();
108
+ for (const f of filters) {
109
+ track(ctx, events_1.EVENT_NAMES.FilterConverted, {
110
+ ...baseProps,
111
+ filter_attribute: f.filter_attribute,
112
+ filter_value: f.filter_value,
113
+ });
114
+ }
115
+ return filters.length;
116
+ }
117
+ const trackProductAdded = (ctx, p) => track(ctx, events_1.EVENT_NAMES.ProductAdded, p);
118
+ exports.trackProductAdded = trackProductAdded;
119
+ // NOTE: Order Line Completed is synthesized downstream by the rotor
120
+ // seekora-orderline-explode function from each Order Completed's
121
+ // `products[]` array. There is NO client helper for it -- callsites
122
+ // only emit Order Completed.
123
+ const trackOrderCompleted = (ctx, p) => track(ctx, events_1.EVENT_NAMES.OrderCompleted, p);
124
+ exports.trackOrderCompleted = trackOrderCompleted;
125
+ const trackProductViewed = (ctx, p) => track(ctx, events_1.EVENT_NAMES.ProductViewed, p);
126
+ exports.trackProductViewed = trackProductViewed;
127
+ const trackObjectViewed = (ctx, p) => track(ctx, events_1.EVENT_NAMES.ObjectViewed, p);
128
+ exports.trackObjectViewed = trackObjectViewed;
129
+ const trackFilterViewed = (ctx, p) => track(ctx, events_1.EVENT_NAMES.FilterViewed, p);
130
+ exports.trackFilterViewed = trackFilterViewed;