@huskel/sdk 0.1.0 → 0.2.1
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 +96 -114
- package/dist/index.d.mts +66 -43
- package/dist/index.d.ts +66 -43
- package/dist/index.js +354 -256
- package/dist/index.mjs +343 -250
- package/package.json +15 -9
package/README.md
CHANGED
|
@@ -1,114 +1,96 @@
|
|
|
1
|
-
# @huskel/sdk
|
|
2
|
-
|
|
3
|
-
AI-powered search SDK
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @huskel/sdk
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Setup
|
|
12
|
-
|
|
13
|
-
```tsx
|
|
14
|
-
// app/layout.tsx or _app.tsx
|
|
15
|
-
'use client';
|
|
16
|
-
import { useHuskel } from '@huskel/sdk';
|
|
17
|
-
|
|
18
|
-
export default function RootLayout({ children }) {
|
|
19
|
-
useHuskel({
|
|
20
|
-
siteId: 'your-site-id',
|
|
21
|
-
apiUrl: 'https://your-huskel-backend.com',
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```tsx
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
```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' });
|
|
104
|
-
|
|
105
|
-
// Batch
|
|
106
|
-
await client.api.ingestBatch([...products]);
|
|
107
|
-
```
|
|
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.
|
|
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
|
+
```tsx
|
|
14
|
+
// app/layout.tsx (Next.js) or _app.tsx
|
|
15
|
+
'use client';
|
|
16
|
+
import { useHuskel } from '@huskel/sdk';
|
|
17
|
+
|
|
18
|
+
export default function RootLayout({ children }) {
|
|
19
|
+
useHuskel({
|
|
20
|
+
siteId: 'your-site-id',
|
|
21
|
+
apiUrl: 'https://your-huskel-backend.com',
|
|
22
|
+
apiToken: 'your-api-token',
|
|
23
|
+
});
|
|
24
|
+
return <html><body>{children}</body></html>;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Ingest products (you pass your own data)
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
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
|
+
}
|
|
48
|
+
|
|
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]);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Search
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { SearchBar } from '@huskel/sdk';
|
|
69
|
+
|
|
70
|
+
export function Header() {
|
|
71
|
+
return (
|
|
72
|
+
<SearchBar
|
|
73
|
+
onSelect={(result) => router.push(result.product.url)}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// Headless
|
|
81
|
+
import { useSearch } from '@huskel/sdk';
|
|
82
|
+
|
|
83
|
+
const { results, loading, search } = useSearch();
|
|
84
|
+
<input onChange={e => search(e.target.value)} />
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Sparkle (similar products)
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { Sparkle } from '@huskel/sdk';
|
|
91
|
+
|
|
92
|
+
<Sparkle
|
|
93
|
+
productName={product.name}
|
|
94
|
+
onResult={(similar) => setSimilar(similar)}
|
|
95
|
+
/>
|
|
96
|
+
```
|
package/dist/index.d.mts
CHANGED
|
@@ -22,31 +22,37 @@ interface Product {
|
|
|
22
22
|
priceNumeric?: number;
|
|
23
23
|
slug?: string;
|
|
24
24
|
}
|
|
25
|
-
interface
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
25
|
+
interface RawProductInput {
|
|
26
|
+
name?: string;
|
|
27
|
+
title?: string;
|
|
28
|
+
productName?: string;
|
|
29
|
+
price?: string | number;
|
|
30
|
+
priceNumeric?: number;
|
|
31
|
+
url?: string;
|
|
32
|
+
image?: string;
|
|
33
|
+
thumbnail?: string;
|
|
34
|
+
images?: string[];
|
|
35
|
+
slug?: string;
|
|
36
|
+
id?: string;
|
|
37
|
+
productId?: string;
|
|
38
|
+
brand?: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
originalPrice?: string;
|
|
41
|
+
discount?: string;
|
|
41
42
|
currency?: string;
|
|
42
|
-
|
|
43
|
+
stock?: string;
|
|
44
|
+
availability?: string;
|
|
45
|
+
rating?: string;
|
|
46
|
+
reviewCount?: number;
|
|
47
|
+
category?: string;
|
|
48
|
+
subCategory?: string;
|
|
49
|
+
tags?: string[];
|
|
50
|
+
specs?: Record<string, string>;
|
|
43
51
|
}
|
|
44
52
|
interface HuskelConfig {
|
|
45
|
-
siteId
|
|
46
|
-
apiUrl
|
|
47
|
-
|
|
48
|
-
autoIngest?: boolean;
|
|
49
|
-
debounceMs?: number;
|
|
53
|
+
siteId?: string;
|
|
54
|
+
apiUrl?: string;
|
|
55
|
+
apiToken?: string;
|
|
50
56
|
}
|
|
51
57
|
interface SearchRequest {
|
|
52
58
|
query: string;
|
|
@@ -67,36 +73,43 @@ interface IngestResponse {
|
|
|
67
73
|
message?: string;
|
|
68
74
|
count?: number;
|
|
69
75
|
}
|
|
76
|
+
interface HuskelError {
|
|
77
|
+
status: number;
|
|
78
|
+
message: string;
|
|
79
|
+
}
|
|
70
80
|
|
|
71
81
|
declare class HuskelAPI {
|
|
72
82
|
private apiUrl;
|
|
73
83
|
private siteId;
|
|
74
|
-
|
|
84
|
+
private apiToken;
|
|
85
|
+
constructor(apiUrl: string, siteId: string, apiToken: string);
|
|
75
86
|
private post;
|
|
76
87
|
ingest(product: Product): Promise<IngestResponse>;
|
|
77
88
|
ingestBatch(products: Product[]): Promise<IngestResponse>;
|
|
78
89
|
search(query: string, limit?: number): Promise<SearchResponse>;
|
|
79
|
-
pushConfig(config: Omit<SiteConfig, 'siteId'>): Promise<void>;
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
declare class HuskelClient {
|
|
83
|
-
private config;
|
|
84
93
|
readonly api: HuskelAPI;
|
|
85
|
-
private
|
|
86
|
-
private
|
|
87
|
-
private
|
|
94
|
+
private ingestQueue;
|
|
95
|
+
private ingestTimer;
|
|
96
|
+
private ingestedUrls;
|
|
97
|
+
private onlineHandler;
|
|
88
98
|
constructor(config: HuskelConfig);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
start(): void;
|
|
95
|
-
stop(): void;
|
|
99
|
+
destroy(): void;
|
|
100
|
+
queueIngest(rawProduct: RawProductInput): Promise<void>;
|
|
101
|
+
queueIngestBatch(rawProducts: RawProductInput[]): Promise<void>;
|
|
102
|
+
private scheduleFlush;
|
|
103
|
+
private flushQueue;
|
|
96
104
|
}
|
|
97
105
|
declare function initHuskel(config: HuskelConfig): HuskelClient;
|
|
98
106
|
declare function getHuskelClient(): HuskelClient;
|
|
99
107
|
|
|
108
|
+
/**
|
|
109
|
+
* @deprecated Use <HuskelProvider> instead to avoid SSR issues.
|
|
110
|
+
*/
|
|
111
|
+
declare function useHuskel(config: HuskelConfig): HuskelClient;
|
|
112
|
+
|
|
100
113
|
interface UseSearchReturn {
|
|
101
114
|
results: SearchResult[];
|
|
102
115
|
loading: boolean;
|
|
@@ -106,11 +119,13 @@ interface UseSearchReturn {
|
|
|
106
119
|
}
|
|
107
120
|
declare function useSearch(): UseSearchReturn;
|
|
108
121
|
|
|
109
|
-
interface
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
interface UseIngestReturn {
|
|
123
|
+
ingest: (product: RawProductInput) => Promise<void>;
|
|
124
|
+
ingestBatch: (products: RawProductInput[]) => Promise<void>;
|
|
125
|
+
loading: boolean;
|
|
126
|
+
error: string | null;
|
|
112
127
|
}
|
|
113
|
-
declare function
|
|
128
|
+
declare function useIngest(): UseIngestReturn;
|
|
114
129
|
|
|
115
130
|
interface SearchBarProps {
|
|
116
131
|
placeholder?: string;
|
|
@@ -118,15 +133,23 @@ interface SearchBarProps {
|
|
|
118
133
|
debounceMs?: number;
|
|
119
134
|
onSelect?: (result: SearchResult) => void;
|
|
120
135
|
className?: string;
|
|
136
|
+
inputClassName?: string;
|
|
137
|
+
dropdownClassName?: string;
|
|
121
138
|
renderResult?: (result: SearchResult) => React.ReactNode;
|
|
122
139
|
}
|
|
123
|
-
declare function SearchBar({ placeholder, limit, debounceMs, onSelect, className, renderResult, }: SearchBarProps): react_jsx_runtime.JSX.Element;
|
|
140
|
+
declare function SearchBar({ placeholder, limit, debounceMs, onSelect, className, inputClassName, dropdownClassName, renderResult, }: SearchBarProps): react_jsx_runtime.JSX.Element;
|
|
124
141
|
|
|
125
142
|
interface SparkleProps {
|
|
126
143
|
productName: string;
|
|
127
|
-
|
|
144
|
+
limit?: number;
|
|
128
145
|
onResult?: (results: SearchResult[]) => void;
|
|
146
|
+
className?: string;
|
|
147
|
+
}
|
|
148
|
+
declare function Sparkle({ productName, limit, onResult, className }: SparkleProps): react_jsx_runtime.JSX.Element;
|
|
149
|
+
|
|
150
|
+
interface HuskelProviderProps extends HuskelConfig {
|
|
151
|
+
children: React.ReactNode;
|
|
129
152
|
}
|
|
130
|
-
declare function
|
|
153
|
+
declare function HuskelProvider({ siteId, apiUrl, apiToken, children }: HuskelProviderProps): react_jsx_runtime.JSX.Element;
|
|
131
154
|
|
|
132
|
-
export { HuskelAPI, HuskelClient, type HuskelConfig, type IngestResponse, type Product, SearchBar, type SearchRequest, type SearchResponse, type SearchResult,
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -22,31 +22,37 @@ interface Product {
|
|
|
22
22
|
priceNumeric?: number;
|
|
23
23
|
slug?: string;
|
|
24
24
|
}
|
|
25
|
-
interface
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
25
|
+
interface RawProductInput {
|
|
26
|
+
name?: string;
|
|
27
|
+
title?: string;
|
|
28
|
+
productName?: string;
|
|
29
|
+
price?: string | number;
|
|
30
|
+
priceNumeric?: number;
|
|
31
|
+
url?: string;
|
|
32
|
+
image?: string;
|
|
33
|
+
thumbnail?: string;
|
|
34
|
+
images?: string[];
|
|
35
|
+
slug?: string;
|
|
36
|
+
id?: string;
|
|
37
|
+
productId?: string;
|
|
38
|
+
brand?: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
originalPrice?: string;
|
|
41
|
+
discount?: string;
|
|
41
42
|
currency?: string;
|
|
42
|
-
|
|
43
|
+
stock?: string;
|
|
44
|
+
availability?: string;
|
|
45
|
+
rating?: string;
|
|
46
|
+
reviewCount?: number;
|
|
47
|
+
category?: string;
|
|
48
|
+
subCategory?: string;
|
|
49
|
+
tags?: string[];
|
|
50
|
+
specs?: Record<string, string>;
|
|
43
51
|
}
|
|
44
52
|
interface HuskelConfig {
|
|
45
|
-
siteId
|
|
46
|
-
apiUrl
|
|
47
|
-
|
|
48
|
-
autoIngest?: boolean;
|
|
49
|
-
debounceMs?: number;
|
|
53
|
+
siteId?: string;
|
|
54
|
+
apiUrl?: string;
|
|
55
|
+
apiToken?: string;
|
|
50
56
|
}
|
|
51
57
|
interface SearchRequest {
|
|
52
58
|
query: string;
|
|
@@ -67,36 +73,43 @@ interface IngestResponse {
|
|
|
67
73
|
message?: string;
|
|
68
74
|
count?: number;
|
|
69
75
|
}
|
|
76
|
+
interface HuskelError {
|
|
77
|
+
status: number;
|
|
78
|
+
message: string;
|
|
79
|
+
}
|
|
70
80
|
|
|
71
81
|
declare class HuskelAPI {
|
|
72
82
|
private apiUrl;
|
|
73
83
|
private siteId;
|
|
74
|
-
|
|
84
|
+
private apiToken;
|
|
85
|
+
constructor(apiUrl: string, siteId: string, apiToken: string);
|
|
75
86
|
private post;
|
|
76
87
|
ingest(product: Product): Promise<IngestResponse>;
|
|
77
88
|
ingestBatch(products: Product[]): Promise<IngestResponse>;
|
|
78
89
|
search(query: string, limit?: number): Promise<SearchResponse>;
|
|
79
|
-
pushConfig(config: Omit<SiteConfig, 'siteId'>): Promise<void>;
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
declare class HuskelClient {
|
|
83
|
-
private config;
|
|
84
93
|
readonly api: HuskelAPI;
|
|
85
|
-
private
|
|
86
|
-
private
|
|
87
|
-
private
|
|
94
|
+
private ingestQueue;
|
|
95
|
+
private ingestTimer;
|
|
96
|
+
private ingestedUrls;
|
|
97
|
+
private onlineHandler;
|
|
88
98
|
constructor(config: HuskelConfig);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
start(): void;
|
|
95
|
-
stop(): void;
|
|
99
|
+
destroy(): void;
|
|
100
|
+
queueIngest(rawProduct: RawProductInput): Promise<void>;
|
|
101
|
+
queueIngestBatch(rawProducts: RawProductInput[]): Promise<void>;
|
|
102
|
+
private scheduleFlush;
|
|
103
|
+
private flushQueue;
|
|
96
104
|
}
|
|
97
105
|
declare function initHuskel(config: HuskelConfig): HuskelClient;
|
|
98
106
|
declare function getHuskelClient(): HuskelClient;
|
|
99
107
|
|
|
108
|
+
/**
|
|
109
|
+
* @deprecated Use <HuskelProvider> instead to avoid SSR issues.
|
|
110
|
+
*/
|
|
111
|
+
declare function useHuskel(config: HuskelConfig): HuskelClient;
|
|
112
|
+
|
|
100
113
|
interface UseSearchReturn {
|
|
101
114
|
results: SearchResult[];
|
|
102
115
|
loading: boolean;
|
|
@@ -106,11 +119,13 @@ interface UseSearchReturn {
|
|
|
106
119
|
}
|
|
107
120
|
declare function useSearch(): UseSearchReturn;
|
|
108
121
|
|
|
109
|
-
interface
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
interface UseIngestReturn {
|
|
123
|
+
ingest: (product: RawProductInput) => Promise<void>;
|
|
124
|
+
ingestBatch: (products: RawProductInput[]) => Promise<void>;
|
|
125
|
+
loading: boolean;
|
|
126
|
+
error: string | null;
|
|
112
127
|
}
|
|
113
|
-
declare function
|
|
128
|
+
declare function useIngest(): UseIngestReturn;
|
|
114
129
|
|
|
115
130
|
interface SearchBarProps {
|
|
116
131
|
placeholder?: string;
|
|
@@ -118,15 +133,23 @@ interface SearchBarProps {
|
|
|
118
133
|
debounceMs?: number;
|
|
119
134
|
onSelect?: (result: SearchResult) => void;
|
|
120
135
|
className?: string;
|
|
136
|
+
inputClassName?: string;
|
|
137
|
+
dropdownClassName?: string;
|
|
121
138
|
renderResult?: (result: SearchResult) => React.ReactNode;
|
|
122
139
|
}
|
|
123
|
-
declare function SearchBar({ placeholder, limit, debounceMs, onSelect, className, renderResult, }: SearchBarProps): react_jsx_runtime.JSX.Element;
|
|
140
|
+
declare function SearchBar({ placeholder, limit, debounceMs, onSelect, className, inputClassName, dropdownClassName, renderResult, }: SearchBarProps): react_jsx_runtime.JSX.Element;
|
|
124
141
|
|
|
125
142
|
interface SparkleProps {
|
|
126
143
|
productName: string;
|
|
127
|
-
|
|
144
|
+
limit?: number;
|
|
128
145
|
onResult?: (results: SearchResult[]) => void;
|
|
146
|
+
className?: string;
|
|
147
|
+
}
|
|
148
|
+
declare function Sparkle({ productName, limit, onResult, className }: SparkleProps): react_jsx_runtime.JSX.Element;
|
|
149
|
+
|
|
150
|
+
interface HuskelProviderProps extends HuskelConfig {
|
|
151
|
+
children: React.ReactNode;
|
|
129
152
|
}
|
|
130
|
-
declare function
|
|
153
|
+
declare function HuskelProvider({ siteId, apiUrl, apiToken, children }: HuskelProviderProps): react_jsx_runtime.JSX.Element;
|
|
131
154
|
|
|
132
|
-
export { HuskelAPI, HuskelClient, type HuskelConfig, type IngestResponse, type Product, SearchBar, type SearchRequest, type SearchResponse, type SearchResult,
|
|
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 };
|