libsql-search 0.9.0 → 0.10.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 +14 -2
- package/dist/index.cjs +91 -39
- package/dist/index.d.ts +107 -8
- package/dist/index.esm.js +91 -39
- package/dist/turso.cjs +96 -0
- package/dist/turso.d.ts +156 -0
- package/dist/turso.esm.js +94 -0
- package/docs/API.md +31 -4
- package/docs/INDEXING.md +1 -1
- package/docs/MIGRATIONS.md +1 -0
- package/docs/README.md +1 -0
- package/docs/TROUBLESHOOTING.md +38 -0
- package/docs/TURSO.md +230 -0
- package/package.json +17 -4
package/docs/API.md
CHANGED
|
@@ -52,6 +52,25 @@ It also exports these types:
|
|
|
52
52
|
- [Migration and reindexing guide](./MIGRATIONS.md)
|
|
53
53
|
- [Indexing and operational behavior](./INDEXING.md)
|
|
54
54
|
- [Testing guidance](./TESTING.md)
|
|
55
|
+
- [Turso Database backend](./TURSO.md)
|
|
56
|
+
|
|
57
|
+
## The `client` argument
|
|
58
|
+
|
|
59
|
+
Every function that talks to a database takes a `client`. It accepts either:
|
|
60
|
+
|
|
61
|
+
- a `@libsql/client` `Client` — the default, and the only backend the main entry point references, or
|
|
62
|
+
- an adapter from a backend entry point, currently `tursoAdapter()` from `libsql-search/turso`
|
|
63
|
+
|
|
64
|
+
`IndexerOptions["client"]` and `SearchOptions["client"]` are typed as
|
|
65
|
+
`Client | DatabaseAdapter`, so existing `Client`-typed code needs no change. The
|
|
66
|
+
main entry point exports no new symbol for this and never imports a backend
|
|
67
|
+
package other than `@libsql/client`.
|
|
68
|
+
|
|
69
|
+
The adapter carries a capability flag that changes two behaviors on a backend
|
|
70
|
+
without an approximate-nearest-neighbor vector index: `createTable()` skips the
|
|
71
|
+
vector index, and `search()` uses the exact path automatically. Both are
|
|
72
|
+
described in the [Turso Database backend guide](./TURSO.md), which is currently
|
|
73
|
+
the only backend where they apply.
|
|
55
74
|
|
|
56
75
|
## `createTable(client, tableName?, dimensions?)`
|
|
57
76
|
|
|
@@ -61,6 +80,10 @@ Creates the search table and supporting indexes.
|
|
|
61
80
|
await createTable(client);
|
|
62
81
|
```
|
|
63
82
|
|
|
83
|
+
On a backend with no vector index support, the `<tableName>_embedding_idx`
|
|
84
|
+
vector index is skipped; the table, the folder index, and the slug index are
|
|
85
|
+
still created. On `@libsql/client` it is never skipped.
|
|
86
|
+
|
|
64
87
|
Defaults:
|
|
65
88
|
|
|
66
89
|
- `tableName`: `"articles"`
|
|
@@ -88,7 +111,7 @@ Indexes Markdown files from a directory on disk.
|
|
|
88
111
|
|
|
89
112
|
```ts
|
|
90
113
|
interface IndexerOptions {
|
|
91
|
-
client: Client;
|
|
114
|
+
client: Client | DatabaseAdapter;
|
|
92
115
|
contentPath: string;
|
|
93
116
|
embeddingOptions?: EmbeddingOptions;
|
|
94
117
|
fileExtensions?: string[];
|
|
@@ -185,7 +208,7 @@ Generates a query embedding and performs vector similarity search.
|
|
|
185
208
|
|
|
186
209
|
```ts
|
|
187
210
|
interface SearchOptions {
|
|
188
|
-
client: Client;
|
|
211
|
+
client: Client | DatabaseAdapter;
|
|
189
212
|
query: string;
|
|
190
213
|
limit?: number;
|
|
191
214
|
tableName?: string;
|
|
@@ -244,6 +267,8 @@ const results = await search({ client, query, exact: true });
|
|
|
244
267
|
|
|
245
268
|
This is the guaranteed-exact path: it computes `vector_distance_cos` for every row with a non-`NULL` embedding, sorts by `(distance, id)`, and trims to `limit`. Cost grows linearly with table size, so it is intended for small corpora, correctness checks against the index path, and tables that have no vector index.
|
|
246
269
|
|
|
270
|
+
A backend with no vector index at all takes this path whether or not `exact` is set, because there is no index path for it to fall back from. That is the case for `@tursodatabase/database`; see the [Turso Database backend guide](./TURSO.md).
|
|
271
|
+
|
|
247
272
|
### Missing Vector Index
|
|
248
273
|
|
|
249
274
|
If the target table has no `<tableName>_embedding_idx`, the default path throws an error naming the missing index and pointing at `createTable()` and `exact: true`. libSQL's own message for this case ("failed to parse vector index parameters") says nothing about a missing index, so it is preserved as the thrown error's `cause` rather than surfaced directly.
|
|
@@ -256,7 +281,9 @@ Search never falls back to the exact scan on its own. A silent fallback would tu
|
|
|
256
281
|
|
|
257
282
|
### Requirements
|
|
258
283
|
|
|
259
|
-
`vector_top_k()` and `libsql_vector_idx()` require a libSQL build with native vector support. The peer dependency is `@libsql/client ^0.15.0`; this behavior is verified against `@libsql/client` `0.15.15` with a local `:memory:` database.
|
|
284
|
+
`vector_top_k()` and `libsql_vector_idx()` require a libSQL build with native vector support. The peer dependency is `@libsql/client ^0.15.0 || ^0.17.0`; this behavior is verified against `@libsql/client` `0.15.15` and `0.17.4` with a local `:memory:` database. On both, `vector_top_k()` returns the matched rowids in an `id` column, libSQL's missing-index wording ("failed to parse vector index parameters") is byte-identical, and so is the dimension-mismatch wording that is passed through unchanged — so the index path and the diagnostics above behave the same on either line. The two lines share one embedded engine (`libsql` `0.5.29`), which is why the on-disk format and index semantics do not differ between them.
|
|
285
|
+
|
|
286
|
+
Coverage differs by layer, and is worth stating plainly: the packaged build is smoke-tested against **both** peer arms on every release, which covers table and vector-index creation. The full test suite — including the byte-exact assertions on the messages above — runs against the dev-pinned client, currently `0.17.4`. The no-vector-support case is the one message not reproducible against a local build on either version; it is asserted from a synthesized error rather than a measured one. Remote Turso/libSQL servers must also support vector indexes — that is a property of the server, not the client, and no minimum server version is claimed here beyond that requirement. A deployment without it fails the default path with an error saying so and naming `exact: true` as the remedy. Use `exact: true` against any deployment where vector index support is unavailable or unverified.
|
|
260
287
|
|
|
261
288
|
Result shape:
|
|
262
289
|
|
|
@@ -295,7 +322,7 @@ Returns articles in a specific folder.
|
|
|
295
322
|
|
|
296
323
|
Returns distinct folder names from the index.
|
|
297
324
|
|
|
298
|
-
All retrieval helpers validate `tableName` before executing SQL.
|
|
325
|
+
All retrieval helpers validate `tableName` before executing SQL, and all of them accept either client kind.
|
|
299
326
|
|
|
300
327
|
## Embedding Helpers
|
|
301
328
|
|
package/docs/INDEXING.md
CHANGED
|
@@ -174,6 +174,6 @@ Many projects wire indexing into a dedicated script and call it before their sit
|
|
|
174
174
|
## Runtime Notes
|
|
175
175
|
|
|
176
176
|
- local embeddings may download and cache a model on the first run
|
|
177
|
-
- Node users need `@libsql/client` installed alongside the package
|
|
177
|
+
- Node users need `@libsql/client` installed alongside the package, at `^0.15.0 || ^0.17.0`; the packaged build is smoke-tested against both arms (`0.15.15` and `0.17.4`), which covers table and vector-index creation. `batch()` rollback behaves identically on both at the contract level, though its error text differs — see [Version differences](./TROUBLESHOOTING.md#libsqlclient-version-differences). Upgrading the client is not a prerequisite for upgrading this package. Deno/JSR users are not covered by that range and should pin the client themselves — see [Install](../README.md#install)
|
|
178
178
|
- hosted providers send indexed or queried text to external services
|
|
179
179
|
- the repository validates package build and `deno check`, but indexing still depends on filesystem access
|
package/docs/MIGRATIONS.md
CHANGED
|
@@ -23,6 +23,7 @@ References:
|
|
|
23
23
|
- if dimensions stay the same but provider, model, endpoint, model revision, pooling, normalization, or input formatting changes, fully reindex anyway
|
|
24
24
|
- never mix two embedding spaces in one table
|
|
25
25
|
- prefer a parallel table migration because `indexContent()` replaces the whole target table, so an in-place rebuild leaves no way back to the old vectors
|
|
26
|
+
- upgrading `@libsql/client` is not one of these migrations. The client moves bytes; it does not define the embedding space. Moving between the supported `^0.15.0` and `^0.17.0` lines leaves stored vectors, table widths, and the embedding index untouched and needs no reindex. This is checkable rather than merely inferred: `libsql`, the embedded native engine that owns the on-disk `F32_BLOB` format and the vector index, resolves to `0.5.29` under both client lines — the client bump does not move it. See [`@libsql/client` version differences](./TROUBLESHOOTING.md#libsqlclient-version-differences) for the two client-side behaviors that do change.
|
|
26
27
|
|
|
27
28
|
In practice, this means:
|
|
28
29
|
|
package/docs/README.md
CHANGED
|
@@ -8,6 +8,7 @@ This directory holds the longer-form reference material for `libsql-search`.
|
|
|
8
8
|
- [API reference](./API.md): exported functions, option shapes, and result data
|
|
9
9
|
- [Indexing and operations](./INDEXING.md): content layout, rebuild behavior, and search quality notes
|
|
10
10
|
- [Testing guidance](./TESTING.md): CI-safe mocks and no-live-call policy
|
|
11
|
+
- [Turso Database backend](./TURSO.md): experimental exact-search-only support for the in-process `@tursodatabase/database` client
|
|
11
12
|
- [Troubleshooting](./TROUBLESHOOTING.md): known install and runtime issues
|
|
12
13
|
- [Releasing](./RELEASING.md): maintainer release workflow
|
|
13
14
|
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -38,3 +38,41 @@ Common operational checks:
|
|
|
38
38
|
`createTable()`, `indexContent()`, and `search()`, and re-index if the stored
|
|
39
39
|
vectors are in the wrong space; see the
|
|
40
40
|
[Migration and reindexing guide](./MIGRATIONS.md)
|
|
41
|
+
|
|
42
|
+
## `@libsql/client` Version Differences
|
|
43
|
+
|
|
44
|
+
The peer range is `^0.15.0 || ^0.17.0`, verified against `0.15.15` and `0.17.4`.
|
|
45
|
+
There is no `0.16.x` line upstream. Everything this package depends on behaves
|
|
46
|
+
the same on both — `vector_top_k()` returns rowids in an `id` column, the
|
|
47
|
+
missing-index message this library rewrites ("failed to parse vector index
|
|
48
|
+
parameters") and the dimension-mismatch message it passes through unchanged are
|
|
49
|
+
both byte-identical, and `batch(..., "write")` still rolls the whole rebuild back
|
|
50
|
+
on failure — so upgrading the client is optional and neither direction requires
|
|
51
|
+
re-indexing. (The third message, the no-vector-support case, is not reproducible
|
|
52
|
+
against a local build on either version; see
|
|
53
|
+
[Requirements](./API.md#requirements).)
|
|
54
|
+
|
|
55
|
+
Two client-side differences are visible to callers on `0.17.x`:
|
|
56
|
+
|
|
57
|
+
- the client no longer exports `./package.json`. Reading it by specifier, as in
|
|
58
|
+
`require("@libsql/client/package.json")`, throws
|
|
59
|
+
`ERR_PACKAGE_PATH_NOT_EXPORTED`. Nothing in this package does that; if your own
|
|
60
|
+
tooling did, read the file through a direct `node_modules` path instead
|
|
61
|
+
- **constraint error codes lost the `_UNIQUE` suffix.** A duplicate slug that
|
|
62
|
+
reported `SQLITE_CONSTRAINT_UNIQUE` on `0.15.x` reports the broader
|
|
63
|
+
`SQLITE_CONSTRAINT` on `0.17.x`. This affects every caller on both query paths:
|
|
64
|
+
|
|
65
|
+
| | `0.15.15` | `0.17.4` |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `execute()` | `SQLITE_CONSTRAINT_UNIQUE: UNIQUE constraint failed: …` | `SQLITE_CONSTRAINT: UNIQUE constraint failed: …` |
|
|
68
|
+
| `batch(…, "write")` | `SQLITE_CONSTRAINT_UNIQUE: UNIQUE constraint failed: …` | `SQLITE_CONSTRAINT: SQLITE_CONSTRAINT: UNIQUE constraint failed: …` |
|
|
69
|
+
|
|
70
|
+
Note that the prefix is additionally **doubled on the `batch()` path only** —
|
|
71
|
+
that is the path `indexContent()` uses, so it is what surfaces as the `cause`
|
|
72
|
+
of an `IndexingError` with `phase: "replace"`. Your own `execute()` calls show
|
|
73
|
+
the single prefix. Both are cosmetic: the rollback and the `IndexingError`
|
|
74
|
+
contract are unchanged.
|
|
75
|
+
|
|
76
|
+
A log matcher or alert rule keyed on `SQLITE_CONSTRAINT_UNIQUE` will stop
|
|
77
|
+
matching after the upgrade, on either path. Match on `UNIQUE constraint failed`
|
|
78
|
+
instead — it is the one substring stable across all four cells above.
|
package/docs/TURSO.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Turso Database backend (experimental)
|
|
2
|
+
|
|
3
|
+
`libsql-search` can run against the in-process [`@tursodatabase/database`](https://www.npmjs.com/package/@tursodatabase/database)
|
|
4
|
+
client in addition to `@libsql/client`.
|
|
5
|
+
|
|
6
|
+
**This backend is experimental, and search on it is exact-only.** Read
|
|
7
|
+
[What is different on Turso](#what-is-different-on-turso) before adopting it —
|
|
8
|
+
the difference is architectural, not a rough edge that will be smoothed out by
|
|
9
|
+
an upgrade.
|
|
10
|
+
|
|
11
|
+
`@libsql/client` remains the default and the only backend the main entry point
|
|
12
|
+
knows about. Nothing on this page affects you if you do not import
|
|
13
|
+
`libsql-search/turso`.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
`@tursodatabase/database` is an **optional** peer dependency, declared as
|
|
18
|
+
`^0.7.0 || ^0.8.0`. It is not installed, resolved, or imported unless you ask
|
|
19
|
+
for it. Both lines behave identically on every dimension this adapter depends
|
|
20
|
+
on; the disjunction is a caret-per-minor range for the same reason
|
|
21
|
+
`@libsql/client`'s is.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add libsql-search @tursodatabase/database
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install libsql-search @tursodatabase/database
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
Wrap the connected handle with `tursoAdapter()` and pass the result as `client`
|
|
34
|
+
everywhere you would otherwise pass a libSQL client.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { connect } from "@tursodatabase/database";
|
|
38
|
+
import { tursoAdapter } from "libsql-search/turso";
|
|
39
|
+
import { createTable, indexContent, search } from "libsql-search";
|
|
40
|
+
|
|
41
|
+
// connect() is async. Awaiting it is not optional — tursoAdapter() rejects a
|
|
42
|
+
// pending Promise with a message saying so, because it is the most common
|
|
43
|
+
// first-use mistake.
|
|
44
|
+
const database = await connect("./local.db");
|
|
45
|
+
const client = tursoAdapter(database);
|
|
46
|
+
|
|
47
|
+
await createTable(client, "articles", 384);
|
|
48
|
+
|
|
49
|
+
await indexContent({
|
|
50
|
+
client,
|
|
51
|
+
contentPath: "./content",
|
|
52
|
+
embeddingOptions: { provider: "local" },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const results = await search({
|
|
56
|
+
client,
|
|
57
|
+
query: "how do I deploy my docs site",
|
|
58
|
+
limit: 5,
|
|
59
|
+
embeddingOptions: { provider: "local" },
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Use `":memory:"` instead of a file path for an ephemeral database.
|
|
64
|
+
|
|
65
|
+
`createTable`, `indexContent`, `search`, `getAllArticles`, `getArticleBySlug`,
|
|
66
|
+
`getArticlesByFolder`, and `getFolders` all accept the adapter. The option and
|
|
67
|
+
result shapes — `SearchOptions`, `SearchResult`, `IndexerOptions`,
|
|
68
|
+
`IndexResult` — are identical on both backends.
|
|
69
|
+
|
|
70
|
+
## What is different on Turso
|
|
71
|
+
|
|
72
|
+
### There is no ANN vector index, so search is a full scan
|
|
73
|
+
|
|
74
|
+
Turso Database implements the vector *functions* — `vector()`, `vector32()`,
|
|
75
|
+
`vector_distance_cos()` — but not the approximate-nearest-neighbor index that
|
|
76
|
+
libSQL exposes through `libsql_vector_idx()` and `vector_top_k()`. Attempting
|
|
77
|
+
either produces a parse error and a "no such table: vector_top_k" respectively.
|
|
78
|
+
|
|
79
|
+
Two consequences, both handled for you:
|
|
80
|
+
|
|
81
|
+
- `createTable()` creates the table, the folder index, and the slug index, and
|
|
82
|
+
**skips** the vector index. It does not skip it on `@libsql/client`.
|
|
83
|
+
- `search()` runs the exact full-scan path automatically. You do not pass
|
|
84
|
+
`exact: true`, and passing it changes nothing.
|
|
85
|
+
|
|
86
|
+
**Search cost is therefore linear in the number of indexed rows.** For a
|
|
87
|
+
personal site or a docs set this is generally fine and often faster than an
|
|
88
|
+
index probe. For a large corpus, use `@libsql/client`, which is not affected by
|
|
89
|
+
any of this.
|
|
90
|
+
|
|
91
|
+
This is a design direction rather than an unshipped feature:
|
|
92
|
+
|
|
93
|
+
- [`tursodatabase/turso` #832](https://github.com/tursodatabase/turso/issues/832),
|
|
94
|
+
the DiskANN port, is open, unassigned, and in the Backlog milestone.
|
|
95
|
+
- [`tursodatabase/turso` #3778](https://github.com/tursodatabase/turso/issues/3778)
|
|
96
|
+
was closed as *completed* by shipping SIMD-accelerated **exact** vector search,
|
|
97
|
+
with the maintainer noting that "fast exact search is what many use cases
|
|
98
|
+
actually want".
|
|
99
|
+
- The project's `COMPAT.md` lists the vector functions as supported and has no
|
|
100
|
+
row for `libsql_vector_idx` or `vector_top_k` at all.
|
|
101
|
+
|
|
102
|
+
One thing worth knowing, because it costs people an afternoon: Turso's own
|
|
103
|
+
**AI & Embeddings** documentation page markets `vector_top_k` as a "Turso and
|
|
104
|
+
libSQL" feature without distinguishing the two engines. If you arrived expecting
|
|
105
|
+
it to work on `@tursodatabase/database`, that page is why. It does not.
|
|
106
|
+
|
|
107
|
+
### Index replacement uses a transaction, not a batch
|
|
108
|
+
|
|
109
|
+
`indexContent()` builds every document in memory and then replaces the table
|
|
110
|
+
contents in one atomic write, so a failed rebuild leaves the previous index
|
|
111
|
+
intact. That guarantee holds identically on both backends, but the primitive
|
|
112
|
+
behind it is inverted between them:
|
|
113
|
+
|
|
114
|
+
| | `@libsql/client` | `@tursodatabase/database` |
|
|
115
|
+
| --- | --- | --- |
|
|
116
|
+
| `batch(statements, "write")` | atomic | **not atomic** |
|
|
117
|
+
| `transaction()` | breaks in-memory clients | atomic, but **deferred** — see below |
|
|
118
|
+
|
|
119
|
+
Turso's `batch()` is not transactional: a batch that fails part way through
|
|
120
|
+
leaves the statements before the failure committed. On this backend the
|
|
121
|
+
replacement therefore runs inside an explicit `BEGIN IMMEDIATE` / `COMMIT`, with
|
|
122
|
+
an explicit `ROLLBACK` on failure. If you are extending this library, do not
|
|
123
|
+
"simplify" the Turso path to use `batch()` — it would silently convert a failed
|
|
124
|
+
rebuild into a half-destroyed index.
|
|
125
|
+
|
|
126
|
+
**Nor is `transaction()` the shortcut it looks like.** Turso's `transaction()`
|
|
127
|
+
is better-sqlite3-style: it *returns a wrapped function* rather than executing.
|
|
128
|
+
`await db.transaction(fn)` resolves to that function and never runs `fn` — no
|
|
129
|
+
error, no rows written. That is why this adapter issues `BEGIN IMMEDIATE`
|
|
130
|
+
itself.
|
|
131
|
+
|
|
132
|
+
This one is genuinely dangerous to get wrong, because it fails green. Rewriting
|
|
133
|
+
`executeAtomicWrite()` as `await database.transaction(async () => { ... })`
|
|
134
|
+
throws nothing and **still passes every test in the suite**, including the one
|
|
135
|
+
asserting that a failed rebuild leaves the previous index intact — an index that
|
|
136
|
+
was never touched trivially satisfies "unchanged". The reindex becomes a silent
|
|
137
|
+
no-op in production while CI stays green. `tests/turso-database.test.ts` pins
|
|
138
|
+
the deferred-execution behavior directly for this reason.
|
|
139
|
+
|
|
140
|
+
## Platform support
|
|
141
|
+
|
|
142
|
+
`@tursodatabase/database` ships prebuilt native binaries for these targets only:
|
|
143
|
+
|
|
144
|
+
- `darwin-arm64` (Apple Silicon)
|
|
145
|
+
- `linux-x64-gnu`
|
|
146
|
+
- `linux-arm64-gnu`
|
|
147
|
+
- `win32-x64-msvc`
|
|
148
|
+
|
|
149
|
+
Two gaps follow from that list, and Turso's documentation does not address
|
|
150
|
+
either one, so this is an observation about the published binaries rather than a
|
|
151
|
+
statement of their support policy:
|
|
152
|
+
|
|
153
|
+
- **No musl target.** Alpine-based images are not covered by a prebuilt binary.
|
|
154
|
+
- **No `darwin-x64`.** Intel Macs are not covered either.
|
|
155
|
+
|
|
156
|
+
`@libsql/client` has no such constraint, which is another reason it stays the
|
|
157
|
+
default.
|
|
158
|
+
|
|
159
|
+
The integration suite in `tests/turso-database.test.ts` skips itself — with a
|
|
160
|
+
warning naming the platform and the reason — anywhere the native module fails to
|
|
161
|
+
load, rather than failing the run. The capability-driven behavior it covers
|
|
162
|
+
(skipping the vector index, forcing the exact search path, routing the
|
|
163
|
+
replacement through the atomic write) is additionally covered on every platform
|
|
164
|
+
by `tests/database.test.ts`, which uses a stub adapter and no native code.
|
|
165
|
+
|
|
166
|
+
Tests never require Turso Cloud credentials, a URL, or a token. Everything runs
|
|
167
|
+
against an in-process `:memory:` database.
|
|
168
|
+
|
|
169
|
+
## Deno and JSR
|
|
170
|
+
|
|
171
|
+
The JSR package exports only the main entry point. `libsql-search/turso` is not
|
|
172
|
+
available there.
|
|
173
|
+
|
|
174
|
+
That is a scope decision, not a technical limit. `src/turso.ts` imports nothing
|
|
175
|
+
from `@tursodatabase/database` — it accepts the handle through a structural
|
|
176
|
+
`TursoDatabase` interface declared in this package — so exposing it on JSR would
|
|
177
|
+
not force Deno to resolve a Node-native package. It is left off because the
|
|
178
|
+
backend is experimental and the native binaries target Node platforms. The entry
|
|
179
|
+
point is still type-checked by `deno task check` so the claim above stays true.
|
|
180
|
+
|
|
181
|
+
## Type compatibility
|
|
182
|
+
|
|
183
|
+
`IndexerOptions["client"]` and `SearchOptions["client"]` accept either a
|
|
184
|
+
`@libsql/client` `Client` or an adapter. Existing `Client`-typed code compiles
|
|
185
|
+
unchanged, and the main entry point exports no new symbol and references no
|
|
186
|
+
Turso type.
|
|
187
|
+
|
|
188
|
+
`tursoAdapter()` returns a `DatabaseAdapter`, and that type is exported from
|
|
189
|
+
`libsql-search/turso` so you can name it:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { tursoAdapter, type DatabaseAdapter } from "libsql-search/turso";
|
|
193
|
+
|
|
194
|
+
let client: DatabaseAdapter;
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
It is exported from the subpath only. The main entry point does not export it,
|
|
198
|
+
so `libsql-search`'s public surface is unchanged for everyone else. An export
|
|
199
|
+
modifier is not a structural member, so the asymmetry does not affect
|
|
200
|
+
assignability between the two entry points.
|
|
201
|
+
|
|
202
|
+
`libsql-search` and `libsql-search/turso` are bundled separately, so each ships
|
|
203
|
+
its own structural copy of the adapter declaration. `pnpm check:dist-types`
|
|
204
|
+
compiles an adapter from the subpath against the client type from the main entry
|
|
205
|
+
to prove the two stay interchangeable, and runs as part of
|
|
206
|
+
`pnpm validate:package`.
|
|
207
|
+
|
|
208
|
+
## If you are extending this adapter
|
|
209
|
+
|
|
210
|
+
Three things in `src/turso.ts` look like noise and are not. Each has a
|
|
211
|
+
regression test; none of them fails loudly at runtime if removed.
|
|
212
|
+
|
|
213
|
+
**Prepared statements must be closed.** A statement holds native memory that the
|
|
214
|
+
garbage collector cannot reclaim, because it is not JavaScript heap. Every
|
|
215
|
+
`prepare()` is released with `close()` in a `finally`. Measured through the
|
|
216
|
+
built bundle, 60 000 `executeQuery()` calls on one handle grow RSS by ~570 MB
|
|
217
|
+
without the close and ~150 MB with it. `search()` issues exactly one query, so
|
|
218
|
+
an SSR site calling it per request is the case that turns this from untidy into
|
|
219
|
+
an OOM. Inside `executeAtomicWrite()` the cached statements are released only
|
|
220
|
+
*after* `COMMIT` or `ROLLBACK` — a statement stays bound to the transaction
|
|
221
|
+
while it is open.
|
|
222
|
+
|
|
223
|
+
**`BEGIN IMMEDIATE` sits outside the `try`.** If it were inside, a `BEGIN` that
|
|
224
|
+
fails because another rebuild already holds the write lock would fall into the
|
|
225
|
+
`catch` and issue a `ROLLBACK`, ending the *other* call's in-flight transaction
|
|
226
|
+
and destroying a good rebuild that was about to commit. Failing to start a
|
|
227
|
+
transaction must never end one.
|
|
228
|
+
|
|
229
|
+
**Do not reach for `batch()` or `transaction()`.** Both are covered above; both
|
|
230
|
+
fail silently rather than loudly.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "libsql-search",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Semantic search for static sites using libSQL/Turso with multi-provider embeddings",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.34.5",
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.esm.js",
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./turso": {
|
|
16
|
+
"types": "./dist/turso.d.ts",
|
|
17
|
+
"import": "./dist/turso.esm.js",
|
|
18
|
+
"require": "./dist/turso.cjs"
|
|
14
19
|
}
|
|
15
20
|
},
|
|
16
21
|
"files": [
|
|
@@ -29,9 +34,10 @@
|
|
|
29
34
|
"test:coverage": "vitest run --coverage",
|
|
30
35
|
"test:release-plan": "node --test ./scripts/plan-release.node-test.mjs",
|
|
31
36
|
"check:deno": "deno task check",
|
|
37
|
+
"check:dist-types": "node ./scripts/check-dist-types.mjs",
|
|
32
38
|
"smoke:package": "node ./scripts/smoke-package.mjs",
|
|
33
39
|
"validate:release-version": "node ./scripts/validate-release-version.mjs",
|
|
34
|
-
"validate:package": "tsc --noEmit && rollup -c && rollup -c rollup.dts.config.js && node ./scripts/smoke-package.mjs",
|
|
40
|
+
"validate:package": "tsc --noEmit && rollup -c && rollup -c rollup.dts.config.js && node ./scripts/check-dist-types.mjs && node ./scripts/smoke-package.mjs",
|
|
35
41
|
"validate:release": "pnpm audit --audit-level moderate && pnpm test:release-plan && pnpm test:coverage && pnpm validate:package && pnpm check:deno",
|
|
36
42
|
"prepublishOnly": "pnpm validate:release"
|
|
37
43
|
},
|
|
@@ -60,16 +66,23 @@
|
|
|
60
66
|
"node": ">=22.12.0"
|
|
61
67
|
},
|
|
62
68
|
"peerDependencies": {
|
|
63
|
-
"@libsql/client": "^0.15.0"
|
|
69
|
+
"@libsql/client": "^0.15.0 || ^0.17.0",
|
|
70
|
+
"@tursodatabase/database": "^0.7.0 || ^0.8.0"
|
|
71
|
+
},
|
|
72
|
+
"peerDependenciesMeta": {
|
|
73
|
+
"@tursodatabase/database": {
|
|
74
|
+
"optional": true
|
|
75
|
+
}
|
|
64
76
|
},
|
|
65
77
|
"dependencies": {
|
|
66
78
|
"@huggingface/transformers": "4.2.0",
|
|
67
79
|
"gray-matter": "^4.0.3"
|
|
68
80
|
},
|
|
69
81
|
"devDependencies": {
|
|
70
|
-
"@libsql/client": "^0.
|
|
82
|
+
"@libsql/client": "^0.17.4",
|
|
71
83
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
72
84
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
85
|
+
"@tursodatabase/database": "^0.7.2",
|
|
73
86
|
"@types/node": "^24.13.3",
|
|
74
87
|
"@vitest/coverage-v8": "4.1.11",
|
|
75
88
|
"@vitest/ui": "^4.1.11",
|