libsql-search 0.6.0 → 0.7.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/docs/API.md CHANGED
@@ -37,9 +37,16 @@ It also exports these types:
37
37
  - `SearchOptions`
38
38
  - `SearchResult`
39
39
 
40
+ ## Navigation
41
+
42
+ - [Provider matrix and credential rules](./PROVIDERS.md)
43
+ - [Migration and reindexing guide](./MIGRATIONS.md)
44
+ - [Indexing and operational behavior](./INDEXING.md)
45
+ - [Testing guidance](./TESTING.md)
46
+
40
47
  ## `createTable(client, tableName?, dimensions?)`
41
48
 
42
- Creates the table and supporting indexes used by search.
49
+ Creates the search table and supporting indexes.
43
50
 
44
51
  ```ts
45
52
  await createTable(client);
@@ -50,10 +57,7 @@ Defaults:
50
57
  - `tableName`: `"articles"`
51
58
  - `dimensions`: `384`
52
59
 
53
- `tableName` must be an ASCII SQLite identifier matching
54
- `[A-Za-z_][A-Za-z0-9_]*`. Valid identifiers are quoted internally, so reserved
55
- words such as `"select"` are safe to use. `dimensions` must be a positive
56
- integer.
60
+ `tableName` must be an ASCII SQLite identifier matching `[A-Za-z_][A-Za-z0-9_]*`. Valid identifiers are quoted internally, so reserved words such as `"select"` are safe to use. `dimensions` must be a positive integer.
57
61
 
58
62
  The created schema includes:
59
63
 
@@ -63,10 +67,12 @@ The created schema includes:
63
67
  - `content`
64
68
  - `folder`
65
69
  - `tags`
66
- - `embedding`
70
+ - `embedding F32_BLOB(dimensions)`
67
71
  - `created_at`
68
72
  - `updated_at`
69
73
 
74
+ `createTable()` uses `CREATE TABLE IF NOT EXISTS`, so it does not resize an existing vector column. See [Migration and reindexing guide](./MIGRATIONS.md) before changing widths or providers.
75
+
70
76
  ## `indexContent(options)`
71
77
 
72
78
  Indexes Markdown files from a directory on disk.
@@ -89,8 +95,6 @@ Defaults:
89
95
  - `exclude`: ["node_modules", ".git", "dist", "build"]
90
96
  - `tableName`: `"articles"`
91
97
 
92
- `tableName` follows the same identifier policy as `createTable()`.
93
-
94
98
  Return shape:
95
99
 
96
100
  ```ts
@@ -104,10 +108,9 @@ Return shape:
104
108
  Behavior notes:
105
109
 
106
110
  - `indexContent()` deletes existing rows in the target table before rebuilding
107
- - frontmatter `title`, `description`, and `tags` are folded into the embedding
108
- text
109
- - embeddings default to `intent: "document"` unless `embeddingOptions.intent`
110
- is set explicitly
111
+ - rebuilds are not transactional
112
+ - frontmatter `title`, `description`, and `tags` are folded into the embedding text
113
+ - embeddings default to `intent: "document"` unless `embeddingOptions.intent` is set explicitly
111
114
  - if a file has no frontmatter title, the filename becomes the title
112
115
 
113
116
  ## `search(options)`
@@ -129,9 +132,7 @@ Defaults:
129
132
  - `limit`: `10`
130
133
  - `tableName`: `"articles"`
131
134
 
132
- `limit` must be an integer from `1` through `100`; invalid values are rejected
133
- before query embedding generation. `tableName` follows the same identifier
134
- policy as `createTable()`.
135
+ `limit` must be an integer from `1` through `100`; invalid values are rejected before query embedding generation.
135
136
 
136
137
  Result shape:
137
138
 
@@ -150,8 +151,7 @@ interface SearchResult {
150
151
 
151
152
  Lower `distance` values are better matches.
152
153
 
153
- Search embeddings default to `intent: "query"` unless
154
- `embeddingOptions.intent` is set explicitly.
154
+ Search embeddings default to `intent: "query"` unless `embeddingOptions.intent` is set explicitly.
155
155
 
156
156
  ## Article Retrieval Helpers
157
157
 
@@ -171,82 +171,141 @@ Returns articles in a specific folder.
171
171
 
172
172
  Returns distinct folder names from the index.
173
173
 
174
- All article retrieval helpers validate `tableName` before executing SQL.
174
+ All retrieval helpers validate `tableName` before executing SQL.
175
175
 
176
176
  ## Embedding Helpers
177
177
 
178
- ### `generateEmbedding(text, options?)`
178
+ ### `EmbeddingOptions`
179
179
 
180
- Generates an embedding for arbitrary text using the selected provider.
180
+ ```ts
181
+ interface EmbeddingOptions {
182
+ provider?:
183
+ | "local"
184
+ | "cloudflare"
185
+ | "mistral"
186
+ | "gemini"
187
+ | "openai"
188
+ | "openai-compatible";
189
+ apiKey?: string;
190
+ accountId?: string;
191
+ apiToken?: string;
192
+ baseUrl?: string;
193
+ model?: string;
194
+ batchSize?: number;
195
+ dimensions?: number;
196
+ maxLength?: number;
197
+ intent?: "document" | "query";
198
+ timeoutMs?: number;
199
+ signal?: AbortSignal;
200
+ }
201
+ ```
181
202
 
182
- ### `generateEmbeddings(texts, options?)`
203
+ Important option rules:
183
204
 
184
- Generates an ordered batch of embeddings. Empty batches return `[]` without
185
- creating a hosted provider client or making a network request.
205
+ - `provider` defaults to `local`
206
+ - `maxLength` defaults to `8000`
207
+ - `timeoutMs` defaults to `30000`
208
+ - `model` is only used by `openai-compatible`
209
+ - `baseUrl`, `model`, and `dimensions` are required for `openai-compatible`
210
+ - `batchSize` only applies to `openai-compatible` and defaults to `32`
211
+ - `openai-compatible` never reads `OPENAI_API_KEY`
212
+ - only the Gemini adapter currently changes payload formatting by `intent`
186
213
 
187
- ### `createEmbeddingProvider(options?)`
214
+ Dimension rules:
215
+
216
+ - local: fixed `384`
217
+ - Cloudflare: fixed `1024`
218
+ - Mistral: fixed `1024`
219
+ - Gemini: default `3072`, allowed integer range `128-3072`
220
+ - OpenAI: default `768`; `text-embedding-3-small` through `1536`, `text-embedding-3-large` above `1536`
221
+ - OpenAI-compatible: required positive integer, no default
188
222
 
189
- Creates a provider client with immutable metadata and an `embed(texts, options?)`
190
- method. Provider clients return a rich `EmbeddingBatchResult`; the compatibility
191
- helpers `generateEmbedding()` and `generateEmbeddings()` continue returning only
192
- vectors.
223
+ See [Provider matrix and credential rules](./PROVIDERS.md) for the canonical provider table.
224
+
225
+ ### `generateEmbedding(text, options?)`
226
+
227
+ Generates one embedding vector.
193
228
 
194
229
  ```ts
195
- const provider = createEmbeddingProvider({
230
+ const embedding = await generateEmbedding("deploy docs", {
196
231
  provider: "openai",
197
232
  apiKey: process.env.OPENAI_API_KEY,
198
233
  dimensions: 1536,
199
234
  });
200
-
201
- console.log(provider.metadata);
202
235
  ```
203
236
 
204
- Provider metadata includes:
237
+ ### `generateEmbeddings(texts, options?)`
205
238
 
206
- - `name`
207
- - `model`
208
- - `dimensions`
209
- - `batch.mode`
210
- - `batch.maxSize`, when the provider has a hard maximum
239
+ Generates an ordered batch of embeddings.
211
240
 
212
- Hosted provider clients are scoped to their options. The library does not reuse
213
- a Cloudflare, Mistral, Gemini, or OpenAI client created with different
214
- credentials or configuration.
241
+ - empty batches return `[]` without loading the local model or making a hosted call
242
+ - OpenAI batches above `2048` inputs are rejected before network work
243
+ - `openai-compatible` batches are chunked sequentially according to `batchSize`
215
244
 
216
- Gemini uses `gemini-embedding-2`. Its default is 3072 dimensions, and explicit
217
- Gemini dimensions must be an integer from 128 through 3072.
245
+ ### `createEmbeddingProvider(options?)`
218
246
 
219
- Local embeddings use `Xenova/all-MiniLM-L6-v2` through
220
- `@huggingface/transformers` and are fixed at the model's native 384 dimensions.
221
- Passing any other local dimension is rejected before the runtime is loaded.
247
+ Creates a provider client with immutable metadata and an `embed(texts, options?)` method.
248
+
249
+ ```ts
250
+ const provider = createEmbeddingProvider({
251
+ provider: "openai-compatible",
252
+ baseUrl: "https://tei.example.internal/v1",
253
+ model: "bge-large-en-v1.5",
254
+ dimensions: 1024,
255
+ batchSize: 32,
256
+ });
257
+
258
+ console.log(provider.metadata);
259
+ ```
260
+
261
+ Provider clients return a rich `EmbeddingBatchResult`; the compatibility helpers `generateEmbedding()` and `generateEmbeddings()` return only vectors.
262
+
263
+ Hosted provider clients are scoped to their current options. The library does not reuse a Cloudflare, Mistral, Gemini, or OpenAI client across different credential sets or configurations.
222
264
 
223
265
  ### `getEmbeddingProviderMetadata(options?)`
224
266
 
225
- Returns the same metadata exposed by `createEmbeddingProvider(options).metadata`
226
- without resolving hosted-provider credentials.
267
+ Returns the same metadata exposed by `createEmbeddingProvider(options).metadata` without resolving hosted-provider credentials.
227
268
 
228
- Provider batch metadata uses:
269
+ Metadata shape:
229
270
 
230
271
  ```ts
231
- type EmbeddingBatchMode = "native" | "sequential";
232
-
233
- interface EmbeddingBatchBehavior {
234
- mode: EmbeddingBatchMode;
235
- maxSize?: number;
272
+ interface EmbeddingProviderMetadata {
273
+ name:
274
+ | "local"
275
+ | "cloudflare"
276
+ | "mistral"
277
+ | "gemini"
278
+ | "openai"
279
+ | "openai-compatible";
280
+ model: string;
281
+ dimensions: number;
282
+ batch: {
283
+ mode: "native" | "sequential";
284
+ maxSize?: number;
285
+ };
236
286
  }
237
287
  ```
238
288
 
239
- `"native"` means the upstream provider accepts the batch in one request.
240
- `"sequential"` means the library accepts an input batch but processes items one
241
- at a time. If `maxSize` is present, the library enforces it before provider or
242
- network work.
289
+ Batch interpretation:
290
+
291
+ - `"native"` means the provider accepts a batch request upstream
292
+ - `"sequential"` means the library accepts a batch but processes items one-by-one
293
+ - `batch.maxSize` is a hard client-side limit when present
243
294
 
244
- Provider clients return:
295
+ `openai-compatible` metadata reports `batch.mode: "native"` because the remote endpoint is expected to accept batch inputs, even though the library may split large arrays into sequential outbound chunks at `batchSize`.
296
+
297
+ ### `EmbeddingBatchResult`
245
298
 
246
299
  ```ts
247
300
  interface EmbeddingBatchResult {
248
301
  embeddings: number[][];
249
- provider: "local" | "cloudflare" | "mistral" | "gemini" | "openai";
302
+ provider:
303
+ | "local"
304
+ | "cloudflare"
305
+ | "mistral"
306
+ | "gemini"
307
+ | "openai"
308
+ | "openai-compatible";
250
309
  model: string;
251
310
  dimensions: number;
252
311
  intent: "document" | "query";
@@ -255,47 +314,17 @@ interface EmbeddingBatchResult {
255
314
 
256
315
  ### `validateEmbeddingBatch(items, expectedCount, expectedDimensions, provider)`
257
316
 
258
- Validates provider results before they are written to the database. It checks
259
- cardinality, dimensions, finite numeric values, and indexed batch ordering.
260
-
261
- `EmbeddingOptions` supports:
262
-
263
- ```ts
264
- interface EmbeddingOptions {
265
- provider?: "local" | "cloudflare" | "mistral" | "gemini" | "openai";
266
- apiKey?: string;
267
- accountId?: string;
268
- apiToken?: string;
269
- dimensions?: number;
270
- maxLength?: number;
271
- intent?: "document" | "query";
272
- timeoutMs?: number;
273
- signal?: AbortSignal;
274
- }
275
- ```
317
+ Validates provider responses before they reach the database:
276
318
 
277
- `apiKey` is used by Mistral, Gemini, and OpenAI. It falls back to
278
- `MISTRAL_API_KEY`, `GEMINI_API_KEY`, or `OPENAI_API_KEY` for those providers.
279
- Cloudflare uses `accountId` and `apiToken`, which fall back to
280
- `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN`.
319
+ - result count must match the requested input count
320
+ - vectors must match the effective dimensions
321
+ - values must be finite numbers
322
+ - indexed provider responses are reordered and checked for contiguous indices
281
323
 
282
324
  ### `padEmbedding(embedding, targetDimensions)`
283
325
 
284
- Pads or truncates an embedding array to the requested length.
285
-
286
- This helper remains exported for callers that used it directly. The local
287
- provider does not use it; local vectors are validated at native 384 dimensions.
326
+ Pads or truncates a vector to the target width. This is exported for compatibility and migration workflows, but the current local provider uses its native `384` dimensions rather than padding by default.
288
327
 
289
328
  ### `prepareTextForEmbedding(fields)`
290
329
 
291
- Combines title, description, tags, and content into the text sent to the
292
- embedding model.
293
-
294
- ```ts
295
- const text = prepareTextForEmbedding({
296
- title: "My Article",
297
- description: "How semantic search works",
298
- tags: ["search", "turso"],
299
- content: "# Content",
300
- });
301
- ```
330
+ Builds the text that is embedded from title, description, content, and tags.
package/docs/INDEXING.md CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  ## Content Shape
4
4
 
5
- `indexContent()` walks a directory tree, reads Markdown files, parses
6
- frontmatter with `gray-matter`, and stores:
5
+ `indexContent()` walks a directory tree, reads Markdown files, parses frontmatter with `gray-matter`, and stores:
7
6
 
8
7
  - `slug`
9
8
  - `title`
@@ -22,41 +21,32 @@ The slug is derived from the file path relative to `contentPath`.
22
21
  await indexContent({
23
22
  client,
24
23
  contentPath: "./content",
25
- tableName: "articles",
24
+ tableName: "articles_local_384",
26
25
  embeddingOptions: {
27
26
  provider: "local",
28
27
  },
29
28
  });
30
29
  ```
31
30
 
32
- That keeps the implementation simple, but it also means a failed rebuild can
33
- leave the index partially repopulated.
31
+ That keeps the implementation simple, but it also means:
34
32
 
35
- Changing an embedding provider or dimension count requires a full re-embed.
36
- For existing local indexes created with the older padded-local behavior, create
37
- or recreate a 384-dimensional table before rebuilding. Those older local
38
- 768-dimensional tables stored 384 model values followed by zero padding;
39
- `indexContent()` clears rows but does not change the table's `F32_BLOB` width.
33
+ - failed rebuilds can leave the table partially repopulated
34
+ - provider or dimension changes should use a parallel table migration
35
+ - `createTable()` does not resize an existing vector column
40
36
 
41
- For Gemini specifically, indexes created with the retired `text-embedding-004`
42
- model must be rebuilt for `gemini-embedding-2` even when staying at 768
43
- dimensions, because the model and query/document formatting both changed. If
44
- you adopt Gemini's 3072-dimensional default, recreate the vector table or build
45
- into a separate table first; clearing rows with `indexContent()` does not change
46
- the table's `F32_BLOB` width.
37
+ If provider, dimensions, model, endpoint, or embedding-space assumptions change, fully reindex into a new table. See the canonical [Migration and reindexing guide](./MIGRATIONS.md).
47
38
 
48
39
  ## Quality Guidelines
49
40
 
50
41
  - include descriptive frontmatter titles
51
42
  - add meaningful `tags` when they help retrieval
52
- - use the same embedding provider and dimensions at index and query time
43
+ - use the same provider and dimensions at index and query time
53
44
  - keep `maxLength` intentional if your content is very large
54
45
  - start with a smaller search `limit` and tune from real query behavior
55
46
 
56
47
  ## Build Integration
57
48
 
58
- Many projects wire indexing into a dedicated script and call it before their
59
- site build:
49
+ Many projects wire indexing into a dedicated script and call it before their site build:
60
50
 
61
51
  ```json
62
52
  {
@@ -69,14 +59,11 @@ site build:
69
59
 
70
60
  ## Table Names
71
61
 
72
- `tableName` must be an ASCII SQLite identifier matching
73
- `[A-Za-z_][A-Za-z0-9_]*`. Valid names are quoted internally for table and index
74
- SQL, so reserved words such as `"select"` work safely. Invalid names fail before
75
- database calls or embedding generation.
62
+ `tableName` must be an ASCII SQLite identifier matching `[A-Za-z_][A-Za-z0-9_]*`. Valid names are quoted internally for table and index SQL, so reserved words such as `"select"` work safely. Invalid names fail before database calls or embedding generation.
76
63
 
77
64
  ## Runtime Notes
78
65
 
79
66
  - local embeddings may download and cache a model on the first run
80
67
  - Node users need `@libsql/client` installed alongside the package
81
- - the repository validates both the npm package build and `deno check`, but the
82
- indexing flow itself still depends on filesystem access
68
+ - hosted providers send indexed or queried text to external services
69
+ - the repository validates package build and `deno check`, but indexing still depends on filesystem access