@huskel/sdk 0.1.0 → 0.2.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,6 +1,6 @@
1
1
  # @huskel/sdk
2
2
 
3
- AI-powered search SDK for SPAs. Drop in, point at your product DOM, get vector search + LLM responses.
3
+ AI-powered vector search SDK. You own your data pass it in, we handle the rest.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,10 +8,10 @@ AI-powered search SDK for SPAs. Drop in, point at your product DOM, get vector s
8
8
  npm install @huskel/sdk
9
9
  ```
10
10
 
11
- ## Setup (Next.js app layout)
11
+ ## Setup
12
12
 
13
13
  ```tsx
14
- // app/layout.tsx or _app.tsx
14
+ // app/layout.tsx (Next.js) or _app.tsx
15
15
  'use client';
16
16
  import { useHuskel } from '@huskel/sdk';
17
17
 
@@ -19,96 +19,78 @@ export default function RootLayout({ children }) {
19
19
  useHuskel({
20
20
  siteId: 'your-site-id',
21
21
  apiUrl: 'https://your-huskel-backend.com',
22
- autoIngest: true,
23
- debounceMs: 600,
24
- selectors: {
25
- selectorContainer: '.product-card',
26
- selectorName: '.product-title',
27
- selectorPrice: '.product-price',
28
- selectorImage: '.product-image img',
29
- selectorUrl: 'a.product-link',
30
- selectorDescription: '.product-desc',
31
- selectorCategory: '.product-category',
32
- currency: 'KES',
33
- },
22
+ apiToken: 'your-api-token',
34
23
  });
35
-
36
24
  return <html><body>{children}</body></html>;
37
25
  }
38
26
  ```
39
27
 
40
- ## Search bar
28
+ ## Ingest products (you pass your own data)
41
29
 
42
30
  ```tsx
43
- import { SearchBar } from '@huskel/sdk';
31
+ import { useIngest } from '@huskel/sdk';
32
+
33
+ // Single product page
34
+ export function ProductPage({ product }) {
35
+ const { ingest } = useIngest();
36
+
37
+ useEffect(() => {
38
+ ingest({
39
+ name: product.title,
40
+ price: product.price,
41
+ url: window.location.href,
42
+ images: product.images,
43
+ category: product.category,
44
+ currency: 'KES',
45
+ });
46
+ }, [product.id]);
47
+ }
44
48
 
45
- export function Header() {
46
- return (
47
- <SearchBar
48
- placeholder="Search for what you want — how you want"
49
- onSelect={(result) => {
50
- window.location.href = result.product.url;
51
- }}
52
- />
53
- );
49
+ // Listing / category page
50
+ export function ProductGrid({ products }) {
51
+ const { ingestBatch } = useIngest();
52
+
53
+ useEffect(() => {
54
+ ingestBatch(products.map(p => ({
55
+ name: p.title,
56
+ price: p.price,
57
+ url: `/products/${p.slug}`,
58
+ images: [p.thumbnail],
59
+ currency: 'KES',
60
+ })));
61
+ }, [products]);
54
62
  }
55
63
  ```
56
64
 
57
- ## Sparkle on product card
65
+ ## Search
58
66
 
59
67
  ```tsx
60
- import { Sparkle } from '@huskel/sdk';
68
+ import { SearchBar } from '@huskel/sdk';
61
69
 
62
- export function ProductCard({ product }) {
70
+ export function Header() {
63
71
  return (
64
- <div className="product-card">
65
- <img src={product.image} />
66
- <h3>{product.name}</h3>
67
- <p>{product.price}</p>
68
- <Sparkle
69
- productName={product.name}
70
- onResult={(similar) => console.log(similar)}
71
- />
72
- </div>
72
+ <SearchBar
73
+ onSelect={(result) => router.push(result.product.url)}
74
+ />
73
75
  );
74
76
  }
75
77
  ```
76
78
 
77
- ## useSearch hook (custom UI)
78
-
79
79
  ```tsx
80
+ // Headless
80
81
  import { useSearch } from '@huskel/sdk';
81
82
 
82
- export function CustomSearch() {
83
- const { results, loading, search } = useSearch();
84
-
85
- return (
86
- <div>
87
- <input onChange={e => search(e.target.value)} />
88
- {loading && <p>Loading…</p>}
89
- {results.map(r => <div key={r.id}>{r.product.name}</div>)}
90
- </div>
91
- );
92
- }
83
+ const { results, loading, search } = useSearch();
84
+ <input onChange={e => search(e.target.value)} />
93
85
  ```
94
86
 
95
- ## Manual ingest
87
+ ## Sparkle (similar products)
96
88
 
97
89
  ```tsx
98
- import { getHuskelClient } from '@huskel/sdk';
99
-
100
- const client = getHuskelClient();
101
-
102
- // Single product
103
- await client.api.ingest({ name: 'Maize Flour 2kg', price: 'KES 180', url: '/products/maize-flour' });
90
+ import { Sparkle } from '@huskel/sdk';
104
91
 
105
- // Batch
106
- await client.api.ingestBatch([...products]);
92
+ <Sparkle
93
+ productName={product.name}
94
+ onResult={(similar) => setSimilar(similar)}
95
+ />
107
96
  ```
108
-
109
- ## How it works
110
-
111
- 1. `useHuskel` pushes your CSS selector config to the backend on mount.
112
- 2. On every SPA route change, the SDK waits for the DOM to settle (debounce), then extracts products using your selectors.
113
- 3. Products are batch-ingested to your Go backend → Upstash (BAAI/BGE embeddings) → NeonDB.
114
- 4. `useSearch` / `SearchBar` hit `/search` for vector similarity results powered by the ingested data.
package/dist/index.d.mts CHANGED
@@ -22,31 +22,10 @@ interface Product {
22
22
  priceNumeric?: number;
23
23
  slug?: string;
24
24
  }
25
- interface SiteConfig {
26
- siteId: string;
27
- selectorContainer: string;
28
- selectorName: string;
29
- selectorPrice: string;
30
- selectorImage?: string;
31
- selectorUrl?: string;
32
- selectorDescription?: string;
33
- selectorCategory?: string;
34
- selectorBrand?: string;
35
- selectorAvailability?: string;
36
- selectorSku?: string;
37
- selectorOriginalPrice?: string;
38
- selectorDiscount?: string;
39
- selectorRating?: string;
40
- selectorNextPage?: string;
41
- currency?: string;
42
- maxPages?: number;
43
- }
44
25
  interface HuskelConfig {
45
26
  siteId: string;
46
27
  apiUrl: string;
47
- selectors: Omit<SiteConfig, 'siteId'>;
48
- autoIngest?: boolean;
49
- debounceMs?: number;
28
+ apiToken: string;
50
29
  }
51
30
  interface SearchRequest {
52
31
  query: string;
@@ -67,36 +46,31 @@ interface IngestResponse {
67
46
  message?: string;
68
47
  count?: number;
69
48
  }
49
+ interface HuskelError {
50
+ status: number;
51
+ message: string;
52
+ }
70
53
 
71
54
  declare class HuskelAPI {
72
55
  private apiUrl;
73
56
  private siteId;
74
- constructor(apiUrl: string, siteId: string);
57
+ private apiToken;
58
+ constructor(apiUrl: string, siteId: string, apiToken: string);
75
59
  private post;
76
60
  ingest(product: Product): Promise<IngestResponse>;
77
61
  ingestBatch(products: Product[]): Promise<IngestResponse>;
78
62
  search(query: string, limit?: number): Promise<SearchResponse>;
79
- pushConfig(config: Omit<SiteConfig, 'siteId'>): Promise<void>;
80
63
  }
81
64
 
82
65
  declare class HuskelClient {
83
- private config;
84
66
  readonly api: HuskelAPI;
85
- private observer?;
86
- private unsubscribe?;
87
- private debounceTimer?;
88
67
  constructor(config: HuskelConfig);
89
- /** Push selectors config to backend once (idempotent) */
90
- configure(): Promise<void>;
91
- /** Scrape current DOM and ingest all products on the page */
92
- ingestCurrentPage(): Promise<Product[]>;
93
- /** Start watching route changes and auto-ingesting */
94
- start(): void;
95
- stop(): void;
96
68
  }
97
69
  declare function initHuskel(config: HuskelConfig): HuskelClient;
98
70
  declare function getHuskelClient(): HuskelClient;
99
71
 
72
+ declare function useHuskel(config: HuskelConfig): HuskelClient;
73
+
100
74
  interface UseSearchReturn {
101
75
  results: SearchResult[];
102
76
  loading: boolean;
@@ -106,11 +80,13 @@ interface UseSearchReturn {
106
80
  }
107
81
  declare function useSearch(): UseSearchReturn;
108
82
 
109
- interface UseHuskelOptions extends HuskelConfig {
110
- onIngest?: (products: Product[]) => void;
111
- onError?: (err: Error) => void;
83
+ interface UseIngestReturn {
84
+ ingest: (product: Product) => Promise<void>;
85
+ ingestBatch: (products: Product[]) => Promise<void>;
86
+ loading: boolean;
87
+ error: string | null;
112
88
  }
113
- declare function useHuskel(options: UseHuskelOptions): HuskelClient | null;
89
+ declare function useIngest(): UseIngestReturn;
114
90
 
115
91
  interface SearchBarProps {
116
92
  placeholder?: string;
@@ -118,15 +94,18 @@ interface SearchBarProps {
118
94
  debounceMs?: number;
119
95
  onSelect?: (result: SearchResult) => void;
120
96
  className?: string;
97
+ inputClassName?: string;
98
+ dropdownClassName?: string;
121
99
  renderResult?: (result: SearchResult) => React.ReactNode;
122
100
  }
123
- declare function SearchBar({ placeholder, limit, debounceMs, onSelect, className, renderResult, }: SearchBarProps): react_jsx_runtime.JSX.Element;
101
+ declare function SearchBar({ placeholder, limit, debounceMs, onSelect, className, inputClassName, dropdownClassName, renderResult, }: SearchBarProps): react_jsx_runtime.JSX.Element;
124
102
 
125
103
  interface SparkleProps {
126
104
  productName: string;
127
- className?: string;
105
+ limit?: number;
128
106
  onResult?: (results: SearchResult[]) => void;
107
+ className?: string;
129
108
  }
130
- declare function Sparkle({ productName, className, onResult }: SparkleProps): react_jsx_runtime.JSX.Element;
109
+ declare function Sparkle({ productName, limit, onResult, className }: SparkleProps): react_jsx_runtime.JSX.Element;
131
110
 
132
- export { HuskelAPI, HuskelClient, type HuskelConfig, type IngestResponse, type Product, SearchBar, type SearchRequest, type SearchResponse, type SearchResult, type SiteConfig, Sparkle, getHuskelClient, initHuskel, useHuskel, useSearch };
111
+ export { HuskelAPI, HuskelClient, type HuskelConfig, type HuskelError, type IngestResponse, type Product, SearchBar, type SearchRequest, type SearchResponse, type SearchResult, Sparkle, getHuskelClient, initHuskel, useHuskel, useIngest, useSearch };
package/dist/index.d.ts CHANGED
@@ -22,31 +22,10 @@ interface Product {
22
22
  priceNumeric?: number;
23
23
  slug?: string;
24
24
  }
25
- interface SiteConfig {
26
- siteId: string;
27
- selectorContainer: string;
28
- selectorName: string;
29
- selectorPrice: string;
30
- selectorImage?: string;
31
- selectorUrl?: string;
32
- selectorDescription?: string;
33
- selectorCategory?: string;
34
- selectorBrand?: string;
35
- selectorAvailability?: string;
36
- selectorSku?: string;
37
- selectorOriginalPrice?: string;
38
- selectorDiscount?: string;
39
- selectorRating?: string;
40
- selectorNextPage?: string;
41
- currency?: string;
42
- maxPages?: number;
43
- }
44
25
  interface HuskelConfig {
45
26
  siteId: string;
46
27
  apiUrl: string;
47
- selectors: Omit<SiteConfig, 'siteId'>;
48
- autoIngest?: boolean;
49
- debounceMs?: number;
28
+ apiToken: string;
50
29
  }
51
30
  interface SearchRequest {
52
31
  query: string;
@@ -67,36 +46,31 @@ interface IngestResponse {
67
46
  message?: string;
68
47
  count?: number;
69
48
  }
49
+ interface HuskelError {
50
+ status: number;
51
+ message: string;
52
+ }
70
53
 
71
54
  declare class HuskelAPI {
72
55
  private apiUrl;
73
56
  private siteId;
74
- constructor(apiUrl: string, siteId: string);
57
+ private apiToken;
58
+ constructor(apiUrl: string, siteId: string, apiToken: string);
75
59
  private post;
76
60
  ingest(product: Product): Promise<IngestResponse>;
77
61
  ingestBatch(products: Product[]): Promise<IngestResponse>;
78
62
  search(query: string, limit?: number): Promise<SearchResponse>;
79
- pushConfig(config: Omit<SiteConfig, 'siteId'>): Promise<void>;
80
63
  }
81
64
 
82
65
  declare class HuskelClient {
83
- private config;
84
66
  readonly api: HuskelAPI;
85
- private observer?;
86
- private unsubscribe?;
87
- private debounceTimer?;
88
67
  constructor(config: HuskelConfig);
89
- /** Push selectors config to backend once (idempotent) */
90
- configure(): Promise<void>;
91
- /** Scrape current DOM and ingest all products on the page */
92
- ingestCurrentPage(): Promise<Product[]>;
93
- /** Start watching route changes and auto-ingesting */
94
- start(): void;
95
- stop(): void;
96
68
  }
97
69
  declare function initHuskel(config: HuskelConfig): HuskelClient;
98
70
  declare function getHuskelClient(): HuskelClient;
99
71
 
72
+ declare function useHuskel(config: HuskelConfig): HuskelClient;
73
+
100
74
  interface UseSearchReturn {
101
75
  results: SearchResult[];
102
76
  loading: boolean;
@@ -106,11 +80,13 @@ interface UseSearchReturn {
106
80
  }
107
81
  declare function useSearch(): UseSearchReturn;
108
82
 
109
- interface UseHuskelOptions extends HuskelConfig {
110
- onIngest?: (products: Product[]) => void;
111
- onError?: (err: Error) => void;
83
+ interface UseIngestReturn {
84
+ ingest: (product: Product) => Promise<void>;
85
+ ingestBatch: (products: Product[]) => Promise<void>;
86
+ loading: boolean;
87
+ error: string | null;
112
88
  }
113
- declare function useHuskel(options: UseHuskelOptions): HuskelClient | null;
89
+ declare function useIngest(): UseIngestReturn;
114
90
 
115
91
  interface SearchBarProps {
116
92
  placeholder?: string;
@@ -118,15 +94,18 @@ interface SearchBarProps {
118
94
  debounceMs?: number;
119
95
  onSelect?: (result: SearchResult) => void;
120
96
  className?: string;
97
+ inputClassName?: string;
98
+ dropdownClassName?: string;
121
99
  renderResult?: (result: SearchResult) => React.ReactNode;
122
100
  }
123
- declare function SearchBar({ placeholder, limit, debounceMs, onSelect, className, renderResult, }: SearchBarProps): react_jsx_runtime.JSX.Element;
101
+ declare function SearchBar({ placeholder, limit, debounceMs, onSelect, className, inputClassName, dropdownClassName, renderResult, }: SearchBarProps): react_jsx_runtime.JSX.Element;
124
102
 
125
103
  interface SparkleProps {
126
104
  productName: string;
127
- className?: string;
105
+ limit?: number;
128
106
  onResult?: (results: SearchResult[]) => void;
107
+ className?: string;
129
108
  }
130
- declare function Sparkle({ productName, className, onResult }: SparkleProps): react_jsx_runtime.JSX.Element;
109
+ declare function Sparkle({ productName, limit, onResult, className }: SparkleProps): react_jsx_runtime.JSX.Element;
131
110
 
132
- export { HuskelAPI, HuskelClient, type HuskelConfig, type IngestResponse, type Product, SearchBar, type SearchRequest, type SearchResponse, type SearchResult, type SiteConfig, Sparkle, getHuskelClient, initHuskel, useHuskel, useSearch };
111
+ export { HuskelAPI, HuskelClient, type HuskelConfig, type HuskelError, type IngestResponse, type Product, SearchBar, type SearchRequest, type SearchResponse, type SearchResult, Sparkle, getHuskelClient, initHuskel, useHuskel, useIngest, useSearch };