@huskel/sdk 0.3.0 → 0.3.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.
package/README.md CHANGED
@@ -12,37 +12,6 @@ pnpm add @huskel/sdk
12
12
 
13
13
  ---
14
14
 
15
- ## How it works
16
-
17
- ```
18
- Customer visits /products/nike-air-max
19
-
20
- usePageIngest() fires silently in the browser
21
-
22
- POST /ingest → Go backend → Upstash vector DB
23
-
24
- Product is now searchable via <SearchBar />
25
- ```
26
-
27
- No cron jobs. No scraping. No manual uploads.
28
-
29
- ---
30
-
31
- ## Setup
32
-
33
- ### Environment variables
34
-
35
- Add these to `.env` or `.env.local` in your project:
36
-
37
- ```bash
38
- NEXT_PUBLIC_HUSKEL_SITE_ID=your-site-id
39
- NEXT_PUBLIC_HUSKEL_API_URL=https://your-huskel-backend.com
40
- NEXT_PUBLIC_HUSKEL_API_TOKEN=sk_live_...
41
- ```
42
-
43
- The SDK picks these up automatically — no need to pass them manually.
44
-
45
- ---
46
15
 
47
16
  ## Next.js (App Router)
48
17
 
package/dist/index.d.mts CHANGED
@@ -53,6 +53,7 @@ interface HuskelConfig {
53
53
  siteId?: string;
54
54
  apiUrl?: string;
55
55
  apiToken?: string;
56
+ shopperId?: string;
56
57
  }
57
58
  interface SearchRequest {
58
59
  query: string;
@@ -82,7 +83,9 @@ declare class HuskelAPI {
82
83
  private apiUrl;
83
84
  private siteId;
84
85
  private apiToken;
85
- constructor(apiUrl: string, siteId: string, apiToken: string);
86
+ private getShopperId?;
87
+ private getSessionId?;
88
+ constructor(apiUrl: string, siteId: string, apiToken: string, getShopperId?: (() => string | undefined) | undefined, getSessionId?: (() => string | undefined) | undefined);
86
89
  private post;
87
90
  ingest(product: Product): Promise<IngestResponse>;
88
91
  ingestBatch(products: Product[]): Promise<IngestResponse>;
@@ -95,7 +98,13 @@ declare class HuskelClient {
95
98
  private ingestTimer;
96
99
  private ingestedUrls;
97
100
  private onlineHandler;
101
+ private shopperId?;
102
+ private sessionId;
98
103
  constructor(config: HuskelConfig);
104
+ setShopperId(id: string | undefined): void;
105
+ getShopperId(): string | undefined;
106
+ getSessionId(): string;
107
+ private initSession;
99
108
  destroy(): void;
100
109
  queueIngest(rawProduct: RawProductInput): Promise<void>;
101
110
  queueIngestBatch(rawProducts: RawProductInput[]): Promise<void>;
@@ -172,6 +181,6 @@ declare function Sparkle({ productName, limit, onResult, className }: SparklePro
172
181
  interface HuskelProviderProps extends HuskelConfig {
173
182
  children: React.ReactNode;
174
183
  }
175
- declare function HuskelProvider({ siteId, apiUrl, apiToken, children }: HuskelProviderProps): react_jsx_runtime.JSX.Element;
184
+ declare function HuskelProvider({ siteId, apiUrl, apiToken, shopperId, children }: HuskelProviderProps): react_jsx_runtime.JSX.Element;
176
185
 
177
186
  export { HuskelAPI, HuskelClient, type HuskelConfig, type HuskelError, HuskelProvider, type IngestResponse, type Product, type RawProductInput, SearchBar, type SearchRequest, type SearchResponse, type SearchResult, Sparkle, getHuskelClient, initHuskel, useHuskel, useIngest, usePageIngest, useSearch };
package/dist/index.d.ts CHANGED
@@ -53,6 +53,7 @@ interface HuskelConfig {
53
53
  siteId?: string;
54
54
  apiUrl?: string;
55
55
  apiToken?: string;
56
+ shopperId?: string;
56
57
  }
57
58
  interface SearchRequest {
58
59
  query: string;
@@ -82,7 +83,9 @@ declare class HuskelAPI {
82
83
  private apiUrl;
83
84
  private siteId;
84
85
  private apiToken;
85
- constructor(apiUrl: string, siteId: string, apiToken: string);
86
+ private getShopperId?;
87
+ private getSessionId?;
88
+ constructor(apiUrl: string, siteId: string, apiToken: string, getShopperId?: (() => string | undefined) | undefined, getSessionId?: (() => string | undefined) | undefined);
86
89
  private post;
87
90
  ingest(product: Product): Promise<IngestResponse>;
88
91
  ingestBatch(products: Product[]): Promise<IngestResponse>;
@@ -95,7 +98,13 @@ declare class HuskelClient {
95
98
  private ingestTimer;
96
99
  private ingestedUrls;
97
100
  private onlineHandler;
101
+ private shopperId?;
102
+ private sessionId;
98
103
  constructor(config: HuskelConfig);
104
+ setShopperId(id: string | undefined): void;
105
+ getShopperId(): string | undefined;
106
+ getSessionId(): string;
107
+ private initSession;
99
108
  destroy(): void;
100
109
  queueIngest(rawProduct: RawProductInput): Promise<void>;
101
110
  queueIngestBatch(rawProducts: RawProductInput[]): Promise<void>;
@@ -172,6 +181,6 @@ declare function Sparkle({ productName, limit, onResult, className }: SparklePro
172
181
  interface HuskelProviderProps extends HuskelConfig {
173
182
  children: React.ReactNode;
174
183
  }
175
- declare function HuskelProvider({ siteId, apiUrl, apiToken, children }: HuskelProviderProps): react_jsx_runtime.JSX.Element;
184
+ declare function HuskelProvider({ siteId, apiUrl, apiToken, shopperId, children }: HuskelProviderProps): react_jsx_runtime.JSX.Element;
176
185
 
177
186
  export { HuskelAPI, HuskelClient, type HuskelConfig, type HuskelError, HuskelProvider, type IngestResponse, type Product, type RawProductInput, SearchBar, type SearchRequest, type SearchResponse, type SearchResult, Sparkle, getHuskelClient, initHuskel, useHuskel, useIngest, usePageIngest, useSearch };
package/dist/index.js CHANGED
@@ -65,21 +65,33 @@ async function sleep(ms) {
65
65
  return new Promise((r) => setTimeout(r, ms));
66
66
  }
67
67
  var HuskelAPI = class {
68
- constructor(apiUrl, siteId, apiToken) {
68
+ constructor(apiUrl, siteId, apiToken, getShopperId, getSessionId) {
69
69
  this.apiUrl = apiUrl;
70
70
  this.siteId = siteId;
71
71
  this.apiToken = apiToken;
72
+ this.getShopperId = getShopperId;
73
+ this.getSessionId = getSessionId;
72
74
  }
73
75
  async post(path, body, attempt = 0) {
76
+ var _a, _b;
74
77
  const url = `${this.apiUrl}${path}`;
75
78
  try {
79
+ const headers = {
80
+ "Content-Type": "application/json",
81
+ "X-Huskel-Token": this.apiToken,
82
+ "X-Huskel-Site": this.siteId
83
+ };
84
+ const shopperId = (_a = this.getShopperId) == null ? void 0 : _a.call(this);
85
+ if (shopperId) {
86
+ headers["X-Huskel-Shopper-Id"] = shopperId;
87
+ }
88
+ const sessionId = (_b = this.getSessionId) == null ? void 0 : _b.call(this);
89
+ if (sessionId) {
90
+ headers["X-Huskel-Session-Id"] = sessionId;
91
+ }
76
92
  const res = await fetch(url, {
77
93
  method: "POST",
78
- headers: {
79
- "Content-Type": "application/json",
80
- "X-Huskel-Token": this.apiToken,
81
- "X-Huskel-Site": this.siteId
82
- },
94
+ headers,
83
95
  body: JSON.stringify(body)
84
96
  });
85
97
  if (!res.ok) {
@@ -205,19 +217,38 @@ function mapRawProduct(input) {
205
217
  slug
206
218
  };
207
219
  }
220
+ function generateUUID() {
221
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
222
+ return crypto.randomUUID();
223
+ }
224
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
225
+ const r = Math.random() * 16 | 0;
226
+ const v = c === "x" ? r : r & 3 | 8;
227
+ return v.toString(16);
228
+ });
229
+ }
208
230
  var HuskelClient = class {
209
231
  constructor(config) {
210
232
  this.ingestQueue = [];
211
233
  this.ingestTimer = null;
212
234
  this.ingestedUrls = /* @__PURE__ */ new Set();
213
235
  this.onlineHandler = null;
236
+ this.sessionId = "";
214
237
  const siteId = config.siteId || getEnvVar("NEXT_PUBLIC_HUSKEL_SITE_ID") || "";
215
238
  const apiUrl = config.apiUrl || getEnvVar("NEXT_PUBLIC_HUSKEL_API_URL") || "";
216
239
  const apiToken = config.apiToken || getEnvVar("NEXT_PUBLIC_HUSKEL_API_TOKEN") || "";
217
240
  if (!siteId) console.error('[Huskel] Missing siteId. Set it via <HuskelProvider siteId="..."> or NEXT_PUBLIC_HUSKEL_SITE_ID.');
218
241
  if (!apiUrl) console.error('[Huskel] Missing apiUrl. Set it via <HuskelProvider apiUrl="..."> or NEXT_PUBLIC_HUSKEL_API_URL.');
219
242
  if (!apiToken) console.error('[Huskel] Missing apiToken. Set it via <HuskelProvider apiToken="..."> or NEXT_PUBLIC_HUSKEL_API_TOKEN.');
220
- this.api = new HuskelAPI(apiUrl, siteId, apiToken);
243
+ this.shopperId = config.shopperId;
244
+ this.initSession();
245
+ this.api = new HuskelAPI(
246
+ apiUrl,
247
+ siteId,
248
+ apiToken,
249
+ () => this.shopperId,
250
+ () => this.sessionId
251
+ );
221
252
  instance = this;
222
253
  if (typeof window !== "undefined") {
223
254
  this.onlineHandler = () => {
@@ -227,6 +258,30 @@ var HuskelClient = class {
227
258
  window.addEventListener("online", this.onlineHandler);
228
259
  }
229
260
  }
261
+ setShopperId(id) {
262
+ this.shopperId = id;
263
+ }
264
+ getShopperId() {
265
+ return this.shopperId;
266
+ }
267
+ getSessionId() {
268
+ return this.sessionId;
269
+ }
270
+ initSession() {
271
+ if (typeof window !== "undefined" && window.sessionStorage) {
272
+ try {
273
+ let sid = window.sessionStorage.getItem("huskel_session_id");
274
+ if (!sid) {
275
+ sid = generateUUID();
276
+ window.sessionStorage.setItem("huskel_session_id", sid);
277
+ }
278
+ this.sessionId = sid;
279
+ return;
280
+ } catch (e) {
281
+ }
282
+ }
283
+ this.sessionId = generateUUID();
284
+ }
230
285
  destroy() {
231
286
  if (typeof window !== "undefined" && this.onlineHandler) {
232
287
  window.removeEventListener("online", this.onlineHandler);
@@ -327,11 +382,15 @@ var import_react3 = require("react");
327
382
  var import_react2 = require("react");
328
383
  var import_jsx_runtime = require("react/jsx-runtime");
329
384
  var HuskelContext = (0, import_react2.createContext)(null);
330
- function HuskelProvider({ siteId, apiUrl, apiToken, children }) {
385
+ function HuskelProvider({ siteId, apiUrl, apiToken, shopperId, children }) {
331
386
  const clientRef = (0, import_react2.useRef)(null);
332
387
  if (!clientRef.current) {
333
- clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken });
388
+ clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken, shopperId });
334
389
  }
390
+ (0, import_react2.useEffect)(() => {
391
+ var _a;
392
+ (_a = clientRef.current) == null ? void 0 : _a.setShopperId(shopperId);
393
+ }, [shopperId]);
335
394
  (0, import_react2.useEffect)(() => {
336
395
  return () => {
337
396
  var _a;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/api.ts","../src/client.ts","../src/hooks/useHuskel.ts","../src/hooks/useSearch.ts","../src/components/HuskelProvider.tsx","../src/hooks/useIngest.ts","../src/hooks/usePageIngest.ts","../src/components/SearchBar.tsx","../src/components/Sparkle.tsx"],"sourcesContent":["export { initHuskel, getHuskelClient, HuskelClient } from './client';\r\nexport { HuskelAPI } from './api';\r\nexport { useHuskel } from './hooks/useHuskel';\r\nexport { useSearch } from './hooks/useSearch';\r\nexport { useIngest } from './hooks/useIngest';\r\nexport { usePageIngest } from './hooks/usePageIngest';\r\nexport { SearchBar } from './components/SearchBar';\r\nexport { Sparkle } from './components/Sparkle';\r\nexport { HuskelProvider } from './components/HuskelProvider';\r\nexport type {\r\n Product,\r\n RawProductInput,\r\n HuskelConfig,\r\n SearchRequest,\r\n SearchResult,\r\n SearchResponse,\r\n IngestResponse,\r\n HuskelError,\r\n} from './types';\r\n","import { Product, SearchResponse, IngestResponse, HuskelError } from './types';\r\n\r\nconst MAX_RETRIES = 3;\r\nconst RETRY_DELAYS = [500, 1000, 2000]; // ms\r\n\r\nfunction log(level: 'info' | 'warn' | 'error', msg: string, data?: unknown) {\r\n const prefix = '[Huskel]';\r\n if (level === 'error') console.error(prefix, msg, data ?? '');\r\n else if (level === 'warn') console.warn(prefix, msg, data ?? '');\r\n else console.log(prefix, msg, data ?? '');\r\n}\r\n\r\nasync function sleep(ms: number) {\r\n return new Promise(r => setTimeout(r, ms));\r\n}\r\n\r\nexport class HuskelAPI {\r\n constructor(\r\n private apiUrl: string,\r\n private siteId: string,\r\n private apiToken: string\r\n ) {}\r\n\r\n private async post<T>(path: string, body: unknown, attempt = 0): Promise<T> {\r\n const url = `${this.apiUrl}${path}`;\r\n\r\n try {\r\n const res = await fetch(url, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-Huskel-Token': this.apiToken,\r\n 'X-Huskel-Site': this.siteId,\r\n },\r\n body: JSON.stringify(body),\r\n });\r\n\r\n if (!res.ok) {\r\n const text = await res.text();\r\n const err: HuskelError = { status: res.status, message: text };\r\n\r\n // Don't retry 4xx — developer errors\r\n if (res.status >= 400 && res.status < 500) {\r\n log('error', `${path} failed [${res.status}]`, text);\r\n throw err;\r\n }\r\n\r\n // Retry 5xx\r\n if (attempt < MAX_RETRIES - 1) {\r\n log('warn', `${path} [${res.status}] retrying (${attempt + 1}/${MAX_RETRIES})...`);\r\n await sleep(RETRY_DELAYS[attempt]);\r\n return this.post(path, body, attempt + 1);\r\n }\r\n\r\n log('error', `${path} failed after ${MAX_RETRIES} attempts`, err);\r\n throw err;\r\n }\r\n\r\n return res.json();\r\n } catch (e) {\r\n // Network error (offline, DNS, etc.)\r\n if ((e as HuskelError).status === undefined) {\r\n if (attempt < MAX_RETRIES - 1) {\r\n log('warn', `${path} network error, retrying (${attempt + 1}/${MAX_RETRIES})...`);\r\n await sleep(RETRY_DELAYS[attempt]);\r\n return this.post(path, body, attempt + 1);\r\n }\r\n log('error', `${path} unreachable after ${MAX_RETRIES} attempts`);\r\n }\r\n throw e;\r\n }\r\n }\r\n\r\n async ingest(product: Product): Promise<IngestResponse> {\r\n log('info', 'ingesting product', product.name);\r\n return this.post('/ingest', { siteId: this.siteId, product });\r\n }\r\n\r\n async ingestBatch(products: Product[]): Promise<IngestResponse> {\r\n log('info', `ingesting batch of ${products.length} products`);\r\n return this.post('/ingest/batch', { siteId: this.siteId, products });\r\n }\r\n\r\n async search(query: string, limit = 10): Promise<SearchResponse> {\r\n log('info', 'search query', query);\r\n return this.post('/search', { query, siteId: this.siteId, limit });\r\n }\r\n}\r\n","import { HuskelConfig, Product, RawProductInput } from './types';\r\nimport { HuskelAPI } from './api';\r\n\r\nfunction getEnvVar(key: string): string | undefined {\r\n if (typeof globalThis !== 'undefined') {\r\n const g = globalThis as any;\r\n if (g.process && g.process.env) {\r\n return g.process.env[key];\r\n }\r\n }\r\n return undefined;\r\n}\r\n\r\nfunction mapRawProduct(input: RawProductInput): Product | null {\r\n const name = input.name || input.title || input.productName || '';\r\n \r\n let price = '';\r\n let priceNumeric: number | undefined = undefined;\r\n\r\n if (input.price !== undefined) {\r\n if (typeof input.price === 'number') {\r\n priceNumeric = input.price;\r\n price = String(input.price);\r\n } else {\r\n price = input.price;\r\n const num = parseFloat(input.price.replace(/[^0-9.]/g, ''));\r\n priceNumeric = isNaN(num) ? undefined : num;\r\n }\r\n }\r\n if (input.priceNumeric !== undefined) {\r\n priceNumeric = input.priceNumeric;\r\n }\r\n\r\n let url = input.url || '';\r\n if (!url && typeof window !== 'undefined') {\r\n url = window.location.href;\r\n }\r\n\r\n let slug = input.slug || input.id || input.productId || '';\r\n if (!slug && url) {\r\n slug = url.split('/').filter(Boolean).pop() || '';\r\n }\r\n if (!slug && name) {\r\n slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');\r\n }\r\n\r\n let images: string[] = [];\r\n if (input.images) {\r\n images = input.images;\r\n } else if (input.image) {\r\n images = [input.image];\r\n } else if (input.thumbnail) {\r\n images = [input.thumbnail];\r\n }\r\n\r\n if (!name) {\r\n console.warn('[Huskel] Validation warning: Product name/title is missing. Skipping:', input);\r\n return null;\r\n }\r\n if (!price) {\r\n console.warn('[Huskel] Validation warning: Product price is missing. Skipping:', input);\r\n return null;\r\n }\r\n if (!url) {\r\n console.warn('[Huskel] Validation warning: Product URL is missing. Skipping:', input);\r\n return null;\r\n }\r\n\r\n return {\r\n name,\r\n price,\r\n url,\r\n brand: input.brand,\r\n description: input.description,\r\n originalPrice: input.originalPrice,\r\n discount: input.discount,\r\n currency: input.currency ?? 'KES',\r\n stock: input.stock,\r\n availability: input.availability,\r\n rating: input.rating,\r\n reviewCount: input.reviewCount,\r\n category: input.category,\r\n subCategory: input.subCategory,\r\n tags: input.tags,\r\n images: images.length > 0 ? images : undefined,\r\n specs: input.specs,\r\n priceNumeric,\r\n slug,\r\n };\r\n}\r\n\r\nexport class HuskelClient {\r\n readonly api: HuskelAPI;\r\n private ingestQueue: Product[] = [];\r\n private ingestTimer: ReturnType<typeof setTimeout> | null = null;\r\n private ingestedUrls = new Set<string>();\r\n private onlineHandler: (() => void) | null = null;\r\n\r\n constructor(config: HuskelConfig) {\r\n const siteId = config.siteId || getEnvVar('NEXT_PUBLIC_HUSKEL_SITE_ID') || '';\r\n const apiUrl = config.apiUrl || getEnvVar('NEXT_PUBLIC_HUSKEL_API_URL') || '';\r\n const apiToken = config.apiToken || getEnvVar('NEXT_PUBLIC_HUSKEL_API_TOKEN') || '';\r\n\r\n // Runtime validation — fail loudly so misconfiguration is never silent\r\n if (!siteId) console.error('[Huskel] Missing siteId. Set it via <HuskelProvider siteId=\"...\"> or NEXT_PUBLIC_HUSKEL_SITE_ID.');\r\n if (!apiUrl) console.error('[Huskel] Missing apiUrl. Set it via <HuskelProvider apiUrl=\"...\"> or NEXT_PUBLIC_HUSKEL_API_URL.');\r\n if (!apiToken) console.error('[Huskel] Missing apiToken. Set it via <HuskelProvider apiToken=\"...\"> or NEXT_PUBLIC_HUSKEL_API_TOKEN.');\r\n\r\n this.api = new HuskelAPI(apiUrl, siteId, apiToken);\r\n instance = this;\r\n\r\n if (typeof window !== 'undefined') {\r\n this.onlineHandler = () => {\r\n console.log('[Huskel] Connectivity restored, flushing queued ingestions.');\r\n this.flushQueue();\r\n };\r\n window.addEventListener('online', this.onlineHandler);\r\n }\r\n }\r\n\r\n destroy() {\r\n if (typeof window !== 'undefined' && this.onlineHandler) {\r\n window.removeEventListener('online', this.onlineHandler);\r\n this.onlineHandler = null;\r\n }\r\n if (this.ingestTimer) {\r\n clearTimeout(this.ingestTimer);\r\n this.ingestTimer = null;\r\n }\r\n if (instance === this) instance = null;\r\n }\r\n\r\n async queueIngest(rawProduct: RawProductInput): Promise<void> {\r\n const product = mapRawProduct(rawProduct);\r\n if (!product) return;\r\n\r\n if (this.ingestedUrls.has(product.url)) {\r\n return;\r\n }\r\n this.ingestedUrls.add(product.url);\r\n\r\n this.ingestQueue.push(product);\r\n this.scheduleFlush();\r\n }\r\n\r\n async queueIngestBatch(rawProducts: RawProductInput[]): Promise<void> {\r\n rawProducts.forEach(p => {\r\n const product = mapRawProduct(p);\r\n if (!product) return;\r\n\r\n if (this.ingestedUrls.has(product.url)) {\r\n return;\r\n }\r\n this.ingestedUrls.add(product.url);\r\n this.ingestQueue.push(product);\r\n });\r\n\r\n if (this.ingestQueue.length > 0) {\r\n this.scheduleFlush();\r\n }\r\n }\r\n\r\n private scheduleFlush() {\r\n if (this.ingestTimer) return;\r\n this.ingestTimer = setTimeout(() => {\r\n this.flushQueue();\r\n }, 300);\r\n }\r\n\r\n private async flushQueue() {\r\n this.ingestTimer = null;\r\n if (this.ingestQueue.length === 0) return;\r\n\r\n if (typeof navigator !== 'undefined' && !navigator.onLine) {\r\n console.warn('[Huskel] Browser offline. Postponing ingestion.');\r\n return;\r\n }\r\n\r\n const batch = [...this.ingestQueue];\r\n this.ingestQueue = [];\r\n\r\n try {\r\n await this.api.ingestBatch(batch);\r\n } catch (e: any) {\r\n if (e.status && e.status >= 400 && e.status < 500) {\r\n console.error('[Huskel] Ingestion discarded due to client error:', e.message);\r\n return;\r\n }\r\n\r\n // Re-queue and schedule another flush so items are not stuck forever\r\n console.warn('[Huskel] Ingestion failed. Re-queuing to retry.', e);\r\n this.ingestQueue = [...batch, ...this.ingestQueue];\r\n this.scheduleFlush();\r\n }\r\n }\r\n}\r\n\r\nlet instance: HuskelClient | null = null;\r\n\r\nexport function initHuskel(config: HuskelConfig): HuskelClient {\r\n instance = new HuskelClient(config);\r\n return instance;\r\n}\r\n\r\nexport function getHuskelClient(): HuskelClient {\r\n if (!instance) {\r\n const siteId = getEnvVar('NEXT_PUBLIC_HUSKEL_SITE_ID');\r\n const apiUrl = getEnvVar('NEXT_PUBLIC_HUSKEL_API_URL');\r\n const apiToken = getEnvVar('NEXT_PUBLIC_HUSKEL_API_TOKEN');\r\n\r\n if (siteId && apiUrl && apiToken) {\r\n instance = new HuskelClient({ siteId, apiUrl, apiToken });\r\n } else {\r\n throw new Error('[Huskel] Call initHuskel() or set NEXT_PUBLIC_HUSKEL_* environment variables before using the client.');\r\n }\r\n }\r\n return instance;\r\n}\r\n","import { useRef } from 'react';\r\nimport { HuskelConfig } from '../types';\r\nimport { HuskelClient, initHuskel } from '../client';\r\n\r\n/**\r\n * @deprecated Use <HuskelProvider> instead to avoid SSR issues.\r\n */\r\nexport function useHuskel(config: HuskelConfig): HuskelClient {\r\n const clientRef = useRef<HuskelClient | null>(null);\r\n\r\n if (!clientRef.current) {\r\n console.warn('[Huskel] useHuskel() is deprecated. Please wrap your application in <HuskelProvider> instead.');\r\n clientRef.current = initHuskel(config);\r\n }\r\n\r\n return clientRef.current;\r\n}\r\n","import { useState, useCallback, useRef } from 'react';\r\nimport { SearchResult } from '../types';\r\nimport { useHuskelContext } from '../components/HuskelProvider';\r\n\r\ninterface UseSearchReturn {\r\n results: SearchResult[];\r\n loading: boolean;\r\n error: string | null;\r\n search: (query: string, limit?: number) => Promise<void>;\r\n clear: () => void;\r\n}\r\n\r\nexport function useSearch(): UseSearchReturn {\r\n const client = useHuskelContext();\r\n const [results, setResults] = useState<SearchResult[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const abortRef = useRef<AbortController | null>(null);\r\n\r\n const search = useCallback(async (query: string, limit = 10) => {\r\n if (!query.trim()) { setResults([]); return; }\r\n abortRef.current?.abort();\r\n abortRef.current = new AbortController();\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n const res = await client.api.search(query, limit);\r\n setResults(res.results ?? []);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Search failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n const clear = useCallback(() => { setResults([]); setError(null); }, []);\r\n\r\n return { results, loading, error, search, clear };\r\n}\r\n","'use client';\r\n\r\nimport React, { createContext, useContext, useEffect, useRef } from 'react';\r\nimport { HuskelClient, getHuskelClient } from '../client';\r\nimport { HuskelConfig } from '../types';\r\n\r\nexport const HuskelContext = createContext<HuskelClient | null>(null);\r\n\r\ninterface HuskelProviderProps extends HuskelConfig {\r\n children: React.ReactNode;\r\n}\r\n\r\nexport function HuskelProvider({ siteId, apiUrl, apiToken, children }: HuskelProviderProps) {\r\n const clientRef = useRef<HuskelClient | null>(null);\r\n\r\n if (!clientRef.current) {\r\n clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken });\r\n }\r\n\r\n // Clean up the online listener and timers when the provider unmounts\r\n // (prevents leaks during hot module reload and React StrictMode double-mount)\r\n useEffect(() => {\r\n return () => {\r\n clientRef.current?.destroy();\r\n };\r\n }, []);\r\n\r\n return (\r\n <HuskelContext.Provider value={clientRef.current}>\r\n {children}\r\n </HuskelContext.Provider>\r\n );\r\n}\r\n\r\nexport function useHuskelContext(): HuskelClient {\r\n const context = useContext(HuskelContext);\r\n if (!context) {\r\n return getHuskelClient();\r\n }\r\n return context;\r\n}\r\n\r\n","import { useCallback, useState } from 'react';\r\nimport { RawProductInput } from '../types';\r\nimport { useHuskelContext } from '../components/HuskelProvider';\r\n\r\ninterface UseIngestReturn {\r\n ingest: (product: RawProductInput) => Promise<void>;\r\n ingestBatch: (products: RawProductInput[]) => Promise<void>;\r\n loading: boolean;\r\n error: string | null;\r\n}\r\n\r\nexport function useIngest(): UseIngestReturn {\r\n const client = useHuskelContext();\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const ingest = useCallback(async (product: RawProductInput) => {\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n await client.queueIngest(product);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Ingest failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n const ingestBatch = useCallback(async (products: RawProductInput[]) => {\r\n if (!products.length) return;\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n await client.queueIngestBatch(products);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Batch ingest failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n return { ingest, ingestBatch, loading, error };\r\n}\r\n","import { useEffect, useRef } from 'react';\nimport { RawProductInput } from '../types';\nimport { getHuskelClient } from '../client';\n\n/**\n * usePageIngest — drop this into any product page component.\n * The moment a customer's browser renders the page, the product is\n * automatically captured and queued for ingestion into the vector index.\n *\n * No configuration needed beyond <HuskelProvider> in your layout.\n *\n * @example\n * // Product detail page — Next.js or React\n * export function ProductPage({ product }) {\n * usePageIngest({\n * name: product.title,\n * price: product.price,\n * url: window.location.href,\n * images: [product.thumbnail],\n * category: product.category,\n * });\n * return <div>...</div>;\n * }\n */\nexport function usePageIngest(product: RawProductInput | null | undefined): void {\n // Use url as the stable key — avoids re-ingesting on unrelated re-renders\n const ingestedRef = useRef<string | null>(null);\n\n useEffect(() => {\n if (!product) return;\n\n // Resolve URL — falls back to window.location if not provided\n const url =\n product.url ||\n (typeof window !== 'undefined' ? window.location.href : '');\n\n // Guard: only ingest once per URL per component lifecycle\n if (ingestedRef.current === url) return;\n ingestedRef.current = url;\n\n try {\n getHuskelClient().queueIngest({ ...product, url });\n } catch {\n // Client not initialised — silently skip\n }\n }, [product?.url ?? product?.name]);\n}\n","import React, { useState, useEffect, useRef } from 'react';\r\nimport { useSearch } from '../hooks/useSearch';\r\nimport { SearchResult } from '../types';\r\n\r\ninterface SearchBarProps {\r\n placeholder?: string;\r\n limit?: number;\r\n debounceMs?: number;\r\n onSelect?: (result: SearchResult) => void;\r\n className?: string;\r\n inputClassName?: string;\r\n dropdownClassName?: string;\r\n renderResult?: (result: SearchResult) => React.ReactNode;\r\n}\r\n\r\nconst S = `\r\n .hsk-wrap{position:relative;width:100%;font-family:inherit}\r\n .hsk-input{width:100%;padding:10px 16px;font-size:15px;border:1.5px solid #e2e2e2;border-radius:8px;outline:none;box-sizing:border-box;background:#fff;transition:border-color .2s}\r\n .hsk-input:focus{border-color:#f47c3c}\r\n .hsk-drop{position:absolute;top:calc(100% + 6px);left:0;right:0;background:#fff;border:1px solid #e2e2e2;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.1);z-index:9999;max-height:360px;overflow-y:auto}\r\n .hsk-item{display:flex;align-items:center;gap:12px;padding:10px 14px;cursor:pointer;transition:background .15s}\r\n .hsk-item:hover{background:#faf5f1}\r\n .hsk-item img{width:40px;height:40px;object-fit:cover;border-radius:4px}\r\n .hsk-item-name{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\r\n .hsk-item-price{font-size:13px;color:#f47c3c;margin-top:2px}\r\n .hsk-msg{padding:16px;text-align:center;font-size:14px;color:#888}\r\n`;\r\n\r\nexport function SearchBar({\r\n placeholder = 'Search for what you want — how you want',\r\n limit = 10,\r\n debounceMs = 300,\r\n onSelect,\r\n className,\r\n inputClassName,\r\n dropdownClassName,\r\n renderResult,\r\n}: SearchBarProps) {\r\n const [query, setQuery] = useState('');\r\n const [open, setOpen] = useState(false);\r\n const { results, loading, search, clear } = useSearch();\r\n const timer = useRef<ReturnType<typeof setTimeout>>();\r\n const wrap = useRef<HTMLDivElement>(null);\r\n\r\n useEffect(() => {\r\n clearTimeout(timer.current);\r\n if (!query.trim()) { clear(); setOpen(false); return; }\r\n timer.current = setTimeout(() => { search(query, limit); setOpen(true); }, debounceMs);\r\n return () => clearTimeout(timer.current);\r\n }, [query, search, clear, limit, debounceMs]);\r\n\r\n useEffect(() => {\r\n const handler = (e: MouseEvent) => {\r\n if (wrap.current && !wrap.current.contains(e.target as Node)) setOpen(false);\r\n };\r\n document.addEventListener('mousedown', handler);\r\n return () => document.removeEventListener('mousedown', handler);\r\n }, []);\r\n\r\n const handleSelect = (r: SearchResult) => {\r\n setOpen(false);\r\n setQuery(r.product.name);\r\n onSelect?.(r);\r\n };\r\n\r\n return (\r\n <>\r\n <style>{S}</style>\r\n <div className={`hsk-wrap ${className ?? ''}`} ref={wrap}>\r\n <input\r\n className={`hsk-input ${inputClassName ?? ''}`}\r\n type=\"text\"\r\n value={query}\r\n placeholder={placeholder}\r\n onChange={e => setQuery(e.target.value)}\r\n onFocus={() => results.length && setOpen(true)}\r\n />\r\n {open && (\r\n <div className={`hsk-drop ${dropdownClassName ?? ''}`}>\r\n {loading && <div className=\"hsk-msg\">Searching…</div>}\r\n {!loading && results.length === 0 && <div className=\"hsk-msg\">No results for \"{query}\"</div>}\r\n {results.map(r =>\r\n renderResult ? (\r\n <div key={r.id} onClick={() => handleSelect(r)}>{renderResult(r)}</div>\r\n ) : (\r\n <div key={r.id} className=\"hsk-item\" onClick={() => handleSelect(r)}>\r\n {r.product.images?.[0] && <img src={r.product.images[0]} alt={r.product.name} />}\r\n <div>\r\n <div className=\"hsk-item-name\">{r.product.name}</div>\r\n <div className=\"hsk-item-price\">{r.product.currency ?? 'KES'} {r.product.price}</div>\r\n </div>\r\n </div>\r\n )\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n </>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { useHuskelContext } from './HuskelProvider';\r\nimport { SearchResult } from '../types';\r\n\r\ninterface SparkleProps {\r\n productName: string;\r\n limit?: number;\r\n onResult?: (results: SearchResult[]) => void;\r\n className?: string;\r\n}\r\n\r\nconst S = `\r\n .hsk-sparkle{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;font-size:12px;font-weight:600;background:#f47c3c;color:#fff;border:none;border-radius:20px;cursor:pointer;transition:opacity .2s,transform .15s}\r\n .hsk-sparkle:hover{opacity:.88;transform:scale(1.04)}\r\n .hsk-sparkle:disabled{opacity:.5;cursor:not-allowed}\r\n`;\r\n\r\nexport function Sparkle({ productName, limit = 5, onResult, className }: SparkleProps) {\r\n const client = useHuskelContext();\r\n const [loading, setLoading] = useState(false);\r\n\r\n const handleClick = async () => {\r\n setLoading(true);\r\n try {\r\n const res = await client.api.search(productName, limit);\r\n onResult?.(res.results);\r\n } catch (e) {\r\n console.error('[Huskel Sparkle]', e);\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <>\r\n <style>{S}</style>\r\n <button className={`hsk-sparkle ${className ?? ''}`} onClick={handleClick} disabled={loading}>\r\n ✦ {loading ? 'Finding…' : 'Similar'}\r\n </button>\r\n </>\r\n );\r\n}\r\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react","import_react","import_jsx_runtime","import_react","import_jsx_runtime","S"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/api.ts","../src/client.ts","../src/hooks/useHuskel.ts","../src/hooks/useSearch.ts","../src/components/HuskelProvider.tsx","../src/hooks/useIngest.ts","../src/hooks/usePageIngest.ts","../src/components/SearchBar.tsx","../src/components/Sparkle.tsx"],"sourcesContent":["export { initHuskel, getHuskelClient, HuskelClient } from './client';\r\nexport { HuskelAPI } from './api';\r\nexport { useHuskel } from './hooks/useHuskel';\r\nexport { useSearch } from './hooks/useSearch';\r\nexport { useIngest } from './hooks/useIngest';\r\nexport { usePageIngest } from './hooks/usePageIngest';\r\nexport { SearchBar } from './components/SearchBar';\r\nexport { Sparkle } from './components/Sparkle';\r\nexport { HuskelProvider } from './components/HuskelProvider';\r\nexport type {\r\n Product,\r\n RawProductInput,\r\n HuskelConfig,\r\n SearchRequest,\r\n SearchResult,\r\n SearchResponse,\r\n IngestResponse,\r\n HuskelError,\r\n} from './types';\r\n","import { Product, SearchResponse, IngestResponse, HuskelError } from './types';\r\n\r\nconst MAX_RETRIES = 3;\r\nconst RETRY_DELAYS = [500, 1000, 2000]; // ms\r\n\r\nfunction log(level: 'info' | 'warn' | 'error', msg: string, data?: unknown) {\r\n const prefix = '[Huskel]';\r\n if (level === 'error') console.error(prefix, msg, data ?? '');\r\n else if (level === 'warn') console.warn(prefix, msg, data ?? '');\r\n else console.log(prefix, msg, data ?? '');\r\n}\r\n\r\nasync function sleep(ms: number) {\r\n return new Promise(r => setTimeout(r, ms));\r\n}\r\n\r\nexport class HuskelAPI {\r\n constructor(\r\n private apiUrl: string,\r\n private siteId: string,\r\n private apiToken: string,\r\n private getShopperId?: () => string | undefined,\r\n private getSessionId?: () => string | undefined\r\n ) {}\r\n\r\n private async post<T>(path: string, body: unknown, attempt = 0): Promise<T> {\r\n const url = `${this.apiUrl}${path}`;\r\n\r\n try {\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n 'X-Huskel-Token': this.apiToken,\r\n 'X-Huskel-Site': this.siteId,\r\n };\r\n\r\n const shopperId = this.getShopperId?.();\r\n if (shopperId) {\r\n headers['X-Huskel-Shopper-Id'] = shopperId;\r\n }\r\n\r\n const sessionId = this.getSessionId?.();\r\n if (sessionId) {\r\n headers['X-Huskel-Session-Id'] = sessionId;\r\n }\r\n\r\n const res = await fetch(url, {\r\n method: 'POST',\r\n headers,\r\n body: JSON.stringify(body),\r\n });\r\n\r\n if (!res.ok) {\r\n const text = await res.text();\r\n const err: HuskelError = { status: res.status, message: text };\r\n\r\n // Don't retry 4xx — developer errors\r\n if (res.status >= 400 && res.status < 500) {\r\n log('error', `${path} failed [${res.status}]`, text);\r\n throw err;\r\n }\r\n\r\n // Retry 5xx\r\n if (attempt < MAX_RETRIES - 1) {\r\n log('warn', `${path} [${res.status}] retrying (${attempt + 1}/${MAX_RETRIES})...`);\r\n await sleep(RETRY_DELAYS[attempt]);\r\n return this.post(path, body, attempt + 1);\r\n }\r\n\r\n log('error', `${path} failed after ${MAX_RETRIES} attempts`, err);\r\n throw err;\r\n }\r\n\r\n return res.json();\r\n } catch (e) {\r\n // Network error (offline, DNS, etc.)\r\n if ((e as HuskelError).status === undefined) {\r\n if (attempt < MAX_RETRIES - 1) {\r\n log('warn', `${path} network error, retrying (${attempt + 1}/${MAX_RETRIES})...`);\r\n await sleep(RETRY_DELAYS[attempt]);\r\n return this.post(path, body, attempt + 1);\r\n }\r\n log('error', `${path} unreachable after ${MAX_RETRIES} attempts`);\r\n }\r\n throw e;\r\n }\r\n }\r\n\r\n async ingest(product: Product): Promise<IngestResponse> {\r\n log('info', 'ingesting product', product.name);\r\n return this.post('/ingest', { siteId: this.siteId, product });\r\n }\r\n\r\n async ingestBatch(products: Product[]): Promise<IngestResponse> {\r\n log('info', `ingesting batch of ${products.length} products`);\r\n return this.post('/ingest/batch', { siteId: this.siteId, products });\r\n }\r\n\r\n async search(query: string, limit = 10): Promise<SearchResponse> {\r\n log('info', 'search query', query);\r\n return this.post('/search', { query, siteId: this.siteId, limit });\r\n }\r\n}\r\n","import { HuskelConfig, Product, RawProductInput } from './types';\r\nimport { HuskelAPI } from './api';\r\n\r\nfunction getEnvVar(key: string): string | undefined {\r\n if (typeof globalThis !== 'undefined') {\r\n const g = globalThis as any;\r\n if (g.process && g.process.env) {\r\n return g.process.env[key];\r\n }\r\n }\r\n return undefined;\r\n}\r\n\r\nfunction mapRawProduct(input: RawProductInput): Product | null {\r\n const name = input.name || input.title || input.productName || '';\r\n \r\n let price = '';\r\n let priceNumeric: number | undefined = undefined;\r\n\r\n if (input.price !== undefined) {\r\n if (typeof input.price === 'number') {\r\n priceNumeric = input.price;\r\n price = String(input.price);\r\n } else {\r\n price = input.price;\r\n const num = parseFloat(input.price.replace(/[^0-9.]/g, ''));\r\n priceNumeric = isNaN(num) ? undefined : num;\r\n }\r\n }\r\n if (input.priceNumeric !== undefined) {\r\n priceNumeric = input.priceNumeric;\r\n }\r\n\r\n let url = input.url || '';\r\n if (!url && typeof window !== 'undefined') {\r\n url = window.location.href;\r\n }\r\n\r\n let slug = input.slug || input.id || input.productId || '';\r\n if (!slug && url) {\r\n slug = url.split('/').filter(Boolean).pop() || '';\r\n }\r\n if (!slug && name) {\r\n slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');\r\n }\r\n\r\n let images: string[] = [];\r\n if (input.images) {\r\n images = input.images;\r\n } else if (input.image) {\r\n images = [input.image];\r\n } else if (input.thumbnail) {\r\n images = [input.thumbnail];\r\n }\r\n\r\n if (!name) {\r\n console.warn('[Huskel] Validation warning: Product name/title is missing. Skipping:', input);\r\n return null;\r\n }\r\n if (!price) {\r\n console.warn('[Huskel] Validation warning: Product price is missing. Skipping:', input);\r\n return null;\r\n }\r\n if (!url) {\r\n console.warn('[Huskel] Validation warning: Product URL is missing. Skipping:', input);\r\n return null;\r\n }\r\n\r\n return {\r\n name,\r\n price,\r\n url,\r\n brand: input.brand,\r\n description: input.description,\r\n originalPrice: input.originalPrice,\r\n discount: input.discount,\r\n currency: input.currency ?? 'KES',\r\n stock: input.stock,\r\n availability: input.availability,\r\n rating: input.rating,\r\n reviewCount: input.reviewCount,\r\n category: input.category,\r\n subCategory: input.subCategory,\r\n tags: input.tags,\r\n images: images.length > 0 ? images : undefined,\r\n specs: input.specs,\r\n priceNumeric,\r\n slug,\r\n };\r\n}\r\n\r\nfunction generateUUID(): string {\r\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\r\n return crypto.randomUUID();\r\n }\r\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\r\n const r = (Math.random() * 16) | 0;\r\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\r\n return v.toString(16);\r\n });\r\n}\r\n\r\nexport class HuskelClient {\r\n readonly api: HuskelAPI;\r\n private ingestQueue: Product[] = [];\r\n private ingestTimer: ReturnType<typeof setTimeout> | null = null;\r\n private ingestedUrls = new Set<string>();\r\n private onlineHandler: (() => void) | null = null;\r\n private shopperId?: string;\r\n private sessionId: string = '';\r\n\r\n constructor(config: HuskelConfig) {\r\n const siteId = config.siteId || getEnvVar('NEXT_PUBLIC_HUSKEL_SITE_ID') || '';\r\n const apiUrl = config.apiUrl || getEnvVar('NEXT_PUBLIC_HUSKEL_API_URL') || '';\r\n const apiToken = config.apiToken || getEnvVar('NEXT_PUBLIC_HUSKEL_API_TOKEN') || '';\r\n\r\n // Runtime validation — fail loudly so misconfiguration is never silent\r\n if (!siteId) console.error('[Huskel] Missing siteId. Set it via <HuskelProvider siteId=\"...\"> or NEXT_PUBLIC_HUSKEL_SITE_ID.');\r\n if (!apiUrl) console.error('[Huskel] Missing apiUrl. Set it via <HuskelProvider apiUrl=\"...\"> or NEXT_PUBLIC_HUSKEL_API_URL.');\r\n if (!apiToken) console.error('[Huskel] Missing apiToken. Set it via <HuskelProvider apiToken=\"...\"> or NEXT_PUBLIC_HUSKEL_API_TOKEN.');\r\n\r\n this.shopperId = config.shopperId;\r\n this.initSession();\r\n\r\n this.api = new HuskelAPI(\r\n apiUrl,\r\n siteId,\r\n apiToken,\r\n () => this.shopperId,\r\n () => this.sessionId\r\n );\r\n instance = this;\r\n\r\n if (typeof window !== 'undefined') {\r\n this.onlineHandler = () => {\r\n console.log('[Huskel] Connectivity restored, flushing queued ingestions.');\r\n this.flushQueue();\r\n };\r\n window.addEventListener('online', this.onlineHandler);\r\n }\r\n }\r\n\r\n setShopperId(id: string | undefined) {\r\n this.shopperId = id;\r\n }\r\n\r\n getShopperId(): string | undefined {\r\n return this.shopperId;\r\n }\r\n\r\n getSessionId(): string {\r\n return this.sessionId;\r\n }\r\n\r\n private initSession() {\r\n if (typeof window !== 'undefined' && window.sessionStorage) {\r\n try {\r\n let sid = window.sessionStorage.getItem('huskel_session_id');\r\n if (!sid) {\r\n sid = generateUUID();\r\n window.sessionStorage.setItem('huskel_session_id', sid);\r\n }\r\n this.sessionId = sid;\r\n return;\r\n } catch (e) {\r\n // Fallback if sessionStorage is disabled or private mode\r\n }\r\n }\r\n this.sessionId = generateUUID();\r\n }\r\n\r\n destroy() {\r\n if (typeof window !== 'undefined' && this.onlineHandler) {\r\n window.removeEventListener('online', this.onlineHandler);\r\n this.onlineHandler = null;\r\n }\r\n if (this.ingestTimer) {\r\n clearTimeout(this.ingestTimer);\r\n this.ingestTimer = null;\r\n }\r\n if (instance === this) instance = null;\r\n }\r\n\r\n async queueIngest(rawProduct: RawProductInput): Promise<void> {\r\n const product = mapRawProduct(rawProduct);\r\n if (!product) return;\r\n\r\n if (this.ingestedUrls.has(product.url)) {\r\n return;\r\n }\r\n this.ingestedUrls.add(product.url);\r\n\r\n this.ingestQueue.push(product);\r\n this.scheduleFlush();\r\n }\r\n\r\n async queueIngestBatch(rawProducts: RawProductInput[]): Promise<void> {\r\n rawProducts.forEach(p => {\r\n const product = mapRawProduct(p);\r\n if (!product) return;\r\n\r\n if (this.ingestedUrls.has(product.url)) {\r\n return;\r\n }\r\n this.ingestedUrls.add(product.url);\r\n this.ingestQueue.push(product);\r\n });\r\n\r\n if (this.ingestQueue.length > 0) {\r\n this.scheduleFlush();\r\n }\r\n }\r\n\r\n private scheduleFlush() {\r\n if (this.ingestTimer) return;\r\n this.ingestTimer = setTimeout(() => {\r\n this.flushQueue();\r\n }, 300);\r\n }\r\n\r\n private async flushQueue() {\r\n this.ingestTimer = null;\r\n if (this.ingestQueue.length === 0) return;\r\n\r\n if (typeof navigator !== 'undefined' && !navigator.onLine) {\r\n console.warn('[Huskel] Browser offline. Postponing ingestion.');\r\n return;\r\n }\r\n\r\n const batch = [...this.ingestQueue];\r\n this.ingestQueue = [];\r\n\r\n try {\r\n await this.api.ingestBatch(batch);\r\n } catch (e: any) {\r\n if (e.status && e.status >= 400 && e.status < 500) {\r\n console.error('[Huskel] Ingestion discarded due to client error:', e.message);\r\n return;\r\n }\r\n\r\n // Re-queue and schedule another flush so items are not stuck forever\r\n console.warn('[Huskel] Ingestion failed. Re-queuing to retry.', e);\r\n this.ingestQueue = [...batch, ...this.ingestQueue];\r\n this.scheduleFlush();\r\n }\r\n }\r\n}\r\n\r\nlet instance: HuskelClient | null = null;\r\n\r\nexport function initHuskel(config: HuskelConfig): HuskelClient {\r\n instance = new HuskelClient(config);\r\n return instance;\r\n}\r\n\r\nexport function getHuskelClient(): HuskelClient {\r\n if (!instance) {\r\n const siteId = getEnvVar('NEXT_PUBLIC_HUSKEL_SITE_ID');\r\n const apiUrl = getEnvVar('NEXT_PUBLIC_HUSKEL_API_URL');\r\n const apiToken = getEnvVar('NEXT_PUBLIC_HUSKEL_API_TOKEN');\r\n\r\n if (siteId && apiUrl && apiToken) {\r\n instance = new HuskelClient({ siteId, apiUrl, apiToken });\r\n } else {\r\n throw new Error('[Huskel] Call initHuskel() or set NEXT_PUBLIC_HUSKEL_* environment variables before using the client.');\r\n }\r\n }\r\n return instance;\r\n}\r\n","import { useRef } from 'react';\r\nimport { HuskelConfig } from '../types';\r\nimport { HuskelClient, initHuskel } from '../client';\r\n\r\n/**\r\n * @deprecated Use <HuskelProvider> instead to avoid SSR issues.\r\n */\r\nexport function useHuskel(config: HuskelConfig): HuskelClient {\r\n const clientRef = useRef<HuskelClient | null>(null);\r\n\r\n if (!clientRef.current) {\r\n console.warn('[Huskel] useHuskel() is deprecated. Please wrap your application in <HuskelProvider> instead.');\r\n clientRef.current = initHuskel(config);\r\n }\r\n\r\n return clientRef.current;\r\n}\r\n","import { useState, useCallback, useRef } from 'react';\r\nimport { SearchResult } from '../types';\r\nimport { useHuskelContext } from '../components/HuskelProvider';\r\n\r\ninterface UseSearchReturn {\r\n results: SearchResult[];\r\n loading: boolean;\r\n error: string | null;\r\n search: (query: string, limit?: number) => Promise<void>;\r\n clear: () => void;\r\n}\r\n\r\nexport function useSearch(): UseSearchReturn {\r\n const client = useHuskelContext();\r\n const [results, setResults] = useState<SearchResult[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const abortRef = useRef<AbortController | null>(null);\r\n\r\n const search = useCallback(async (query: string, limit = 10) => {\r\n if (!query.trim()) { setResults([]); return; }\r\n abortRef.current?.abort();\r\n abortRef.current = new AbortController();\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n const res = await client.api.search(query, limit);\r\n setResults(res.results ?? []);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Search failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n const clear = useCallback(() => { setResults([]); setError(null); }, []);\r\n\r\n return { results, loading, error, search, clear };\r\n}\r\n","'use client';\r\n\r\nimport React, { createContext, useContext, useEffect, useRef } from 'react';\r\nimport { HuskelClient, getHuskelClient } from '../client';\r\nimport { HuskelConfig } from '../types';\r\n\r\nexport const HuskelContext = createContext<HuskelClient | null>(null);\r\n\r\ninterface HuskelProviderProps extends HuskelConfig {\r\n children: React.ReactNode;\r\n}\r\n\r\nexport function HuskelProvider({ siteId, apiUrl, apiToken, shopperId, children }: HuskelProviderProps) {\r\n const clientRef = useRef<HuskelClient | null>(null);\r\n\r\n if (!clientRef.current) {\r\n clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken, shopperId });\r\n }\r\n\r\n // Update shopperId dynamically when it changes (e.g., shopper logs in/out)\r\n useEffect(() => {\r\n clientRef.current?.setShopperId(shopperId);\r\n }, [shopperId]);\r\n\r\n // Clean up the online listener and timers when the provider unmounts\r\n // (prevents leaks during hot module reload and React StrictMode double-mount)\r\n useEffect(() => {\r\n return () => {\r\n clientRef.current?.destroy();\r\n };\r\n }, []);\r\n\r\n return (\r\n <HuskelContext.Provider value={clientRef.current}>\r\n {children}\r\n </HuskelContext.Provider>\r\n );\r\n}\r\n\r\nexport function useHuskelContext(): HuskelClient {\r\n const context = useContext(HuskelContext);\r\n if (!context) {\r\n return getHuskelClient();\r\n }\r\n return context;\r\n}\r\n\r\n","import { useCallback, useState } from 'react';\r\nimport { RawProductInput } from '../types';\r\nimport { useHuskelContext } from '../components/HuskelProvider';\r\n\r\ninterface UseIngestReturn {\r\n ingest: (product: RawProductInput) => Promise<void>;\r\n ingestBatch: (products: RawProductInput[]) => Promise<void>;\r\n loading: boolean;\r\n error: string | null;\r\n}\r\n\r\nexport function useIngest(): UseIngestReturn {\r\n const client = useHuskelContext();\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const ingest = useCallback(async (product: RawProductInput) => {\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n await client.queueIngest(product);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Ingest failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n const ingestBatch = useCallback(async (products: RawProductInput[]) => {\r\n if (!products.length) return;\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n await client.queueIngestBatch(products);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Batch ingest failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n return { ingest, ingestBatch, loading, error };\r\n}\r\n","import { useEffect, useRef } from 'react';\nimport { RawProductInput } from '../types';\nimport { getHuskelClient } from '../client';\n\n/**\n * usePageIngest — drop this into any product page component.\n * The moment a customer's browser renders the page, the product is\n * automatically captured and queued for ingestion into the vector index.\n *\n * No configuration needed beyond <HuskelProvider> in your layout.\n *\n * @example\n * // Product detail page — Next.js or React\n * export function ProductPage({ product }) {\n * usePageIngest({\n * name: product.title,\n * price: product.price,\n * url: window.location.href,\n * images: [product.thumbnail],\n * category: product.category,\n * });\n * return <div>...</div>;\n * }\n */\nexport function usePageIngest(product: RawProductInput | null | undefined): void {\n // Use url as the stable key — avoids re-ingesting on unrelated re-renders\n const ingestedRef = useRef<string | null>(null);\n\n useEffect(() => {\n if (!product) return;\n\n // Resolve URL — falls back to window.location if not provided\n const url =\n product.url ||\n (typeof window !== 'undefined' ? window.location.href : '');\n\n // Guard: only ingest once per URL per component lifecycle\n if (ingestedRef.current === url) return;\n ingestedRef.current = url;\n\n try {\n getHuskelClient().queueIngest({ ...product, url });\n } catch {\n // Client not initialised — silently skip\n }\n }, [product?.url ?? product?.name]);\n}\n","import React, { useState, useEffect, useRef } from 'react';\r\nimport { useSearch } from '../hooks/useSearch';\r\nimport { SearchResult } from '../types';\r\n\r\ninterface SearchBarProps {\r\n placeholder?: string;\r\n limit?: number;\r\n debounceMs?: number;\r\n onSelect?: (result: SearchResult) => void;\r\n className?: string;\r\n inputClassName?: string;\r\n dropdownClassName?: string;\r\n renderResult?: (result: SearchResult) => React.ReactNode;\r\n}\r\n\r\nconst S = `\r\n .hsk-wrap{position:relative;width:100%;font-family:inherit}\r\n .hsk-input{width:100%;padding:10px 16px;font-size:15px;border:1.5px solid #e2e2e2;border-radius:8px;outline:none;box-sizing:border-box;background:#fff;transition:border-color .2s}\r\n .hsk-input:focus{border-color:#f47c3c}\r\n .hsk-drop{position:absolute;top:calc(100% + 6px);left:0;right:0;background:#fff;border:1px solid #e2e2e2;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.1);z-index:9999;max-height:360px;overflow-y:auto}\r\n .hsk-item{display:flex;align-items:center;gap:12px;padding:10px 14px;cursor:pointer;transition:background .15s}\r\n .hsk-item:hover{background:#faf5f1}\r\n .hsk-item img{width:40px;height:40px;object-fit:cover;border-radius:4px}\r\n .hsk-item-name{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\r\n .hsk-item-price{font-size:13px;color:#f47c3c;margin-top:2px}\r\n .hsk-msg{padding:16px;text-align:center;font-size:14px;color:#888}\r\n`;\r\n\r\nexport function SearchBar({\r\n placeholder = 'Search for what you want — how you want',\r\n limit = 10,\r\n debounceMs = 300,\r\n onSelect,\r\n className,\r\n inputClassName,\r\n dropdownClassName,\r\n renderResult,\r\n}: SearchBarProps) {\r\n const [query, setQuery] = useState('');\r\n const [open, setOpen] = useState(false);\r\n const { results, loading, search, clear } = useSearch();\r\n const timer = useRef<ReturnType<typeof setTimeout>>();\r\n const wrap = useRef<HTMLDivElement>(null);\r\n\r\n useEffect(() => {\r\n clearTimeout(timer.current);\r\n if (!query.trim()) { clear(); setOpen(false); return; }\r\n timer.current = setTimeout(() => { search(query, limit); setOpen(true); }, debounceMs);\r\n return () => clearTimeout(timer.current);\r\n }, [query, search, clear, limit, debounceMs]);\r\n\r\n useEffect(() => {\r\n const handler = (e: MouseEvent) => {\r\n if (wrap.current && !wrap.current.contains(e.target as Node)) setOpen(false);\r\n };\r\n document.addEventListener('mousedown', handler);\r\n return () => document.removeEventListener('mousedown', handler);\r\n }, []);\r\n\r\n const handleSelect = (r: SearchResult) => {\r\n setOpen(false);\r\n setQuery(r.product.name);\r\n onSelect?.(r);\r\n };\r\n\r\n return (\r\n <>\r\n <style>{S}</style>\r\n <div className={`hsk-wrap ${className ?? ''}`} ref={wrap}>\r\n <input\r\n className={`hsk-input ${inputClassName ?? ''}`}\r\n type=\"text\"\r\n value={query}\r\n placeholder={placeholder}\r\n onChange={e => setQuery(e.target.value)}\r\n onFocus={() => results.length && setOpen(true)}\r\n />\r\n {open && (\r\n <div className={`hsk-drop ${dropdownClassName ?? ''}`}>\r\n {loading && <div className=\"hsk-msg\">Searching…</div>}\r\n {!loading && results.length === 0 && <div className=\"hsk-msg\">No results for \"{query}\"</div>}\r\n {results.map(r =>\r\n renderResult ? (\r\n <div key={r.id} onClick={() => handleSelect(r)}>{renderResult(r)}</div>\r\n ) : (\r\n <div key={r.id} className=\"hsk-item\" onClick={() => handleSelect(r)}>\r\n {r.product.images?.[0] && <img src={r.product.images[0]} alt={r.product.name} />}\r\n <div>\r\n <div className=\"hsk-item-name\">{r.product.name}</div>\r\n <div className=\"hsk-item-price\">{r.product.currency ?? 'KES'} {r.product.price}</div>\r\n </div>\r\n </div>\r\n )\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n </>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { useHuskelContext } from './HuskelProvider';\r\nimport { SearchResult } from '../types';\r\n\r\ninterface SparkleProps {\r\n productName: string;\r\n limit?: number;\r\n onResult?: (results: SearchResult[]) => void;\r\n className?: string;\r\n}\r\n\r\nconst S = `\r\n .hsk-sparkle{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;font-size:12px;font-weight:600;background:#f47c3c;color:#fff;border:none;border-radius:20px;cursor:pointer;transition:opacity .2s,transform .15s}\r\n .hsk-sparkle:hover{opacity:.88;transform:scale(1.04)}\r\n .hsk-sparkle:disabled{opacity:.5;cursor:not-allowed}\r\n`;\r\n\r\nexport function Sparkle({ productName, limit = 5, onResult, className }: SparkleProps) {\r\n const client = useHuskelContext();\r\n const [loading, setLoading] = useState(false);\r\n\r\n const handleClick = async () => {\r\n setLoading(true);\r\n try {\r\n const res = await client.api.search(productName, limit);\r\n onResult?.(res.results);\r\n } catch (e) {\r\n console.error('[Huskel Sparkle]', e);\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <>\r\n <style>{S}</style>\r\n <button className={`hsk-sparkle ${className ?? ''}`} onClick={handleClick} disabled={loading}>\r\n ✦ {loading ? 'Finding…' : 'Similar'}\r\n </button>\r\n </>\r\n );\r\n}\r\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react","import_react","import_jsx_runtime","import_react","import_jsx_runtime","S"]}
package/dist/index.mjs CHANGED
@@ -32,21 +32,33 @@ async function sleep(ms) {
32
32
  return new Promise((r) => setTimeout(r, ms));
33
33
  }
34
34
  var HuskelAPI = class {
35
- constructor(apiUrl, siteId, apiToken) {
35
+ constructor(apiUrl, siteId, apiToken, getShopperId, getSessionId) {
36
36
  this.apiUrl = apiUrl;
37
37
  this.siteId = siteId;
38
38
  this.apiToken = apiToken;
39
+ this.getShopperId = getShopperId;
40
+ this.getSessionId = getSessionId;
39
41
  }
40
42
  async post(path, body, attempt = 0) {
43
+ var _a, _b;
41
44
  const url = `${this.apiUrl}${path}`;
42
45
  try {
46
+ const headers = {
47
+ "Content-Type": "application/json",
48
+ "X-Huskel-Token": this.apiToken,
49
+ "X-Huskel-Site": this.siteId
50
+ };
51
+ const shopperId = (_a = this.getShopperId) == null ? void 0 : _a.call(this);
52
+ if (shopperId) {
53
+ headers["X-Huskel-Shopper-Id"] = shopperId;
54
+ }
55
+ const sessionId = (_b = this.getSessionId) == null ? void 0 : _b.call(this);
56
+ if (sessionId) {
57
+ headers["X-Huskel-Session-Id"] = sessionId;
58
+ }
43
59
  const res = await fetch(url, {
44
60
  method: "POST",
45
- headers: {
46
- "Content-Type": "application/json",
47
- "X-Huskel-Token": this.apiToken,
48
- "X-Huskel-Site": this.siteId
49
- },
61
+ headers,
50
62
  body: JSON.stringify(body)
51
63
  });
52
64
  if (!res.ok) {
@@ -172,19 +184,38 @@ function mapRawProduct(input) {
172
184
  slug
173
185
  };
174
186
  }
187
+ function generateUUID() {
188
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
189
+ return crypto.randomUUID();
190
+ }
191
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
192
+ const r = Math.random() * 16 | 0;
193
+ const v = c === "x" ? r : r & 3 | 8;
194
+ return v.toString(16);
195
+ });
196
+ }
175
197
  var HuskelClient = class {
176
198
  constructor(config) {
177
199
  this.ingestQueue = [];
178
200
  this.ingestTimer = null;
179
201
  this.ingestedUrls = /* @__PURE__ */ new Set();
180
202
  this.onlineHandler = null;
203
+ this.sessionId = "";
181
204
  const siteId = config.siteId || getEnvVar("NEXT_PUBLIC_HUSKEL_SITE_ID") || "";
182
205
  const apiUrl = config.apiUrl || getEnvVar("NEXT_PUBLIC_HUSKEL_API_URL") || "";
183
206
  const apiToken = config.apiToken || getEnvVar("NEXT_PUBLIC_HUSKEL_API_TOKEN") || "";
184
207
  if (!siteId) console.error('[Huskel] Missing siteId. Set it via <HuskelProvider siteId="..."> or NEXT_PUBLIC_HUSKEL_SITE_ID.');
185
208
  if (!apiUrl) console.error('[Huskel] Missing apiUrl. Set it via <HuskelProvider apiUrl="..."> or NEXT_PUBLIC_HUSKEL_API_URL.');
186
209
  if (!apiToken) console.error('[Huskel] Missing apiToken. Set it via <HuskelProvider apiToken="..."> or NEXT_PUBLIC_HUSKEL_API_TOKEN.');
187
- this.api = new HuskelAPI(apiUrl, siteId, apiToken);
210
+ this.shopperId = config.shopperId;
211
+ this.initSession();
212
+ this.api = new HuskelAPI(
213
+ apiUrl,
214
+ siteId,
215
+ apiToken,
216
+ () => this.shopperId,
217
+ () => this.sessionId
218
+ );
188
219
  instance = this;
189
220
  if (typeof window !== "undefined") {
190
221
  this.onlineHandler = () => {
@@ -194,6 +225,30 @@ var HuskelClient = class {
194
225
  window.addEventListener("online", this.onlineHandler);
195
226
  }
196
227
  }
228
+ setShopperId(id) {
229
+ this.shopperId = id;
230
+ }
231
+ getShopperId() {
232
+ return this.shopperId;
233
+ }
234
+ getSessionId() {
235
+ return this.sessionId;
236
+ }
237
+ initSession() {
238
+ if (typeof window !== "undefined" && window.sessionStorage) {
239
+ try {
240
+ let sid = window.sessionStorage.getItem("huskel_session_id");
241
+ if (!sid) {
242
+ sid = generateUUID();
243
+ window.sessionStorage.setItem("huskel_session_id", sid);
244
+ }
245
+ this.sessionId = sid;
246
+ return;
247
+ } catch (e) {
248
+ }
249
+ }
250
+ this.sessionId = generateUUID();
251
+ }
197
252
  destroy() {
198
253
  if (typeof window !== "undefined" && this.onlineHandler) {
199
254
  window.removeEventListener("online", this.onlineHandler);
@@ -294,11 +349,15 @@ import { useState, useCallback, useRef as useRef3 } from "react";
294
349
  import { createContext, useContext, useEffect, useRef as useRef2 } from "react";
295
350
  import { jsx } from "react/jsx-runtime";
296
351
  var HuskelContext = createContext(null);
297
- function HuskelProvider({ siteId, apiUrl, apiToken, children }) {
352
+ function HuskelProvider({ siteId, apiUrl, apiToken, shopperId, children }) {
298
353
  const clientRef = useRef2(null);
299
354
  if (!clientRef.current) {
300
- clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken });
355
+ clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken, shopperId });
301
356
  }
357
+ useEffect(() => {
358
+ var _a;
359
+ (_a = clientRef.current) == null ? void 0 : _a.setShopperId(shopperId);
360
+ }, [shopperId]);
302
361
  useEffect(() => {
303
362
  return () => {
304
363
  var _a;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/api.ts","../src/client.ts","../src/hooks/useHuskel.ts","../src/hooks/useSearch.ts","../src/components/HuskelProvider.tsx","../src/hooks/useIngest.ts","../src/hooks/usePageIngest.ts","../src/components/SearchBar.tsx","../src/components/Sparkle.tsx"],"sourcesContent":["import { Product, SearchResponse, IngestResponse, HuskelError } from './types';\r\n\r\nconst MAX_RETRIES = 3;\r\nconst RETRY_DELAYS = [500, 1000, 2000]; // ms\r\n\r\nfunction log(level: 'info' | 'warn' | 'error', msg: string, data?: unknown) {\r\n const prefix = '[Huskel]';\r\n if (level === 'error') console.error(prefix, msg, data ?? '');\r\n else if (level === 'warn') console.warn(prefix, msg, data ?? '');\r\n else console.log(prefix, msg, data ?? '');\r\n}\r\n\r\nasync function sleep(ms: number) {\r\n return new Promise(r => setTimeout(r, ms));\r\n}\r\n\r\nexport class HuskelAPI {\r\n constructor(\r\n private apiUrl: string,\r\n private siteId: string,\r\n private apiToken: string\r\n ) {}\r\n\r\n private async post<T>(path: string, body: unknown, attempt = 0): Promise<T> {\r\n const url = `${this.apiUrl}${path}`;\r\n\r\n try {\r\n const res = await fetch(url, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-Huskel-Token': this.apiToken,\r\n 'X-Huskel-Site': this.siteId,\r\n },\r\n body: JSON.stringify(body),\r\n });\r\n\r\n if (!res.ok) {\r\n const text = await res.text();\r\n const err: HuskelError = { status: res.status, message: text };\r\n\r\n // Don't retry 4xx — developer errors\r\n if (res.status >= 400 && res.status < 500) {\r\n log('error', `${path} failed [${res.status}]`, text);\r\n throw err;\r\n }\r\n\r\n // Retry 5xx\r\n if (attempt < MAX_RETRIES - 1) {\r\n log('warn', `${path} [${res.status}] retrying (${attempt + 1}/${MAX_RETRIES})...`);\r\n await sleep(RETRY_DELAYS[attempt]);\r\n return this.post(path, body, attempt + 1);\r\n }\r\n\r\n log('error', `${path} failed after ${MAX_RETRIES} attempts`, err);\r\n throw err;\r\n }\r\n\r\n return res.json();\r\n } catch (e) {\r\n // Network error (offline, DNS, etc.)\r\n if ((e as HuskelError).status === undefined) {\r\n if (attempt < MAX_RETRIES - 1) {\r\n log('warn', `${path} network error, retrying (${attempt + 1}/${MAX_RETRIES})...`);\r\n await sleep(RETRY_DELAYS[attempt]);\r\n return this.post(path, body, attempt + 1);\r\n }\r\n log('error', `${path} unreachable after ${MAX_RETRIES} attempts`);\r\n }\r\n throw e;\r\n }\r\n }\r\n\r\n async ingest(product: Product): Promise<IngestResponse> {\r\n log('info', 'ingesting product', product.name);\r\n return this.post('/ingest', { siteId: this.siteId, product });\r\n }\r\n\r\n async ingestBatch(products: Product[]): Promise<IngestResponse> {\r\n log('info', `ingesting batch of ${products.length} products`);\r\n return this.post('/ingest/batch', { siteId: this.siteId, products });\r\n }\r\n\r\n async search(query: string, limit = 10): Promise<SearchResponse> {\r\n log('info', 'search query', query);\r\n return this.post('/search', { query, siteId: this.siteId, limit });\r\n }\r\n}\r\n","import { HuskelConfig, Product, RawProductInput } from './types';\r\nimport { HuskelAPI } from './api';\r\n\r\nfunction getEnvVar(key: string): string | undefined {\r\n if (typeof globalThis !== 'undefined') {\r\n const g = globalThis as any;\r\n if (g.process && g.process.env) {\r\n return g.process.env[key];\r\n }\r\n }\r\n return undefined;\r\n}\r\n\r\nfunction mapRawProduct(input: RawProductInput): Product | null {\r\n const name = input.name || input.title || input.productName || '';\r\n \r\n let price = '';\r\n let priceNumeric: number | undefined = undefined;\r\n\r\n if (input.price !== undefined) {\r\n if (typeof input.price === 'number') {\r\n priceNumeric = input.price;\r\n price = String(input.price);\r\n } else {\r\n price = input.price;\r\n const num = parseFloat(input.price.replace(/[^0-9.]/g, ''));\r\n priceNumeric = isNaN(num) ? undefined : num;\r\n }\r\n }\r\n if (input.priceNumeric !== undefined) {\r\n priceNumeric = input.priceNumeric;\r\n }\r\n\r\n let url = input.url || '';\r\n if (!url && typeof window !== 'undefined') {\r\n url = window.location.href;\r\n }\r\n\r\n let slug = input.slug || input.id || input.productId || '';\r\n if (!slug && url) {\r\n slug = url.split('/').filter(Boolean).pop() || '';\r\n }\r\n if (!slug && name) {\r\n slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');\r\n }\r\n\r\n let images: string[] = [];\r\n if (input.images) {\r\n images = input.images;\r\n } else if (input.image) {\r\n images = [input.image];\r\n } else if (input.thumbnail) {\r\n images = [input.thumbnail];\r\n }\r\n\r\n if (!name) {\r\n console.warn('[Huskel] Validation warning: Product name/title is missing. Skipping:', input);\r\n return null;\r\n }\r\n if (!price) {\r\n console.warn('[Huskel] Validation warning: Product price is missing. Skipping:', input);\r\n return null;\r\n }\r\n if (!url) {\r\n console.warn('[Huskel] Validation warning: Product URL is missing. Skipping:', input);\r\n return null;\r\n }\r\n\r\n return {\r\n name,\r\n price,\r\n url,\r\n brand: input.brand,\r\n description: input.description,\r\n originalPrice: input.originalPrice,\r\n discount: input.discount,\r\n currency: input.currency ?? 'KES',\r\n stock: input.stock,\r\n availability: input.availability,\r\n rating: input.rating,\r\n reviewCount: input.reviewCount,\r\n category: input.category,\r\n subCategory: input.subCategory,\r\n tags: input.tags,\r\n images: images.length > 0 ? images : undefined,\r\n specs: input.specs,\r\n priceNumeric,\r\n slug,\r\n };\r\n}\r\n\r\nexport class HuskelClient {\r\n readonly api: HuskelAPI;\r\n private ingestQueue: Product[] = [];\r\n private ingestTimer: ReturnType<typeof setTimeout> | null = null;\r\n private ingestedUrls = new Set<string>();\r\n private onlineHandler: (() => void) | null = null;\r\n\r\n constructor(config: HuskelConfig) {\r\n const siteId = config.siteId || getEnvVar('NEXT_PUBLIC_HUSKEL_SITE_ID') || '';\r\n const apiUrl = config.apiUrl || getEnvVar('NEXT_PUBLIC_HUSKEL_API_URL') || '';\r\n const apiToken = config.apiToken || getEnvVar('NEXT_PUBLIC_HUSKEL_API_TOKEN') || '';\r\n\r\n // Runtime validation — fail loudly so misconfiguration is never silent\r\n if (!siteId) console.error('[Huskel] Missing siteId. Set it via <HuskelProvider siteId=\"...\"> or NEXT_PUBLIC_HUSKEL_SITE_ID.');\r\n if (!apiUrl) console.error('[Huskel] Missing apiUrl. Set it via <HuskelProvider apiUrl=\"...\"> or NEXT_PUBLIC_HUSKEL_API_URL.');\r\n if (!apiToken) console.error('[Huskel] Missing apiToken. Set it via <HuskelProvider apiToken=\"...\"> or NEXT_PUBLIC_HUSKEL_API_TOKEN.');\r\n\r\n this.api = new HuskelAPI(apiUrl, siteId, apiToken);\r\n instance = this;\r\n\r\n if (typeof window !== 'undefined') {\r\n this.onlineHandler = () => {\r\n console.log('[Huskel] Connectivity restored, flushing queued ingestions.');\r\n this.flushQueue();\r\n };\r\n window.addEventListener('online', this.onlineHandler);\r\n }\r\n }\r\n\r\n destroy() {\r\n if (typeof window !== 'undefined' && this.onlineHandler) {\r\n window.removeEventListener('online', this.onlineHandler);\r\n this.onlineHandler = null;\r\n }\r\n if (this.ingestTimer) {\r\n clearTimeout(this.ingestTimer);\r\n this.ingestTimer = null;\r\n }\r\n if (instance === this) instance = null;\r\n }\r\n\r\n async queueIngest(rawProduct: RawProductInput): Promise<void> {\r\n const product = mapRawProduct(rawProduct);\r\n if (!product) return;\r\n\r\n if (this.ingestedUrls.has(product.url)) {\r\n return;\r\n }\r\n this.ingestedUrls.add(product.url);\r\n\r\n this.ingestQueue.push(product);\r\n this.scheduleFlush();\r\n }\r\n\r\n async queueIngestBatch(rawProducts: RawProductInput[]): Promise<void> {\r\n rawProducts.forEach(p => {\r\n const product = mapRawProduct(p);\r\n if (!product) return;\r\n\r\n if (this.ingestedUrls.has(product.url)) {\r\n return;\r\n }\r\n this.ingestedUrls.add(product.url);\r\n this.ingestQueue.push(product);\r\n });\r\n\r\n if (this.ingestQueue.length > 0) {\r\n this.scheduleFlush();\r\n }\r\n }\r\n\r\n private scheduleFlush() {\r\n if (this.ingestTimer) return;\r\n this.ingestTimer = setTimeout(() => {\r\n this.flushQueue();\r\n }, 300);\r\n }\r\n\r\n private async flushQueue() {\r\n this.ingestTimer = null;\r\n if (this.ingestQueue.length === 0) return;\r\n\r\n if (typeof navigator !== 'undefined' && !navigator.onLine) {\r\n console.warn('[Huskel] Browser offline. Postponing ingestion.');\r\n return;\r\n }\r\n\r\n const batch = [...this.ingestQueue];\r\n this.ingestQueue = [];\r\n\r\n try {\r\n await this.api.ingestBatch(batch);\r\n } catch (e: any) {\r\n if (e.status && e.status >= 400 && e.status < 500) {\r\n console.error('[Huskel] Ingestion discarded due to client error:', e.message);\r\n return;\r\n }\r\n\r\n // Re-queue and schedule another flush so items are not stuck forever\r\n console.warn('[Huskel] Ingestion failed. Re-queuing to retry.', e);\r\n this.ingestQueue = [...batch, ...this.ingestQueue];\r\n this.scheduleFlush();\r\n }\r\n }\r\n}\r\n\r\nlet instance: HuskelClient | null = null;\r\n\r\nexport function initHuskel(config: HuskelConfig): HuskelClient {\r\n instance = new HuskelClient(config);\r\n return instance;\r\n}\r\n\r\nexport function getHuskelClient(): HuskelClient {\r\n if (!instance) {\r\n const siteId = getEnvVar('NEXT_PUBLIC_HUSKEL_SITE_ID');\r\n const apiUrl = getEnvVar('NEXT_PUBLIC_HUSKEL_API_URL');\r\n const apiToken = getEnvVar('NEXT_PUBLIC_HUSKEL_API_TOKEN');\r\n\r\n if (siteId && apiUrl && apiToken) {\r\n instance = new HuskelClient({ siteId, apiUrl, apiToken });\r\n } else {\r\n throw new Error('[Huskel] Call initHuskel() or set NEXT_PUBLIC_HUSKEL_* environment variables before using the client.');\r\n }\r\n }\r\n return instance;\r\n}\r\n","import { useRef } from 'react';\r\nimport { HuskelConfig } from '../types';\r\nimport { HuskelClient, initHuskel } from '../client';\r\n\r\n/**\r\n * @deprecated Use <HuskelProvider> instead to avoid SSR issues.\r\n */\r\nexport function useHuskel(config: HuskelConfig): HuskelClient {\r\n const clientRef = useRef<HuskelClient | null>(null);\r\n\r\n if (!clientRef.current) {\r\n console.warn('[Huskel] useHuskel() is deprecated. Please wrap your application in <HuskelProvider> instead.');\r\n clientRef.current = initHuskel(config);\r\n }\r\n\r\n return clientRef.current;\r\n}\r\n","import { useState, useCallback, useRef } from 'react';\r\nimport { SearchResult } from '../types';\r\nimport { useHuskelContext } from '../components/HuskelProvider';\r\n\r\ninterface UseSearchReturn {\r\n results: SearchResult[];\r\n loading: boolean;\r\n error: string | null;\r\n search: (query: string, limit?: number) => Promise<void>;\r\n clear: () => void;\r\n}\r\n\r\nexport function useSearch(): UseSearchReturn {\r\n const client = useHuskelContext();\r\n const [results, setResults] = useState<SearchResult[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const abortRef = useRef<AbortController | null>(null);\r\n\r\n const search = useCallback(async (query: string, limit = 10) => {\r\n if (!query.trim()) { setResults([]); return; }\r\n abortRef.current?.abort();\r\n abortRef.current = new AbortController();\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n const res = await client.api.search(query, limit);\r\n setResults(res.results ?? []);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Search failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n const clear = useCallback(() => { setResults([]); setError(null); }, []);\r\n\r\n return { results, loading, error, search, clear };\r\n}\r\n","'use client';\r\n\r\nimport React, { createContext, useContext, useEffect, useRef } from 'react';\r\nimport { HuskelClient, getHuskelClient } from '../client';\r\nimport { HuskelConfig } from '../types';\r\n\r\nexport const HuskelContext = createContext<HuskelClient | null>(null);\r\n\r\ninterface HuskelProviderProps extends HuskelConfig {\r\n children: React.ReactNode;\r\n}\r\n\r\nexport function HuskelProvider({ siteId, apiUrl, apiToken, children }: HuskelProviderProps) {\r\n const clientRef = useRef<HuskelClient | null>(null);\r\n\r\n if (!clientRef.current) {\r\n clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken });\r\n }\r\n\r\n // Clean up the online listener and timers when the provider unmounts\r\n // (prevents leaks during hot module reload and React StrictMode double-mount)\r\n useEffect(() => {\r\n return () => {\r\n clientRef.current?.destroy();\r\n };\r\n }, []);\r\n\r\n return (\r\n <HuskelContext.Provider value={clientRef.current}>\r\n {children}\r\n </HuskelContext.Provider>\r\n );\r\n}\r\n\r\nexport function useHuskelContext(): HuskelClient {\r\n const context = useContext(HuskelContext);\r\n if (!context) {\r\n return getHuskelClient();\r\n }\r\n return context;\r\n}\r\n\r\n","import { useCallback, useState } from 'react';\r\nimport { RawProductInput } from '../types';\r\nimport { useHuskelContext } from '../components/HuskelProvider';\r\n\r\ninterface UseIngestReturn {\r\n ingest: (product: RawProductInput) => Promise<void>;\r\n ingestBatch: (products: RawProductInput[]) => Promise<void>;\r\n loading: boolean;\r\n error: string | null;\r\n}\r\n\r\nexport function useIngest(): UseIngestReturn {\r\n const client = useHuskelContext();\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const ingest = useCallback(async (product: RawProductInput) => {\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n await client.queueIngest(product);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Ingest failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n const ingestBatch = useCallback(async (products: RawProductInput[]) => {\r\n if (!products.length) return;\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n await client.queueIngestBatch(products);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Batch ingest failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n return { ingest, ingestBatch, loading, error };\r\n}\r\n","import { useEffect, useRef } from 'react';\nimport { RawProductInput } from '../types';\nimport { getHuskelClient } from '../client';\n\n/**\n * usePageIngest — drop this into any product page component.\n * The moment a customer's browser renders the page, the product is\n * automatically captured and queued for ingestion into the vector index.\n *\n * No configuration needed beyond <HuskelProvider> in your layout.\n *\n * @example\n * // Product detail page — Next.js or React\n * export function ProductPage({ product }) {\n * usePageIngest({\n * name: product.title,\n * price: product.price,\n * url: window.location.href,\n * images: [product.thumbnail],\n * category: product.category,\n * });\n * return <div>...</div>;\n * }\n */\nexport function usePageIngest(product: RawProductInput | null | undefined): void {\n // Use url as the stable key — avoids re-ingesting on unrelated re-renders\n const ingestedRef = useRef<string | null>(null);\n\n useEffect(() => {\n if (!product) return;\n\n // Resolve URL — falls back to window.location if not provided\n const url =\n product.url ||\n (typeof window !== 'undefined' ? window.location.href : '');\n\n // Guard: only ingest once per URL per component lifecycle\n if (ingestedRef.current === url) return;\n ingestedRef.current = url;\n\n try {\n getHuskelClient().queueIngest({ ...product, url });\n } catch {\n // Client not initialised — silently skip\n }\n }, [product?.url ?? product?.name]);\n}\n","import React, { useState, useEffect, useRef } from 'react';\r\nimport { useSearch } from '../hooks/useSearch';\r\nimport { SearchResult } from '../types';\r\n\r\ninterface SearchBarProps {\r\n placeholder?: string;\r\n limit?: number;\r\n debounceMs?: number;\r\n onSelect?: (result: SearchResult) => void;\r\n className?: string;\r\n inputClassName?: string;\r\n dropdownClassName?: string;\r\n renderResult?: (result: SearchResult) => React.ReactNode;\r\n}\r\n\r\nconst S = `\r\n .hsk-wrap{position:relative;width:100%;font-family:inherit}\r\n .hsk-input{width:100%;padding:10px 16px;font-size:15px;border:1.5px solid #e2e2e2;border-radius:8px;outline:none;box-sizing:border-box;background:#fff;transition:border-color .2s}\r\n .hsk-input:focus{border-color:#f47c3c}\r\n .hsk-drop{position:absolute;top:calc(100% + 6px);left:0;right:0;background:#fff;border:1px solid #e2e2e2;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.1);z-index:9999;max-height:360px;overflow-y:auto}\r\n .hsk-item{display:flex;align-items:center;gap:12px;padding:10px 14px;cursor:pointer;transition:background .15s}\r\n .hsk-item:hover{background:#faf5f1}\r\n .hsk-item img{width:40px;height:40px;object-fit:cover;border-radius:4px}\r\n .hsk-item-name{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\r\n .hsk-item-price{font-size:13px;color:#f47c3c;margin-top:2px}\r\n .hsk-msg{padding:16px;text-align:center;font-size:14px;color:#888}\r\n`;\r\n\r\nexport function SearchBar({\r\n placeholder = 'Search for what you want — how you want',\r\n limit = 10,\r\n debounceMs = 300,\r\n onSelect,\r\n className,\r\n inputClassName,\r\n dropdownClassName,\r\n renderResult,\r\n}: SearchBarProps) {\r\n const [query, setQuery] = useState('');\r\n const [open, setOpen] = useState(false);\r\n const { results, loading, search, clear } = useSearch();\r\n const timer = useRef<ReturnType<typeof setTimeout>>();\r\n const wrap = useRef<HTMLDivElement>(null);\r\n\r\n useEffect(() => {\r\n clearTimeout(timer.current);\r\n if (!query.trim()) { clear(); setOpen(false); return; }\r\n timer.current = setTimeout(() => { search(query, limit); setOpen(true); }, debounceMs);\r\n return () => clearTimeout(timer.current);\r\n }, [query, search, clear, limit, debounceMs]);\r\n\r\n useEffect(() => {\r\n const handler = (e: MouseEvent) => {\r\n if (wrap.current && !wrap.current.contains(e.target as Node)) setOpen(false);\r\n };\r\n document.addEventListener('mousedown', handler);\r\n return () => document.removeEventListener('mousedown', handler);\r\n }, []);\r\n\r\n const handleSelect = (r: SearchResult) => {\r\n setOpen(false);\r\n setQuery(r.product.name);\r\n onSelect?.(r);\r\n };\r\n\r\n return (\r\n <>\r\n <style>{S}</style>\r\n <div className={`hsk-wrap ${className ?? ''}`} ref={wrap}>\r\n <input\r\n className={`hsk-input ${inputClassName ?? ''}`}\r\n type=\"text\"\r\n value={query}\r\n placeholder={placeholder}\r\n onChange={e => setQuery(e.target.value)}\r\n onFocus={() => results.length && setOpen(true)}\r\n />\r\n {open && (\r\n <div className={`hsk-drop ${dropdownClassName ?? ''}`}>\r\n {loading && <div className=\"hsk-msg\">Searching…</div>}\r\n {!loading && results.length === 0 && <div className=\"hsk-msg\">No results for \"{query}\"</div>}\r\n {results.map(r =>\r\n renderResult ? (\r\n <div key={r.id} onClick={() => handleSelect(r)}>{renderResult(r)}</div>\r\n ) : (\r\n <div key={r.id} className=\"hsk-item\" onClick={() => handleSelect(r)}>\r\n {r.product.images?.[0] && <img src={r.product.images[0]} alt={r.product.name} />}\r\n <div>\r\n <div className=\"hsk-item-name\">{r.product.name}</div>\r\n <div className=\"hsk-item-price\">{r.product.currency ?? 'KES'} {r.product.price}</div>\r\n </div>\r\n </div>\r\n )\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n </>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { useHuskelContext } from './HuskelProvider';\r\nimport { SearchResult } from '../types';\r\n\r\ninterface SparkleProps {\r\n productName: string;\r\n limit?: number;\r\n onResult?: (results: SearchResult[]) => void;\r\n className?: string;\r\n}\r\n\r\nconst S = `\r\n .hsk-sparkle{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;font-size:12px;font-weight:600;background:#f47c3c;color:#fff;border:none;border-radius:20px;cursor:pointer;transition:opacity .2s,transform .15s}\r\n .hsk-sparkle:hover{opacity:.88;transform:scale(1.04)}\r\n .hsk-sparkle:disabled{opacity:.5;cursor:not-allowed}\r\n`;\r\n\r\nexport function Sparkle({ productName, limit = 5, onResult, className }: SparkleProps) {\r\n const client = useHuskelContext();\r\n const [loading, setLoading] = useState(false);\r\n\r\n const handleClick = async () => {\r\n setLoading(true);\r\n try {\r\n const res = await client.api.search(productName, limit);\r\n onResult?.(res.results);\r\n } catch (e) {\r\n console.error('[Huskel Sparkle]', e);\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <>\r\n <style>{S}</style>\r\n <button className={`hsk-sparkle ${className ?? ''}`} onClick={handleClick} disabled={loading}>\r\n ✦ {loading ? 'Finding…' : 'Similar'}\r\n </button>\r\n </>\r\n );\r\n}\r\n"],"mappings":"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","names":["useRef","useRef","useRef","useRef","useCallback","useState","useState","useCallback","useEffect","useRef","useRef","useEffect","useState","useEffect","useRef","jsx","useState","useRef","useEffect","useState","Fragment","jsx","jsxs","S","useState"]}
1
+ {"version":3,"sources":["../src/api.ts","../src/client.ts","../src/hooks/useHuskel.ts","../src/hooks/useSearch.ts","../src/components/HuskelProvider.tsx","../src/hooks/useIngest.ts","../src/hooks/usePageIngest.ts","../src/components/SearchBar.tsx","../src/components/Sparkle.tsx"],"sourcesContent":["import { Product, SearchResponse, IngestResponse, HuskelError } from './types';\r\n\r\nconst MAX_RETRIES = 3;\r\nconst RETRY_DELAYS = [500, 1000, 2000]; // ms\r\n\r\nfunction log(level: 'info' | 'warn' | 'error', msg: string, data?: unknown) {\r\n const prefix = '[Huskel]';\r\n if (level === 'error') console.error(prefix, msg, data ?? '');\r\n else if (level === 'warn') console.warn(prefix, msg, data ?? '');\r\n else console.log(prefix, msg, data ?? '');\r\n}\r\n\r\nasync function sleep(ms: number) {\r\n return new Promise(r => setTimeout(r, ms));\r\n}\r\n\r\nexport class HuskelAPI {\r\n constructor(\r\n private apiUrl: string,\r\n private siteId: string,\r\n private apiToken: string,\r\n private getShopperId?: () => string | undefined,\r\n private getSessionId?: () => string | undefined\r\n ) {}\r\n\r\n private async post<T>(path: string, body: unknown, attempt = 0): Promise<T> {\r\n const url = `${this.apiUrl}${path}`;\r\n\r\n try {\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n 'X-Huskel-Token': this.apiToken,\r\n 'X-Huskel-Site': this.siteId,\r\n };\r\n\r\n const shopperId = this.getShopperId?.();\r\n if (shopperId) {\r\n headers['X-Huskel-Shopper-Id'] = shopperId;\r\n }\r\n\r\n const sessionId = this.getSessionId?.();\r\n if (sessionId) {\r\n headers['X-Huskel-Session-Id'] = sessionId;\r\n }\r\n\r\n const res = await fetch(url, {\r\n method: 'POST',\r\n headers,\r\n body: JSON.stringify(body),\r\n });\r\n\r\n if (!res.ok) {\r\n const text = await res.text();\r\n const err: HuskelError = { status: res.status, message: text };\r\n\r\n // Don't retry 4xx — developer errors\r\n if (res.status >= 400 && res.status < 500) {\r\n log('error', `${path} failed [${res.status}]`, text);\r\n throw err;\r\n }\r\n\r\n // Retry 5xx\r\n if (attempt < MAX_RETRIES - 1) {\r\n log('warn', `${path} [${res.status}] retrying (${attempt + 1}/${MAX_RETRIES})...`);\r\n await sleep(RETRY_DELAYS[attempt]);\r\n return this.post(path, body, attempt + 1);\r\n }\r\n\r\n log('error', `${path} failed after ${MAX_RETRIES} attempts`, err);\r\n throw err;\r\n }\r\n\r\n return res.json();\r\n } catch (e) {\r\n // Network error (offline, DNS, etc.)\r\n if ((e as HuskelError).status === undefined) {\r\n if (attempt < MAX_RETRIES - 1) {\r\n log('warn', `${path} network error, retrying (${attempt + 1}/${MAX_RETRIES})...`);\r\n await sleep(RETRY_DELAYS[attempt]);\r\n return this.post(path, body, attempt + 1);\r\n }\r\n log('error', `${path} unreachable after ${MAX_RETRIES} attempts`);\r\n }\r\n throw e;\r\n }\r\n }\r\n\r\n async ingest(product: Product): Promise<IngestResponse> {\r\n log('info', 'ingesting product', product.name);\r\n return this.post('/ingest', { siteId: this.siteId, product });\r\n }\r\n\r\n async ingestBatch(products: Product[]): Promise<IngestResponse> {\r\n log('info', `ingesting batch of ${products.length} products`);\r\n return this.post('/ingest/batch', { siteId: this.siteId, products });\r\n }\r\n\r\n async search(query: string, limit = 10): Promise<SearchResponse> {\r\n log('info', 'search query', query);\r\n return this.post('/search', { query, siteId: this.siteId, limit });\r\n }\r\n}\r\n","import { HuskelConfig, Product, RawProductInput } from './types';\r\nimport { HuskelAPI } from './api';\r\n\r\nfunction getEnvVar(key: string): string | undefined {\r\n if (typeof globalThis !== 'undefined') {\r\n const g = globalThis as any;\r\n if (g.process && g.process.env) {\r\n return g.process.env[key];\r\n }\r\n }\r\n return undefined;\r\n}\r\n\r\nfunction mapRawProduct(input: RawProductInput): Product | null {\r\n const name = input.name || input.title || input.productName || '';\r\n \r\n let price = '';\r\n let priceNumeric: number | undefined = undefined;\r\n\r\n if (input.price !== undefined) {\r\n if (typeof input.price === 'number') {\r\n priceNumeric = input.price;\r\n price = String(input.price);\r\n } else {\r\n price = input.price;\r\n const num = parseFloat(input.price.replace(/[^0-9.]/g, ''));\r\n priceNumeric = isNaN(num) ? undefined : num;\r\n }\r\n }\r\n if (input.priceNumeric !== undefined) {\r\n priceNumeric = input.priceNumeric;\r\n }\r\n\r\n let url = input.url || '';\r\n if (!url && typeof window !== 'undefined') {\r\n url = window.location.href;\r\n }\r\n\r\n let slug = input.slug || input.id || input.productId || '';\r\n if (!slug && url) {\r\n slug = url.split('/').filter(Boolean).pop() || '';\r\n }\r\n if (!slug && name) {\r\n slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');\r\n }\r\n\r\n let images: string[] = [];\r\n if (input.images) {\r\n images = input.images;\r\n } else if (input.image) {\r\n images = [input.image];\r\n } else if (input.thumbnail) {\r\n images = [input.thumbnail];\r\n }\r\n\r\n if (!name) {\r\n console.warn('[Huskel] Validation warning: Product name/title is missing. Skipping:', input);\r\n return null;\r\n }\r\n if (!price) {\r\n console.warn('[Huskel] Validation warning: Product price is missing. Skipping:', input);\r\n return null;\r\n }\r\n if (!url) {\r\n console.warn('[Huskel] Validation warning: Product URL is missing. Skipping:', input);\r\n return null;\r\n }\r\n\r\n return {\r\n name,\r\n price,\r\n url,\r\n brand: input.brand,\r\n description: input.description,\r\n originalPrice: input.originalPrice,\r\n discount: input.discount,\r\n currency: input.currency ?? 'KES',\r\n stock: input.stock,\r\n availability: input.availability,\r\n rating: input.rating,\r\n reviewCount: input.reviewCount,\r\n category: input.category,\r\n subCategory: input.subCategory,\r\n tags: input.tags,\r\n images: images.length > 0 ? images : undefined,\r\n specs: input.specs,\r\n priceNumeric,\r\n slug,\r\n };\r\n}\r\n\r\nfunction generateUUID(): string {\r\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\r\n return crypto.randomUUID();\r\n }\r\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\r\n const r = (Math.random() * 16) | 0;\r\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\r\n return v.toString(16);\r\n });\r\n}\r\n\r\nexport class HuskelClient {\r\n readonly api: HuskelAPI;\r\n private ingestQueue: Product[] = [];\r\n private ingestTimer: ReturnType<typeof setTimeout> | null = null;\r\n private ingestedUrls = new Set<string>();\r\n private onlineHandler: (() => void) | null = null;\r\n private shopperId?: string;\r\n private sessionId: string = '';\r\n\r\n constructor(config: HuskelConfig) {\r\n const siteId = config.siteId || getEnvVar('NEXT_PUBLIC_HUSKEL_SITE_ID') || '';\r\n const apiUrl = config.apiUrl || getEnvVar('NEXT_PUBLIC_HUSKEL_API_URL') || '';\r\n const apiToken = config.apiToken || getEnvVar('NEXT_PUBLIC_HUSKEL_API_TOKEN') || '';\r\n\r\n // Runtime validation — fail loudly so misconfiguration is never silent\r\n if (!siteId) console.error('[Huskel] Missing siteId. Set it via <HuskelProvider siteId=\"...\"> or NEXT_PUBLIC_HUSKEL_SITE_ID.');\r\n if (!apiUrl) console.error('[Huskel] Missing apiUrl. Set it via <HuskelProvider apiUrl=\"...\"> or NEXT_PUBLIC_HUSKEL_API_URL.');\r\n if (!apiToken) console.error('[Huskel] Missing apiToken. Set it via <HuskelProvider apiToken=\"...\"> or NEXT_PUBLIC_HUSKEL_API_TOKEN.');\r\n\r\n this.shopperId = config.shopperId;\r\n this.initSession();\r\n\r\n this.api = new HuskelAPI(\r\n apiUrl,\r\n siteId,\r\n apiToken,\r\n () => this.shopperId,\r\n () => this.sessionId\r\n );\r\n instance = this;\r\n\r\n if (typeof window !== 'undefined') {\r\n this.onlineHandler = () => {\r\n console.log('[Huskel] Connectivity restored, flushing queued ingestions.');\r\n this.flushQueue();\r\n };\r\n window.addEventListener('online', this.onlineHandler);\r\n }\r\n }\r\n\r\n setShopperId(id: string | undefined) {\r\n this.shopperId = id;\r\n }\r\n\r\n getShopperId(): string | undefined {\r\n return this.shopperId;\r\n }\r\n\r\n getSessionId(): string {\r\n return this.sessionId;\r\n }\r\n\r\n private initSession() {\r\n if (typeof window !== 'undefined' && window.sessionStorage) {\r\n try {\r\n let sid = window.sessionStorage.getItem('huskel_session_id');\r\n if (!sid) {\r\n sid = generateUUID();\r\n window.sessionStorage.setItem('huskel_session_id', sid);\r\n }\r\n this.sessionId = sid;\r\n return;\r\n } catch (e) {\r\n // Fallback if sessionStorage is disabled or private mode\r\n }\r\n }\r\n this.sessionId = generateUUID();\r\n }\r\n\r\n destroy() {\r\n if (typeof window !== 'undefined' && this.onlineHandler) {\r\n window.removeEventListener('online', this.onlineHandler);\r\n this.onlineHandler = null;\r\n }\r\n if (this.ingestTimer) {\r\n clearTimeout(this.ingestTimer);\r\n this.ingestTimer = null;\r\n }\r\n if (instance === this) instance = null;\r\n }\r\n\r\n async queueIngest(rawProduct: RawProductInput): Promise<void> {\r\n const product = mapRawProduct(rawProduct);\r\n if (!product) return;\r\n\r\n if (this.ingestedUrls.has(product.url)) {\r\n return;\r\n }\r\n this.ingestedUrls.add(product.url);\r\n\r\n this.ingestQueue.push(product);\r\n this.scheduleFlush();\r\n }\r\n\r\n async queueIngestBatch(rawProducts: RawProductInput[]): Promise<void> {\r\n rawProducts.forEach(p => {\r\n const product = mapRawProduct(p);\r\n if (!product) return;\r\n\r\n if (this.ingestedUrls.has(product.url)) {\r\n return;\r\n }\r\n this.ingestedUrls.add(product.url);\r\n this.ingestQueue.push(product);\r\n });\r\n\r\n if (this.ingestQueue.length > 0) {\r\n this.scheduleFlush();\r\n }\r\n }\r\n\r\n private scheduleFlush() {\r\n if (this.ingestTimer) return;\r\n this.ingestTimer = setTimeout(() => {\r\n this.flushQueue();\r\n }, 300);\r\n }\r\n\r\n private async flushQueue() {\r\n this.ingestTimer = null;\r\n if (this.ingestQueue.length === 0) return;\r\n\r\n if (typeof navigator !== 'undefined' && !navigator.onLine) {\r\n console.warn('[Huskel] Browser offline. Postponing ingestion.');\r\n return;\r\n }\r\n\r\n const batch = [...this.ingestQueue];\r\n this.ingestQueue = [];\r\n\r\n try {\r\n await this.api.ingestBatch(batch);\r\n } catch (e: any) {\r\n if (e.status && e.status >= 400 && e.status < 500) {\r\n console.error('[Huskel] Ingestion discarded due to client error:', e.message);\r\n return;\r\n }\r\n\r\n // Re-queue and schedule another flush so items are not stuck forever\r\n console.warn('[Huskel] Ingestion failed. Re-queuing to retry.', e);\r\n this.ingestQueue = [...batch, ...this.ingestQueue];\r\n this.scheduleFlush();\r\n }\r\n }\r\n}\r\n\r\nlet instance: HuskelClient | null = null;\r\n\r\nexport function initHuskel(config: HuskelConfig): HuskelClient {\r\n instance = new HuskelClient(config);\r\n return instance;\r\n}\r\n\r\nexport function getHuskelClient(): HuskelClient {\r\n if (!instance) {\r\n const siteId = getEnvVar('NEXT_PUBLIC_HUSKEL_SITE_ID');\r\n const apiUrl = getEnvVar('NEXT_PUBLIC_HUSKEL_API_URL');\r\n const apiToken = getEnvVar('NEXT_PUBLIC_HUSKEL_API_TOKEN');\r\n\r\n if (siteId && apiUrl && apiToken) {\r\n instance = new HuskelClient({ siteId, apiUrl, apiToken });\r\n } else {\r\n throw new Error('[Huskel] Call initHuskel() or set NEXT_PUBLIC_HUSKEL_* environment variables before using the client.');\r\n }\r\n }\r\n return instance;\r\n}\r\n","import { useRef } from 'react';\r\nimport { HuskelConfig } from '../types';\r\nimport { HuskelClient, initHuskel } from '../client';\r\n\r\n/**\r\n * @deprecated Use <HuskelProvider> instead to avoid SSR issues.\r\n */\r\nexport function useHuskel(config: HuskelConfig): HuskelClient {\r\n const clientRef = useRef<HuskelClient | null>(null);\r\n\r\n if (!clientRef.current) {\r\n console.warn('[Huskel] useHuskel() is deprecated. Please wrap your application in <HuskelProvider> instead.');\r\n clientRef.current = initHuskel(config);\r\n }\r\n\r\n return clientRef.current;\r\n}\r\n","import { useState, useCallback, useRef } from 'react';\r\nimport { SearchResult } from '../types';\r\nimport { useHuskelContext } from '../components/HuskelProvider';\r\n\r\ninterface UseSearchReturn {\r\n results: SearchResult[];\r\n loading: boolean;\r\n error: string | null;\r\n search: (query: string, limit?: number) => Promise<void>;\r\n clear: () => void;\r\n}\r\n\r\nexport function useSearch(): UseSearchReturn {\r\n const client = useHuskelContext();\r\n const [results, setResults] = useState<SearchResult[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const abortRef = useRef<AbortController | null>(null);\r\n\r\n const search = useCallback(async (query: string, limit = 10) => {\r\n if (!query.trim()) { setResults([]); return; }\r\n abortRef.current?.abort();\r\n abortRef.current = new AbortController();\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n const res = await client.api.search(query, limit);\r\n setResults(res.results ?? []);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Search failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n const clear = useCallback(() => { setResults([]); setError(null); }, []);\r\n\r\n return { results, loading, error, search, clear };\r\n}\r\n","'use client';\r\n\r\nimport React, { createContext, useContext, useEffect, useRef } from 'react';\r\nimport { HuskelClient, getHuskelClient } from '../client';\r\nimport { HuskelConfig } from '../types';\r\n\r\nexport const HuskelContext = createContext<HuskelClient | null>(null);\r\n\r\ninterface HuskelProviderProps extends HuskelConfig {\r\n children: React.ReactNode;\r\n}\r\n\r\nexport function HuskelProvider({ siteId, apiUrl, apiToken, shopperId, children }: HuskelProviderProps) {\r\n const clientRef = useRef<HuskelClient | null>(null);\r\n\r\n if (!clientRef.current) {\r\n clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken, shopperId });\r\n }\r\n\r\n // Update shopperId dynamically when it changes (e.g., shopper logs in/out)\r\n useEffect(() => {\r\n clientRef.current?.setShopperId(shopperId);\r\n }, [shopperId]);\r\n\r\n // Clean up the online listener and timers when the provider unmounts\r\n // (prevents leaks during hot module reload and React StrictMode double-mount)\r\n useEffect(() => {\r\n return () => {\r\n clientRef.current?.destroy();\r\n };\r\n }, []);\r\n\r\n return (\r\n <HuskelContext.Provider value={clientRef.current}>\r\n {children}\r\n </HuskelContext.Provider>\r\n );\r\n}\r\n\r\nexport function useHuskelContext(): HuskelClient {\r\n const context = useContext(HuskelContext);\r\n if (!context) {\r\n return getHuskelClient();\r\n }\r\n return context;\r\n}\r\n\r\n","import { useCallback, useState } from 'react';\r\nimport { RawProductInput } from '../types';\r\nimport { useHuskelContext } from '../components/HuskelProvider';\r\n\r\ninterface UseIngestReturn {\r\n ingest: (product: RawProductInput) => Promise<void>;\r\n ingestBatch: (products: RawProductInput[]) => Promise<void>;\r\n loading: boolean;\r\n error: string | null;\r\n}\r\n\r\nexport function useIngest(): UseIngestReturn {\r\n const client = useHuskelContext();\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const ingest = useCallback(async (product: RawProductInput) => {\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n await client.queueIngest(product);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Ingest failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n const ingestBatch = useCallback(async (products: RawProductInput[]) => {\r\n if (!products.length) return;\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n await client.queueIngestBatch(products);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Batch ingest failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n return { ingest, ingestBatch, loading, error };\r\n}\r\n","import { useEffect, useRef } from 'react';\nimport { RawProductInput } from '../types';\nimport { getHuskelClient } from '../client';\n\n/**\n * usePageIngest — drop this into any product page component.\n * The moment a customer's browser renders the page, the product is\n * automatically captured and queued for ingestion into the vector index.\n *\n * No configuration needed beyond <HuskelProvider> in your layout.\n *\n * @example\n * // Product detail page — Next.js or React\n * export function ProductPage({ product }) {\n * usePageIngest({\n * name: product.title,\n * price: product.price,\n * url: window.location.href,\n * images: [product.thumbnail],\n * category: product.category,\n * });\n * return <div>...</div>;\n * }\n */\nexport function usePageIngest(product: RawProductInput | null | undefined): void {\n // Use url as the stable key — avoids re-ingesting on unrelated re-renders\n const ingestedRef = useRef<string | null>(null);\n\n useEffect(() => {\n if (!product) return;\n\n // Resolve URL — falls back to window.location if not provided\n const url =\n product.url ||\n (typeof window !== 'undefined' ? window.location.href : '');\n\n // Guard: only ingest once per URL per component lifecycle\n if (ingestedRef.current === url) return;\n ingestedRef.current = url;\n\n try {\n getHuskelClient().queueIngest({ ...product, url });\n } catch {\n // Client not initialised — silently skip\n }\n }, [product?.url ?? product?.name]);\n}\n","import React, { useState, useEffect, useRef } from 'react';\r\nimport { useSearch } from '../hooks/useSearch';\r\nimport { SearchResult } from '../types';\r\n\r\ninterface SearchBarProps {\r\n placeholder?: string;\r\n limit?: number;\r\n debounceMs?: number;\r\n onSelect?: (result: SearchResult) => void;\r\n className?: string;\r\n inputClassName?: string;\r\n dropdownClassName?: string;\r\n renderResult?: (result: SearchResult) => React.ReactNode;\r\n}\r\n\r\nconst S = `\r\n .hsk-wrap{position:relative;width:100%;font-family:inherit}\r\n .hsk-input{width:100%;padding:10px 16px;font-size:15px;border:1.5px solid #e2e2e2;border-radius:8px;outline:none;box-sizing:border-box;background:#fff;transition:border-color .2s}\r\n .hsk-input:focus{border-color:#f47c3c}\r\n .hsk-drop{position:absolute;top:calc(100% + 6px);left:0;right:0;background:#fff;border:1px solid #e2e2e2;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.1);z-index:9999;max-height:360px;overflow-y:auto}\r\n .hsk-item{display:flex;align-items:center;gap:12px;padding:10px 14px;cursor:pointer;transition:background .15s}\r\n .hsk-item:hover{background:#faf5f1}\r\n .hsk-item img{width:40px;height:40px;object-fit:cover;border-radius:4px}\r\n .hsk-item-name{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\r\n .hsk-item-price{font-size:13px;color:#f47c3c;margin-top:2px}\r\n .hsk-msg{padding:16px;text-align:center;font-size:14px;color:#888}\r\n`;\r\n\r\nexport function SearchBar({\r\n placeholder = 'Search for what you want — how you want',\r\n limit = 10,\r\n debounceMs = 300,\r\n onSelect,\r\n className,\r\n inputClassName,\r\n dropdownClassName,\r\n renderResult,\r\n}: SearchBarProps) {\r\n const [query, setQuery] = useState('');\r\n const [open, setOpen] = useState(false);\r\n const { results, loading, search, clear } = useSearch();\r\n const timer = useRef<ReturnType<typeof setTimeout>>();\r\n const wrap = useRef<HTMLDivElement>(null);\r\n\r\n useEffect(() => {\r\n clearTimeout(timer.current);\r\n if (!query.trim()) { clear(); setOpen(false); return; }\r\n timer.current = setTimeout(() => { search(query, limit); setOpen(true); }, debounceMs);\r\n return () => clearTimeout(timer.current);\r\n }, [query, search, clear, limit, debounceMs]);\r\n\r\n useEffect(() => {\r\n const handler = (e: MouseEvent) => {\r\n if (wrap.current && !wrap.current.contains(e.target as Node)) setOpen(false);\r\n };\r\n document.addEventListener('mousedown', handler);\r\n return () => document.removeEventListener('mousedown', handler);\r\n }, []);\r\n\r\n const handleSelect = (r: SearchResult) => {\r\n setOpen(false);\r\n setQuery(r.product.name);\r\n onSelect?.(r);\r\n };\r\n\r\n return (\r\n <>\r\n <style>{S}</style>\r\n <div className={`hsk-wrap ${className ?? ''}`} ref={wrap}>\r\n <input\r\n className={`hsk-input ${inputClassName ?? ''}`}\r\n type=\"text\"\r\n value={query}\r\n placeholder={placeholder}\r\n onChange={e => setQuery(e.target.value)}\r\n onFocus={() => results.length && setOpen(true)}\r\n />\r\n {open && (\r\n <div className={`hsk-drop ${dropdownClassName ?? ''}`}>\r\n {loading && <div className=\"hsk-msg\">Searching…</div>}\r\n {!loading && results.length === 0 && <div className=\"hsk-msg\">No results for \"{query}\"</div>}\r\n {results.map(r =>\r\n renderResult ? (\r\n <div key={r.id} onClick={() => handleSelect(r)}>{renderResult(r)}</div>\r\n ) : (\r\n <div key={r.id} className=\"hsk-item\" onClick={() => handleSelect(r)}>\r\n {r.product.images?.[0] && <img src={r.product.images[0]} alt={r.product.name} />}\r\n <div>\r\n <div className=\"hsk-item-name\">{r.product.name}</div>\r\n <div className=\"hsk-item-price\">{r.product.currency ?? 'KES'} {r.product.price}</div>\r\n </div>\r\n </div>\r\n )\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n </>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { useHuskelContext } from './HuskelProvider';\r\nimport { SearchResult } from '../types';\r\n\r\ninterface SparkleProps {\r\n productName: string;\r\n limit?: number;\r\n onResult?: (results: SearchResult[]) => void;\r\n className?: string;\r\n}\r\n\r\nconst S = `\r\n .hsk-sparkle{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;font-size:12px;font-weight:600;background:#f47c3c;color:#fff;border:none;border-radius:20px;cursor:pointer;transition:opacity .2s,transform .15s}\r\n .hsk-sparkle:hover{opacity:.88;transform:scale(1.04)}\r\n .hsk-sparkle:disabled{opacity:.5;cursor:not-allowed}\r\n`;\r\n\r\nexport function Sparkle({ productName, limit = 5, onResult, className }: SparkleProps) {\r\n const client = useHuskelContext();\r\n const [loading, setLoading] = useState(false);\r\n\r\n const handleClick = async () => {\r\n setLoading(true);\r\n try {\r\n const res = await client.api.search(productName, limit);\r\n onResult?.(res.results);\r\n } catch (e) {\r\n console.error('[Huskel Sparkle]', e);\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <>\r\n <style>{S}</style>\r\n <button className={`hsk-sparkle ${className ?? ''}`} onClick={handleClick} disabled={loading}>\r\n ✦ {loading ? 'Finding…' : 'Similar'}\r\n </button>\r\n </>\r\n );\r\n}\r\n"],"mappings":"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","names":["useRef","useRef","useRef","useRef","useCallback","useState","useState","useCallback","useEffect","useRef","useRef","useEffect","useState","useEffect","useRef","jsx","useState","useRef","useEffect","useState","Fragment","jsx","jsxs","S","useState"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huskel/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Huskel AI-powered search SDK for SPAs",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",