@huskel/sdk 0.2.2 → 0.3.0

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
@@ -1,121 +1,228 @@
1
- # @huskel/sdk
2
-
3
- AI-powered vector search SDK. You own your data pass it in, we handle the rest.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm install @huskel/sdk
9
- ```
10
-
11
- ## Setup
12
-
13
- Wrap your application in the `<HuskelProvider>` (it uses `"use client"` internally, allowing your root layout to remain a Next.js Server Component):
14
-
15
- ```tsx
16
- // app/layout.tsx (Next.js Root Layout - Server Component)
17
- import { HuskelProvider } from '@huskel/sdk';
18
-
19
- export default function RootLayout({ children }) {
20
- return (
21
- <html>
22
- <body>
23
- {/* siteId, apiUrl, and apiToken are read automatically from NEXT_PUBLIC_HUSKEL_* env variables */}
24
- <HuskelProvider>
25
- {children}
26
- </HuskelProvider>
27
- </body>
28
- </html>
29
- );
30
- }
31
- ```
32
-
33
- *Or pass configuration explicitly if you are not using environment variables:*
34
-
35
- ```tsx
36
- <HuskelProvider
37
- siteId="your-site-id"
38
- apiUrl="https://your-huskel-backend.com"
39
- apiToken="your-api-token"
40
- >
41
- {children}
42
- </HuskelProvider>
43
- ```
44
-
45
- ## Ingest products (forgiving schema mapping)
46
-
47
- Pass your raw database or CMS objects directly. The SDK automatically validates, dedupes, batches, and resolves common field naming variations (e.g. `title`/`name`, `thumbnail`/`image`/`images`, `slug`/`id`/`productId`):
48
-
49
- ```tsx
50
- import { useEffect } from 'react';
51
- import { useIngest } from '@huskel/sdk';
52
-
53
- // Single product page
54
- export function ProductPage({ product }) {
55
- const { ingest } = useIngest();
56
-
57
- useEffect(() => {
58
- // Passes raw product object directly.
59
- // Handles background batching, client-side deduplication, and offline recovery automatically.
60
- ingest(product);
61
- }, [product.id, ingest]);
62
- }
63
-
64
- // Listing / category page
65
- export function ProductGrid({ products }) {
66
- const { ingestBatch } = useIngest();
67
-
68
- useEffect(() => {
69
- // Ingest array of products in a single debounced batch
70
- ingestBatch(products);
71
- }, [products, ingestBatch]);
72
- }
73
- ```
74
-
75
- ## Search
76
-
77
- ### SearchBar Dropdown Component
78
-
79
- ```tsx
80
- import { SearchBar } from '@huskel/sdk';
81
-
82
- export function Header() {
83
- return (
84
- <SearchBar
85
- onSelect={(result) => router.push(result.product.url)}
86
- />
87
- );
88
- }
89
- ```
90
-
91
- ### Headless Search Hook
92
-
93
- ```tsx
94
- import { useSearch } from '@huskel/sdk';
95
-
96
- export function CustomSearch() {
97
- const { results, loading, search } = useSearch();
98
-
99
- return (
100
- <div>
101
- <input onChange={e => search(e.target.value)} />
102
- <ul>
103
- {results.map(r => (
104
- <li key={r.id}>{r.product.name}</li>
105
- ))}
106
- </ul>
107
- </div>
108
- );
109
- }
110
- ```
111
-
112
- ## Sparkle (similar products)
113
-
114
- ```tsx
115
- import { Sparkle } from '@huskel/sdk';
116
-
117
- <Sparkle
118
- productName={product.name}
119
- onResult={(similar) => setSimilar(similar)}
120
- />
121
- ```
1
+ # @huskel/sdk
2
+
3
+ AI-powered vector search for any storefront. Your customers browse products index automatically. Zero scraping, zero manual uploads.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @huskel/sdk
9
+ # or
10
+ pnpm add @huskel/sdk
11
+ ```
12
+
13
+ ---
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
+
47
+ ## Next.js (App Router)
48
+
49
+ Next.js App Router uses **Server Components** by default. The SDK is client-only, so follow this pattern:
50
+
51
+ ### 1. Create a client provider wrapper
52
+
53
+ ```tsx
54
+ // app/components/HuskelClientProvider.tsx
55
+ 'use client';
56
+
57
+ import { HuskelProvider } from '@huskel/sdk';
58
+
59
+ export default function HuskelClientProvider({
60
+ children,
61
+ }: {
62
+ children: React.ReactNode;
63
+ }) {
64
+ return <HuskelProvider>{children}</HuskelProvider>;
65
+ }
66
+ ```
67
+
68
+ ### 2. Add it to your root layout
69
+
70
+ ```tsx
71
+ // app/layout.tsx this is a Server Component, no 'use client' needed
72
+ import HuskelClientProvider from './components/HuskelClientProvider';
73
+
74
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
75
+ return (
76
+ <html lang="en">
77
+ <body>
78
+ <HuskelClientProvider>
79
+ {children}
80
+ </HuskelClientProvider>
81
+ </body>
82
+ </html>
83
+ );
84
+ }
85
+ ```
86
+
87
+ ### 3. Auto-ingest on product pages
88
+
89
+ ```tsx
90
+ // app/products/[slug]/page.tsx
91
+ import { getProduct } from '@/lib/db'; // your own data fetching
92
+
93
+ // ProductView is a Client Component — handles ingestion
94
+ 'use client';
95
+ import { usePageIngest } from '@huskel/sdk';
96
+
97
+ export function ProductView({ product }) {
98
+ // One line — fires automatically when the customer's browser loads the page
99
+ usePageIngest({
100
+ name: product.title,
101
+ price: product.price,
102
+ url: window.location.href,
103
+ images: [product.thumbnail],
104
+ category: product.category,
105
+ description: product.description,
106
+ });
107
+
108
+ return <div>{/* your product UI */}</div>;
109
+ }
110
+ ```
111
+
112
+ > **Why a separate client component?**
113
+ > `usePageIngest` uses `useEffect` which runs only in the browser. Server Components can't call hooks. The wrapper pattern keeps your data-fetching in Server Components (fast, cached) while the SDK fires client-side.
114
+
115
+ ### 4. Add the search bar
116
+
117
+ ```tsx
118
+ // app/components/Header.tsx
119
+ 'use client';
120
+ import { SearchBar } from '@huskel/sdk';
121
+ import { useRouter } from 'next/navigation';
122
+
123
+ export function Header() {
124
+ const router = useRouter();
125
+ return (
126
+ <SearchBar
127
+ placeholder="Search products..."
128
+ onSelect={(result) => router.push(result.product.url)}
129
+ />
130
+ );
131
+ }
132
+ ```
133
+
134
+ ---
135
+
136
+ ## React (CRA / Vite)
137
+
138
+ With a standard SPA, everything is already a client component. Much simpler:
139
+
140
+ ### 1. Wrap your app
141
+
142
+ ```tsx
143
+ // src/main.tsx or src/App.tsx
144
+ import { HuskelProvider } from '@huskel/sdk';
145
+
146
+ function App() {
147
+ return (
148
+ <HuskelProvider>
149
+ <Router>
150
+ <Routes />
151
+ </Router>
152
+ </HuskelProvider>
153
+ );
154
+ }
155
+ ```
156
+
157
+ ### 2. Ingest on product pages
158
+
159
+ ```tsx
160
+ // src/pages/ProductPage.tsx
161
+ import { usePageIngest } from '@huskel/sdk';
162
+
163
+ export function ProductPage({ product }) {
164
+ usePageIngest({
165
+ name: product.title,
166
+ price: product.price,
167
+ url: window.location.href,
168
+ images: [product.thumbnail],
169
+ category: product.category,
170
+ });
171
+
172
+ return <div>{/* your product UI */}</div>;
173
+ }
174
+ ```
175
+
176
+ ### 3. Add search
177
+
178
+ ```tsx
179
+ import { SearchBar } from '@huskel/sdk';
180
+
181
+ <SearchBar onSelect={(result) => navigate(result.product.url)} />
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Batch ingest (listing pages)
187
+
188
+ When rendering a grid of products, ingest them all at once:
189
+
190
+ ```tsx
191
+ 'use client';
192
+ import { useIngest } from '@huskel/sdk';
193
+ import { useEffect } from 'react';
194
+
195
+ export function ProductGrid({ products }) {
196
+ const { ingestBatch } = useIngest();
197
+
198
+ useEffect(() => {
199
+ ingestBatch(
200
+ products.map((p) => ({
201
+ name: p.title,
202
+ price: p.price,
203
+ url: `/products/${p.slug}`,
204
+ images: [p.thumbnail],
205
+ category: p.category,
206
+ currency: 'KES',
207
+ }))
208
+ );
209
+ }, [products]);
210
+
211
+ return <ul>{/* render cards */}</ul>;
212
+ }
213
+ ```
214
+
215
+ ---
216
+
217
+ ## API Reference
218
+
219
+ | Export | Type | Description |
220
+ |--------|------|-------------|
221
+ | `HuskelProvider` | Component | Wraps your app. Reads env vars automatically. |
222
+ | `usePageIngest(product)` | Hook | Ingest one product. Call on any product detail page. |
223
+ | `useIngest()` | Hook | Returns `{ ingest, ingestBatch }` for manual control. |
224
+ | `useSearch()` | Hook | Returns `{ search, results, loading }` for headless search. |
225
+ | `SearchBar` | Component | Plug-and-play autocomplete search UI. |
226
+ | `Sparkle` | Component | "Similar products" button powered by vector similarity. |
227
+ | `getHuskelClient()` | Function | Get the singleton client instance imperatively. |
228
+ | `initHuskel(config)` | Function | Initialize manually (non-React environments). |
package/dist/index.d.mts CHANGED
@@ -127,6 +127,28 @@ interface UseIngestReturn {
127
127
  }
128
128
  declare function useIngest(): UseIngestReturn;
129
129
 
130
+ /**
131
+ * usePageIngest — drop this into any product page component.
132
+ * The moment a customer's browser renders the page, the product is
133
+ * automatically captured and queued for ingestion into the vector index.
134
+ *
135
+ * No configuration needed beyond <HuskelProvider> in your layout.
136
+ *
137
+ * @example
138
+ * // Product detail page — Next.js or React
139
+ * export function ProductPage({ product }) {
140
+ * usePageIngest({
141
+ * name: product.title,
142
+ * price: product.price,
143
+ * url: window.location.href,
144
+ * images: [product.thumbnail],
145
+ * category: product.category,
146
+ * });
147
+ * return <div>...</div>;
148
+ * }
149
+ */
150
+ declare function usePageIngest(product: RawProductInput | null | undefined): void;
151
+
130
152
  interface SearchBarProps {
131
153
  placeholder?: string;
132
154
  limit?: number;
@@ -152,4 +174,4 @@ interface HuskelProviderProps extends HuskelConfig {
152
174
  }
153
175
  declare function HuskelProvider({ siteId, apiUrl, apiToken, children }: HuskelProviderProps): react_jsx_runtime.JSX.Element;
154
176
 
155
- 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, useSearch };
177
+ 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
@@ -127,6 +127,28 @@ interface UseIngestReturn {
127
127
  }
128
128
  declare function useIngest(): UseIngestReturn;
129
129
 
130
+ /**
131
+ * usePageIngest — drop this into any product page component.
132
+ * The moment a customer's browser renders the page, the product is
133
+ * automatically captured and queued for ingestion into the vector index.
134
+ *
135
+ * No configuration needed beyond <HuskelProvider> in your layout.
136
+ *
137
+ * @example
138
+ * // Product detail page — Next.js or React
139
+ * export function ProductPage({ product }) {
140
+ * usePageIngest({
141
+ * name: product.title,
142
+ * price: product.price,
143
+ * url: window.location.href,
144
+ * images: [product.thumbnail],
145
+ * category: product.category,
146
+ * });
147
+ * return <div>...</div>;
148
+ * }
149
+ */
150
+ declare function usePageIngest(product: RawProductInput | null | undefined): void;
151
+
130
152
  interface SearchBarProps {
131
153
  placeholder?: string;
132
154
  limit?: number;
@@ -152,4 +174,4 @@ interface HuskelProviderProps extends HuskelConfig {
152
174
  }
153
175
  declare function HuskelProvider({ siteId, apiUrl, apiToken, children }: HuskelProviderProps): react_jsx_runtime.JSX.Element;
154
176
 
155
- 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, useSearch };
177
+ 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
@@ -1,8 +1,26 @@
1
+ 'use client';
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
3
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
9
  var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
11
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
12
+ var __spreadValues = (a, b) => {
13
+ for (var prop in b || (b = {}))
14
+ if (__hasOwnProp.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ if (__getOwnPropSymbols)
17
+ for (var prop of __getOwnPropSymbols(b)) {
18
+ if (__propIsEnum.call(b, prop))
19
+ __defNormalProp(a, prop, b[prop]);
20
+ }
21
+ return a;
22
+ };
23
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
6
24
  var __export = (target, all) => {
7
25
  for (var name in all)
8
26
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -29,6 +47,7 @@ __export(index_exports, {
29
47
  initHuskel: () => initHuskel,
30
48
  useHuskel: () => useHuskel,
31
49
  useIngest: () => useIngest,
50
+ usePageIngest: () => usePageIngest,
32
51
  useSearch: () => useSearch
33
52
  });
34
53
  module.exports = __toCommonJS(index_exports);
@@ -396,8 +415,25 @@ function useIngest() {
396
415
  return { ingest, ingestBatch, loading, error };
397
416
  }
398
417
 
399
- // src/components/SearchBar.tsx
418
+ // src/hooks/usePageIngest.ts
400
419
  var import_react5 = require("react");
420
+ function usePageIngest(product) {
421
+ var _a;
422
+ const ingestedRef = (0, import_react5.useRef)(null);
423
+ (0, import_react5.useEffect)(() => {
424
+ if (!product) return;
425
+ const url = product.url || (typeof window !== "undefined" ? window.location.href : "");
426
+ if (ingestedRef.current === url) return;
427
+ ingestedRef.current = url;
428
+ try {
429
+ getHuskelClient().queueIngest(__spreadProps(__spreadValues({}, product), { url }));
430
+ } catch (e) {
431
+ }
432
+ }, [(_a = product == null ? void 0 : product.url) != null ? _a : product == null ? void 0 : product.name]);
433
+ }
434
+
435
+ // src/components/SearchBar.tsx
436
+ var import_react6 = require("react");
401
437
  var import_jsx_runtime2 = require("react/jsx-runtime");
402
438
  var S = `
403
439
  .hsk-wrap{position:relative;width:100%;font-family:inherit}
@@ -421,12 +457,12 @@ function SearchBar({
421
457
  dropdownClassName,
422
458
  renderResult
423
459
  }) {
424
- const [query, setQuery] = (0, import_react5.useState)("");
425
- const [open, setOpen] = (0, import_react5.useState)(false);
460
+ const [query, setQuery] = (0, import_react6.useState)("");
461
+ const [open, setOpen] = (0, import_react6.useState)(false);
426
462
  const { results, loading, search, clear } = useSearch();
427
- const timer = (0, import_react5.useRef)();
428
- const wrap = (0, import_react5.useRef)(null);
429
- (0, import_react5.useEffect)(() => {
463
+ const timer = (0, import_react6.useRef)();
464
+ const wrap = (0, import_react6.useRef)(null);
465
+ (0, import_react6.useEffect)(() => {
430
466
  clearTimeout(timer.current);
431
467
  if (!query.trim()) {
432
468
  clear();
@@ -439,7 +475,7 @@ function SearchBar({
439
475
  }, debounceMs);
440
476
  return () => clearTimeout(timer.current);
441
477
  }, [query, search, clear, limit, debounceMs]);
442
- (0, import_react5.useEffect)(() => {
478
+ (0, import_react6.useEffect)(() => {
443
479
  const handler = (e) => {
444
480
  if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
445
481
  };
@@ -494,7 +530,7 @@ function SearchBar({
494
530
  }
495
531
 
496
532
  // src/components/Sparkle.tsx
497
- var import_react6 = require("react");
533
+ var import_react7 = require("react");
498
534
  var import_jsx_runtime3 = require("react/jsx-runtime");
499
535
  var S2 = `
500
536
  .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}
@@ -503,7 +539,7 @@ var S2 = `
503
539
  `;
504
540
  function Sparkle({ productName, limit = 5, onResult, className }) {
505
541
  const client = useHuskelContext();
506
- const [loading, setLoading] = (0, import_react6.useState)(false);
542
+ const [loading, setLoading] = (0, import_react7.useState)(false);
507
543
  const handleClick = async () => {
508
544
  setLoading(true);
509
545
  try {
@@ -534,5 +570,7 @@ function Sparkle({ productName, limit = 5, onResult, className }) {
534
570
  initHuskel,
535
571
  useHuskel,
536
572
  useIngest,
573
+ usePageIngest,
537
574
  useSearch
538
575
  });
576
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
package/dist/index.mjs CHANGED
@@ -1,3 +1,24 @@
1
+ 'use client';
2
+ var __defProp = Object.defineProperty;
3
+ var __defProps = Object.defineProperties;
4
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
20
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
+
1
22
  // src/api.ts
2
23
  var MAX_RETRIES = 3;
3
24
  var RETRY_DELAYS = [500, 1e3, 2e3];
@@ -361,8 +382,25 @@ function useIngest() {
361
382
  return { ingest, ingestBatch, loading, error };
362
383
  }
363
384
 
385
+ // src/hooks/usePageIngest.ts
386
+ import { useEffect as useEffect2, useRef as useRef4 } from "react";
387
+ function usePageIngest(product) {
388
+ var _a;
389
+ const ingestedRef = useRef4(null);
390
+ useEffect2(() => {
391
+ if (!product) return;
392
+ const url = product.url || (typeof window !== "undefined" ? window.location.href : "");
393
+ if (ingestedRef.current === url) return;
394
+ ingestedRef.current = url;
395
+ try {
396
+ getHuskelClient().queueIngest(__spreadProps(__spreadValues({}, product), { url }));
397
+ } catch (e) {
398
+ }
399
+ }, [(_a = product == null ? void 0 : product.url) != null ? _a : product == null ? void 0 : product.name]);
400
+ }
401
+
364
402
  // src/components/SearchBar.tsx
365
- import { useState as useState3, useEffect as useEffect2, useRef as useRef4 } from "react";
403
+ import { useState as useState3, useEffect as useEffect3, useRef as useRef5 } from "react";
366
404
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
367
405
  var S = `
368
406
  .hsk-wrap{position:relative;width:100%;font-family:inherit}
@@ -389,9 +427,9 @@ function SearchBar({
389
427
  const [query, setQuery] = useState3("");
390
428
  const [open, setOpen] = useState3(false);
391
429
  const { results, loading, search, clear } = useSearch();
392
- const timer = useRef4();
393
- const wrap = useRef4(null);
394
- useEffect2(() => {
430
+ const timer = useRef5();
431
+ const wrap = useRef5(null);
432
+ useEffect3(() => {
395
433
  clearTimeout(timer.current);
396
434
  if (!query.trim()) {
397
435
  clear();
@@ -404,7 +442,7 @@ function SearchBar({
404
442
  }, debounceMs);
405
443
  return () => clearTimeout(timer.current);
406
444
  }, [query, search, clear, limit, debounceMs]);
407
- useEffect2(() => {
445
+ useEffect3(() => {
408
446
  const handler = (e) => {
409
447
  if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
410
448
  };
@@ -498,5 +536,7 @@ export {
498
536
  initHuskel,
499
537
  useHuskel,
500
538
  useIngest,
539
+ usePageIngest,
501
540
  useSearch
502
541
  };
542
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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"]}
package/package.json CHANGED
@@ -1,32 +1,31 @@
1
- {
2
- "name": "@huskel/sdk",
3
- "version": "0.2.2",
4
- "description": "Huskel AI-powered search SDK for SPAs",
5
- "main": "dist/index.js",
6
- "module": "dist/index.esm.js",
7
- "types": "dist/index.d.ts",
8
- "files": [
9
- "dist"
10
- ],
11
- "peerDependencies": {
12
- "react": ">=17",
13
- "react-dom": ">=17"
14
- },
15
- "peerDependenciesMeta": {
16
- "react": {
17
- "optional": true
18
- },
19
- "react-dom": {
20
- "optional": true
21
- }
22
- },
23
- "devDependencies": {
24
- "@types/react": "^18.0.0",
25
- "tsup": "^8.0.0",
26
- "typescript": "^5.0.0"
27
- },
28
- "scripts": {
29
- "build": "tsup src/index.ts --format cjs,esm --dts",
30
- "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
31
- }
32
- }
1
+ {
2
+ "name": "@huskel/sdk",
3
+ "version": "0.3.0",
4
+ "description": "Huskel AI-powered search SDK for SPAs",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.esm.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": ["dist"],
9
+ "scripts": {
10
+ "build": "tsup",
11
+ "dev": "tsup --watch"
12
+ },
13
+ "peerDependencies": {
14
+ "react": ">=17",
15
+ "react-dom": ">=17"
16
+ },
17
+ "peerDependenciesMeta": {
18
+ "react": { "optional": true },
19
+ "react-dom": { "optional": true }
20
+ },
21
+ "devDependencies": {
22
+ "@types/react": "^18.0.0",
23
+ "tsup": "^8.0.0",
24
+ "typescript": "^5.0.0"
25
+ },
26
+ "pnpm": {
27
+ "onlyBuiltDependencies": [
28
+ "esbuild"
29
+ ]
30
+ }
31
+ }