@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 +50 -68
- package/dist/index.d.mts +22 -43
- package/dist/index.d.ts +22 -43
- package/dist/index.js +153 -257
- package/dist/index.mjs +145 -253
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @huskel/sdk
|
|
2
2
|
|
|
3
|
-
AI-powered search SDK
|
|
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
|
|
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
|
-
|
|
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
|
-
##
|
|
28
|
+
## Ingest products (you pass your own data)
|
|
41
29
|
|
|
42
30
|
```tsx
|
|
43
|
-
import {
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
##
|
|
65
|
+
## Search
|
|
58
66
|
|
|
59
67
|
```tsx
|
|
60
|
-
import {
|
|
68
|
+
import { SearchBar } from '@huskel/sdk';
|
|
61
69
|
|
|
62
|
-
export function
|
|
70
|
+
export function Header() {
|
|
63
71
|
return (
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
##
|
|
87
|
+
## Sparkle (similar products)
|
|
96
88
|
|
|
97
89
|
```tsx
|
|
98
|
-
import {
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
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
|
-
|
|
105
|
+
limit?: number;
|
|
128
106
|
onResult?: (results: SearchResult[]) => void;
|
|
107
|
+
className?: string;
|
|
129
108
|
}
|
|
130
|
-
declare function Sparkle({ productName,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
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
|
-
|
|
105
|
+
limit?: number;
|
|
128
106
|
onResult?: (results: SearchResult[]) => void;
|
|
107
|
+
className?: string;
|
|
129
108
|
}
|
|
130
|
-
declare function Sparkle({ productName,
|
|
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,
|
|
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 };
|