libsql-search 0.1.4 → 0.1.6

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