libsql-search 0.1.3 → 0.1.5

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,477 +1,125 @@
1
1
  # libsql-search
2
2
 
3
- Semantic search for static sites using libSQL/Turso with multi-provider embeddings.
3
+ [![npm version](https://img.shields.io/npm/v/libsql-search.svg)](https://www.npmjs.com/package/libsql-search)
4
+ [![JSR](https://jsr.io/badges/@logan/libsql-search)](https://jsr.io/@logan/libsql-search)
5
+ [![CI](https://github.com/llbbl/libsql-search/actions/workflows/ci.yml/badge.svg)](https://github.com/llbbl/libsql-search/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
7
 
5
- Add AI-powered vector search to your Astro, Next.js, or any static site with minimal configuration. Index markdown content, generate embeddings locally or via API, and provide lightning-fast semantic search to your users.
8
+ `libsql-search` adds semantic search to Markdown-backed sites using libSQL/Turso.
9
+ It indexes frontmatter and content from files on disk, stores vectors in your
10
+ database, and lets you query by meaning instead of exact keywords.
6
11
 
7
- ## Features
12
+ Use it when you want:
8
13
 
9
- - 🔍 **Semantic Search** - Find content by meaning, not just keywords
10
- - 🌐 **Multi-Provider Embeddings** - Choose local (Xenova), Gemini, or OpenAI
11
- - **Edge-Ready** - Works with Turso's global edge database
12
- - 📝 **Markdown Support** - Built-in gray-matter parsing
13
- - 🎯 **Type-Safe** - Full TypeScript support
14
- - 🆓 **Free Tier Friendly** - Local embeddings require no API keys
14
+ - a small TypeScript library instead of a hosted search product
15
+ - one search index shared across static-site builds and app routes
16
+ - local or API-based embeddings behind the same indexing/search API
17
+ - direct control over table names, dimensions, content shape, and deployment
15
18
 
16
- ## Installation
19
+ ## What It Supports
20
+
21
+ - Markdown indexing from local directories with frontmatter via `gray-matter`
22
+ - libSQL/Turso storage and vector search
23
+ - Embedding providers that exist in the code today: local
24
+ `Xenova/all-MiniLM-L6-v2`, Google Gemini `text-embedding-004`, and OpenAI
25
+ `text-embedding-3-small` and `text-embedding-3-large`
26
+ - npm distribution plus JSR publishing
27
+
28
+ ## Install
29
+
30
+ `@libsql/client` is a peer dependency.
17
31
 
18
- **npm:**
19
32
  ```bash
20
- npm install libsql-search @libsql/client
33
+ pnpm add libsql-search @libsql/client
21
34
  ```
22
35
 
23
- **pnpm:**
24
36
  ```bash
25
- pnpm add libsql-search @libsql/client
37
+ npm install libsql-search @libsql/client
26
38
  ```
27
39
 
28
- **JSR:**
29
40
  ```bash
30
- deno add @llbbl/libsql-search
41
+ deno add jsr:@logan/libsql-search npm:@libsql/client
31
42
  ```
32
43
 
44
+ For npm usage, the package requires Node `>=22.12.0`.
45
+ Node examples in this README import from `libsql-search` and `@libsql/client`.
46
+ In Deno, after `deno add`, import from `@logan/libsql-search` and
47
+ `@libsql/client`.
48
+
33
49
  ## Quick Start
34
50
 
35
- ### 1. Set Up Your Database
51
+ The shortest working flow is:
52
+
53
+ 1. create a libSQL client
54
+ 2. create the search table
55
+ 3. index a Markdown directory
56
+ 4. query it with the same embedding provider and dimensions
36
57
 
37
- ```typescript
38
- import { createClient } from '@libsql/client';
39
- import { createTable } from 'libsql-search';
58
+ ```ts
59
+ import { createClient } from "@libsql/client";
60
+ import { createTable, indexContent, search } from "libsql-search";
40
61
 
41
62
  const client = createClient({
42
- url: 'libsql://your-db.turso.io',
43
- authToken: 'your-auth-token'
63
+ url: "libsql://your-db.turso.io",
64
+ authToken: "your-auth-token",
44
65
  });
45
66
 
46
- // Create the articles table with vector index
47
- await createTable(client, 'articles', 768);
48
- ```
49
-
50
- ### 2. Index Your Content
51
-
52
- ```typescript
53
- import { indexContent } from 'libsql-search';
67
+ await createTable(client, "articles", 768);
54
68
 
55
- const result = await indexContent({
69
+ await indexContent({
56
70
  client,
57
- contentPath: './content',
71
+ contentPath: "./content",
58
72
  embeddingOptions: {
59
- provider: 'local', // or 'gemini', 'openai'
60
- dimensions: 768
73
+ provider: "local",
74
+ dimensions: 768,
61
75
  },
62
- onProgress: (current, total, file) => {
63
- console.log(`[${current}/${total}] Indexing: ${file}`);
64
- }
65
76
  });
66
77
 
67
- console.log(`Indexed ${result.success}/${result.total} documents`);
68
- ```
69
-
70
- ### 3. Search Your Content
71
-
72
- ```typescript
73
- import { search } from 'libsql-search';
74
-
75
78
  const results = await search({
76
79
  client,
77
- query: 'how to deploy astro',
80
+ query: "how do I deploy my docs site",
78
81
  limit: 5,
79
82
  embeddingOptions: {
80
- provider: 'local'
81
- }
82
- });
83
-
84
- results.forEach(result => {
85
- console.log(`${result.title} (${result.distance})`);
86
- });
87
- ```
88
-
89
- ## Embedding Providers
90
-
91
- ### Local (Xenova/Transformers.js)
92
-
93
- **Free, no API key required**. Runs `all-MiniLM-L6-v2` in Node.js using ONNX.
94
-
95
- ```typescript
96
- embeddingOptions: {
97
- provider: 'local',
98
- dimensions: 768 // 384 native, padded to 768
99
- }
100
- ```
101
-
102
- **Pros:**
103
- - ✅ No API costs
104
- - ✅ No rate limits
105
- - ✅ Works offline
106
- - ✅ Privacy-friendly
107
-
108
- **Cons:**
109
- - ⚠️ First run downloads model (~50MB)
110
- - ⚠️ Slower than API-based options
111
- - ⚠️ Lower quality than large models
112
-
113
- ### Google Gemini
114
-
115
- **Free tier: 1,500 requests/day**. Uses `text-embedding-004` model.
116
-
117
- ```typescript
118
- embeddingOptions: {
119
- provider: 'gemini',
120
- apiKey: process.env.GEMINI_API_KEY,
121
- dimensions: 768 // native
122
- }
123
- ```
124
-
125
- **Pros:**
126
- - ✅ Generous free tier
127
- - ✅ High quality embeddings
128
- - ✅ Fast
129
-
130
- **Cons:**
131
- - ⚠️ Requires API key
132
- - ⚠️ Rate limited
133
-
134
- ### OpenAI
135
-
136
- **Paid only**. Uses `text-embedding-3-small` or `text-embedding-3-large`.
137
-
138
- ```typescript
139
- embeddingOptions: {
140
- provider: 'openai',
141
- apiKey: process.env.OPENAI_API_KEY,
142
- dimensions: 1536 // or 3072 for large
143
- }
144
- ```
145
-
146
- **Pros:**
147
- - ✅ Highest quality
148
- - ✅ Very fast
149
- - ✅ Configurable dimensions
150
-
151
- **Cons:**
152
- - ⚠️ Costs money ($0.02 per 1M tokens)
153
- - ⚠️ Requires API key
154
-
155
- ## API Reference
156
-
157
- ### Indexing
158
-
159
- #### `indexContent(options)`
160
-
161
- Index markdown files from a directory.
162
-
163
- ```typescript
164
- interface IndexerOptions {
165
- client: Client; // libSQL client
166
- contentPath: string; // Path to content directory
167
- embeddingOptions?: EmbeddingOptions;
168
- fileExtensions?: string[]; // Default: ['.md', '.markdown']
169
- exclude?: string[]; // Default: ['node_modules', '.git']
170
- tableName?: string; // Default: 'articles'
171
- onProgress?: (current, total, file) => void;
172
- }
173
- ```
174
-
175
- #### `createTable(client, tableName?, dimensions?)`
176
-
177
- Create the articles table with vector index.
178
-
179
- ### Searching
180
-
181
- #### `search(options)`
182
-
183
- Perform semantic search.
184
-
185
- ```typescript
186
- interface SearchOptions {
187
- client: Client;
188
- query: string;
189
- limit?: number; // Default: 10
190
- tableName?: string; // Default: 'articles'
191
- embeddingOptions?: EmbeddingOptions;
192
- }
193
- ```
194
-
195
- Returns `SearchResult[]`:
196
-
197
- ```typescript
198
- interface SearchResult {
199
- id: number;
200
- slug: string;
201
- title: string;
202
- content: string;
203
- folder: string;
204
- tags: string[];
205
- distance: number; // Lower is better
206
- created_at: string;
207
- }
208
- ```
209
-
210
- #### `getAllArticles(client, tableName?)`
211
-
212
- Get all articles (useful for building static pages).
213
-
214
- #### `getArticleBySlug(client, slug, tableName?)`
215
-
216
- Get a single article by slug.
217
-
218
- #### `getArticlesByFolder(client, folder, tableName?)`
219
-
220
- Get all articles in a folder.
221
-
222
- #### `getFolders(client, tableName?)`
223
-
224
- Get all unique folders.
225
-
226
- ### Embeddings
227
-
228
- #### `generateEmbedding(text, options?)`
229
-
230
- Generate embeddings for arbitrary text.
231
-
232
- ```typescript
233
- interface EmbeddingOptions {
234
- provider?: 'local' | 'gemini' | 'openai';
235
- apiKey?: string;
236
- dimensions?: number;
237
- maxLength?: number; // Default: 8000
238
- }
239
- ```
240
-
241
- #### `prepareTextForEmbedding(fields)`
242
-
243
- Combine multiple fields into embedding text.
244
-
245
- ```typescript
246
- const text = prepareTextForEmbedding({
247
- title: 'My Article',
248
- description: 'A description',
249
- content: '# Content here',
250
- tags: ['astro', 'turso']
251
- });
252
- ```
253
-
254
- ## Framework Integration
255
-
256
- ### Astro
257
-
258
- **Search API Endpoint** (`src/pages/api/search.json.ts`):
259
-
260
- ```typescript
261
- import type { APIRoute } from 'astro';
262
- import { createClient } from '@libsql/client';
263
- import { search } from 'libsql-search';
264
-
265
- export const prerender = false;
266
-
267
- const client = createClient({
268
- url: import.meta.env.TURSO_DB_URL,
269
- authToken: import.meta.env.TURSO_AUTH_TOKEN
270
- });
271
-
272
- export const POST: APIRoute = async ({ request }) => {
273
- const { query, limit = 10 } = await request.json();
274
-
275
- const results = await search({
276
- client,
277
- query,
278
- limit,
279
- embeddingOptions: { provider: 'local' }
280
- });
281
-
282
- return new Response(JSON.stringify({ results }), {
283
- headers: { 'Content-Type': 'application/json' }
284
- });
285
- };
286
- ```
287
-
288
- **Static Page Generation** (`src/pages/[...slug].astro`):
289
-
290
- ```astro
291
- ---
292
- import { createClient } from '@libsql/client';
293
- import { getAllArticles, getArticleBySlug } from 'libsql-search';
294
-
295
- export const prerender = true;
296
-
297
- const client = createClient({
298
- url: import.meta.env.TURSO_DB_URL,
299
- authToken: import.meta.env.TURSO_AUTH_TOKEN
300
- });
301
-
302
- export async function getStaticPaths() {
303
- const articles = await getAllArticles(client);
304
- return articles.map(article => ({
305
- params: { slug: article.slug }
306
- }));
307
- }
308
-
309
- const { slug } = Astro.params;
310
- const article = await getArticleBySlug(client, slug);
311
- ---
312
-
313
- <article>
314
- <h1>{article.title}</h1>
315
- <div set:html={article.content} />
316
- </article>
317
- ```
318
-
319
- ### Next.js
320
-
321
- **API Route** (`app/api/search/route.ts`):
322
-
323
- ```typescript
324
- import { createClient } from '@libsql/client';
325
- import { search } from 'libsql-search';
326
- import { NextRequest } from 'next/server';
327
-
328
- const client = createClient({
329
- url: process.env.TURSO_DB_URL!,
330
- authToken: process.env.TURSO_AUTH_TOKEN!
331
- });
332
-
333
- export async function POST(request: NextRequest) {
334
- const { query, limit = 10 } = await request.json();
335
-
336
- const results = await search({
337
- client,
338
- query,
339
- limit,
340
- embeddingOptions: { provider: 'local' }
341
- });
342
-
343
- return Response.json({ results });
344
- }
345
- ```
346
-
347
- **Static Generation** (`app/[slug]/page.tsx`):
348
-
349
- ```typescript
350
- import { createClient } from '@libsql/client';
351
- import { getAllArticles, getArticleBySlug } from 'libsql-search';
352
-
353
- const client = createClient({
354
- url: process.env.TURSO_DB_URL!,
355
- authToken: process.env.TURSO_AUTH_TOKEN!
83
+ provider: "local",
84
+ dimensions: 768,
85
+ },
356
86
  });
357
87
 
358
- export async function generateStaticParams() {
359
- const articles = await getAllArticles(client);
360
- return articles.map(article => ({
361
- slug: article.slug
362
- }));
363
- }
364
-
365
- export default async function Page({ params }: { params: { slug: string } }) {
366
- const article = await getArticleBySlug(client, params.slug);
367
-
368
- return (
369
- <article>
370
- <h1>{article.title}</h1>
371
- <div dangerouslySetInnerHTML={{ __html: article.content }} />
372
- </article>
373
- );
374
- }
375
- ```
376
-
377
- ## Best Practices
378
-
379
- ### Embedding Dimensions
380
-
381
- - Use **768 dimensions** for best compatibility
382
- - Local model outputs 384, automatically padded to 768
383
- - Gemini outputs 768 natively
384
- - OpenAI supports custom dimensions
385
-
386
- ### Index Updates
387
-
388
- Create a script to re-index content:
389
-
390
- ```json
391
- {
392
- "scripts": {
393
- "index": "node scripts/index.js",
394
- "build": "npm run index && astro build"
395
- }
396
- }
88
+ console.log(results.map((result) => ({
89
+ slug: result.slug,
90
+ title: result.title,
91
+ distance: result.distance,
92
+ })));
397
93
  ```
398
94
 
399
- ### Search Quality
400
-
401
- Improve search results:
402
-
403
- 1. **Include relevant fields** in embedding text (title, description, tags)
404
- 2. **Truncate long content** to avoid noise
405
- 3. **Use the same provider** for indexing and search
406
- 4. **Experiment with distance thresholds** (lower is better)
407
-
408
- ### Performance
409
-
410
- - **Cache the embedding model** (done automatically)
411
- - **Use edge databases** (Turso) for low latency
412
- - **Implement search debouncing** in the UI
413
- - **Limit result count** to 5-10 for best UX
414
-
415
- ## Examples
416
-
417
- See the `/examples` directory for complete implementations:
418
-
419
- - [Astro Documentation Site](./examples/astro-docs)
420
- - [Next.js Blog](./examples/nextjs-blog)
421
- - [CLI Indexer](./examples/cli-indexer)
95
+ Important behavior:
422
96
 
423
- ## CLI Usage
97
+ - Call `createTable()` before indexing or searching.
98
+ - Keep dimensions aligned across table creation, indexing, and search queries.
99
+ - `indexContent()` clears existing rows before rebuilding the index.
424
100
 
425
- For a standalone indexing script:
101
+ ## Core API
426
102
 
427
- ```javascript
428
- // scripts/index.js
429
- import { createClient } from '@libsql/client';
430
- import { createTable, indexContent } from 'libsql-search';
103
+ - `createTable(client, tableName?, dimensions?)`
104
+ - `indexContent(options)`
105
+ - `search(options)`
106
+ - `getAllArticles(client, tableName?)`
107
+ - `getArticleBySlug(client, slug, tableName?)`
108
+ - `getArticlesByFolder(client, folder, tableName?)`
109
+ - `getFolders(client, tableName?)`
110
+ - `generateEmbedding(text, options?)`
111
+ - `prepareTextForEmbedding(fields)`
431
112
 
432
- const client = createClient({
433
- url: process.env.TURSO_DB_URL,
434
- authToken: process.env.TURSO_AUTH_TOKEN
435
- });
436
-
437
- await createTable(client);
438
-
439
- const result = await indexContent({
440
- client,
441
- contentPath: './content',
442
- embeddingOptions: {
443
- provider: process.env.EMBEDDING_PROVIDER || 'local'
444
- },
445
- onProgress: (current, total, file) => {
446
- console.log(`[${current}/${total}] ${file}`);
447
- }
448
- });
449
-
450
- console.log(`✅ Indexed ${result.success} documents`);
451
- ```
113
+ ## Docs
452
114
 
453
- Run with:
454
- ```bash
455
- node --env-file=.env scripts/index.js
456
- ```
115
+ - [Docs index](./docs/README.md)
116
+ - [Provider guide](./docs/PROVIDERS.md)
117
+ - [API reference](./docs/API.md)
118
+ - [Integration examples](./docs/INTEGRATIONS.md)
119
+ - [Indexing and operations](./docs/INDEXING.md)
120
+ - [Troubleshooting](./docs/TROUBLESHOOTING.md)
121
+ - [Release workflow](./docs/RELEASING.md)
457
122
 
458
123
  ## License
459
124
 
460
125
  MIT
461
-
462
- ## Contributing
463
-
464
- Contributions welcome! Please open an issue or PR on [GitHub](https://github.com/llbbl/libsql-search).
465
-
466
- ## Related Projects
467
-
468
- - [Turso](https://turso.tech) - Edge SQLite database
469
- - [libSQL](https://github.com/tursodatabase/libsql) - Open source SQLite fork
470
- - [Astro](https://astro.build) - Static site framework
471
- - [Transformers.js](https://huggingface.co/docs/transformers.js) - ML models in JavaScript
472
-
473
- ## Support
474
-
475
- - [Documentation](https://github.com/llbbl/libsql-search)
476
- - [Issues](https://github.com/llbbl/libsql-search/issues)
477
- - [Discussions](https://github.com/llbbl/libsql-search/discussions)