@statewalker/db-sqlite-browser 0.1.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/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/index.d.mts +19 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +40 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
- package/src/browser-sqlite-db.browser.test.ts +159 -0
- package/src/browser-sqlite-db.test.ts +14 -0
- package/src/browser-sqlite-db.ts +54 -0
- package/src/index.ts +3 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mikhail Kotelnikov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @statewalker/db-sqlite-browser
|
|
2
|
+
|
|
3
|
+
libSQL/SQLite WASM driver implementing `@statewalker/db-api` for browser environments,
|
|
4
|
+
backed by `@libsql/libsql-wasm-experimental` (FTS5 + vector search compiled to WebAssembly).
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pnpm add @statewalker/db-sqlite-browser
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { newBrowserSqliteDb } from "@statewalker/db-sqlite-browser";
|
|
16
|
+
|
|
17
|
+
// In-memory (omit `path`) or file-backed; `wasmUrl` locates sqlite3.wasm (default "/sqlite3.wasm").
|
|
18
|
+
const db = await newBrowserSqliteDb({ path: "local.db" });
|
|
19
|
+
await db.exec("CREATE TABLE t (x INTEGER)");
|
|
20
|
+
await db.query("SELECT * FROM t");
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## API
|
|
24
|
+
|
|
25
|
+
- `newBrowserSqliteDb(options?)` — initializes the libSQL-WASM engine and returns a `Db`.
|
|
26
|
+
`options.path` selects file-backed vs in-memory; `options.wasmUrl` locates the `sqlite3.wasm` asset.
|
|
27
|
+
|
|
28
|
+
## Related
|
|
29
|
+
|
|
30
|
+
- `@statewalker/db-api` — interface contract.
|
|
31
|
+
- `@statewalker/db-sqlite-node` — Node-side counterpart.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Db, Db as Db$1, DbEntry, DbOptions, DbOptions as DbOptions$1 } from "@statewalker/db-api";
|
|
2
|
+
//#region src/browser-sqlite-db.d.ts
|
|
3
|
+
type BrowserDbOptions = DbOptions$1 & {
|
|
4
|
+
/** URL to the sqlite3.wasm file. Defaults to "/sqlite3.wasm". */
|
|
5
|
+
wasmUrl?: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Create a libSQL WASM-backed {@link Db} in the browser.
|
|
9
|
+
*
|
|
10
|
+
* Uses `@libsql/libsql-wasm-experimental` which provides the full libSQL
|
|
11
|
+
* engine (FTS5, vector search) compiled to WebAssembly.
|
|
12
|
+
*
|
|
13
|
+
* @param options.path File path for persistent storage. Omit for in-memory.
|
|
14
|
+
* @param options.wasmUrl URL to the sqlite3.wasm file. Defaults to "/sqlite3.wasm".
|
|
15
|
+
*/
|
|
16
|
+
declare function newBrowserSqliteDb(options?: BrowserDbOptions): Promise<Db$1>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { type BrowserDbOptions, type Db, type DbEntry, type DbOptions, newBrowserSqliteDb };
|
|
19
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/browser-sqlite-db.ts"],"mappings":";;KAIY,mBAAmB;;EAE7B;;;;;;;;;;;iBAuBoB,mBAAmB,UAAU,mBAAmB,QAAQ"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import sqlite3InitModule from "@libsql/libsql-wasm-experimental";
|
|
2
|
+
//#region src/browser-sqlite-db.ts
|
|
3
|
+
let sqlite3Promise;
|
|
4
|
+
function getSqlite3(wasmUrl) {
|
|
5
|
+
if (!sqlite3Promise) sqlite3Promise = sqlite3InitModule({ locateFile: (file) => file.endsWith(".wasm") ? wasmUrl : file });
|
|
6
|
+
return sqlite3Promise;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create a libSQL WASM-backed {@link Db} in the browser.
|
|
10
|
+
*
|
|
11
|
+
* Uses `@libsql/libsql-wasm-experimental` which provides the full libSQL
|
|
12
|
+
* engine (FTS5, vector search) compiled to WebAssembly.
|
|
13
|
+
*
|
|
14
|
+
* @param options.path File path for persistent storage. Omit for in-memory.
|
|
15
|
+
* @param options.wasmUrl URL to the sqlite3.wasm file. Defaults to "/sqlite3.wasm".
|
|
16
|
+
*/
|
|
17
|
+
async function newBrowserSqliteDb(options) {
|
|
18
|
+
const sqlite3 = await getSqlite3(options?.wasmUrl ?? "/sqlite3.wasm");
|
|
19
|
+
const dbPath = options?.path ?? ":memory:";
|
|
20
|
+
const db = new sqlite3.oo1.DB(dbPath, "c");
|
|
21
|
+
return {
|
|
22
|
+
async query(sql, params) {
|
|
23
|
+
return db.exec(sql, {
|
|
24
|
+
returnValue: "resultRows",
|
|
25
|
+
rowMode: "object",
|
|
26
|
+
bind: params
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
async exec(sql) {
|
|
30
|
+
db.exec(sql);
|
|
31
|
+
},
|
|
32
|
+
async close() {
|
|
33
|
+
db.close();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { newBrowserSqliteDb };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/browser-sqlite-db.ts"],"sourcesContent":["import type { Database, Sqlite3Static } from \"@libsql/libsql-wasm-experimental\";\nimport sqlite3InitModule from \"@libsql/libsql-wasm-experimental\";\nimport type { Db, DbEntry, DbOptions } from \"@statewalker/db-api\";\n\nexport type BrowserDbOptions = DbOptions & {\n /** URL to the sqlite3.wasm file. Defaults to \"/sqlite3.wasm\". */\n wasmUrl?: string;\n};\n\nlet sqlite3Promise: Promise<Sqlite3Static> | undefined;\n\nfunction getSqlite3(wasmUrl: string): Promise<Sqlite3Static> {\n if (!sqlite3Promise) {\n sqlite3Promise = sqlite3InitModule({\n locateFile: (file: string) => (file.endsWith(\".wasm\") ? wasmUrl : file),\n });\n }\n return sqlite3Promise;\n}\n\n/**\n * Create a libSQL WASM-backed {@link Db} in the browser.\n *\n * Uses `@libsql/libsql-wasm-experimental` which provides the full libSQL\n * engine (FTS5, vector search) compiled to WebAssembly.\n *\n * @param options.path File path for persistent storage. Omit for in-memory.\n * @param options.wasmUrl URL to the sqlite3.wasm file. Defaults to \"/sqlite3.wasm\".\n */\nexport async function newBrowserSqliteDb(options?: BrowserDbOptions): Promise<Db> {\n const wasmUrl = options?.wasmUrl ?? \"/sqlite3.wasm\";\n const sqlite3 = await getSqlite3(wasmUrl);\n const dbPath = options?.path ?? \":memory:\";\n const db: Database = new sqlite3.oo1.DB(dbPath, \"c\");\n\n return {\n async query<T = DbEntry>(sql: string, params?: unknown[]): Promise<T[]> {\n const rows = db.exec(sql, {\n returnValue: \"resultRows\",\n rowMode: \"object\",\n bind: params as never[],\n });\n return rows as unknown as T[];\n },\n\n async exec(sql: string): Promise<void> {\n db.exec(sql);\n },\n\n async close(): Promise<void> {\n db.close();\n },\n };\n}\n"],"mappings":";;AASA,IAAI;AAEJ,SAAS,WAAW,SAAyC;CAC3D,IAAI,CAAC,gBACH,iBAAiB,kBAAkB,EACjC,aAAa,SAAkB,KAAK,SAAS,OAAO,IAAI,UAAU,KACpE,CAAC;CAEH,OAAO;AACT;;;;;;;;;;AAWA,eAAsB,mBAAmB,SAAyC;CAEhF,MAAM,UAAU,MAAM,WADN,SAAS,WAAW,eACI;CACxC,MAAM,SAAS,SAAS,QAAQ;CAChC,MAAM,KAAe,IAAI,QAAQ,IAAI,GAAG,QAAQ,GAAG;CAEnD,OAAO;EACL,MAAM,MAAmB,KAAa,QAAkC;GAMtE,OALa,GAAG,KAAK,KAAK;IACxB,aAAa;IACb,SAAS;IACT,MAAM;GACR,CACU;EACZ;EAEA,MAAM,KAAK,KAA4B;GACrC,GAAG,KAAK,GAAG;EACb;EAEA,MAAM,QAAuB;GAC3B,GAAG,MAAM;EACX;CACF;AACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@statewalker/db-sqlite-browser",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "libSQL/SQLite WASM driver for @statewalker/db-api, targeting browser environments.",
|
|
7
|
+
"homepage": "https://github.com/statewalker/statewalker-db",
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "Mikhail Kotelnikov",
|
|
10
|
+
"email": "mikhail.kotelnikov@gmail.com"
|
|
11
|
+
},
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+ssh://git@github.com/statewalker/statewalker-db.git"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./src/index.ts"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@libsql/libsql-wasm-experimental": "^0.0.3",
|
|
26
|
+
"@statewalker/db-api": "0.1.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@playwright/test": "^1.62.1",
|
|
30
|
+
"@vitest/browser": "^4.1.10",
|
|
31
|
+
"@vitest/browser-playwright": "^4.1.10",
|
|
32
|
+
"playwright": "^1.62.1",
|
|
33
|
+
"rimraf": "^6.1.3",
|
|
34
|
+
"tsdown": "^0.22.14",
|
|
35
|
+
"typescript": "^7.0.2",
|
|
36
|
+
"vitest": "^4.1.10",
|
|
37
|
+
"@statewalker/db-tests": "0.1.1"
|
|
38
|
+
},
|
|
39
|
+
"sideEffects": false,
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsdown",
|
|
45
|
+
"dev": "tsdown --watch",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"test:browser": "vitest run --config vitest.browser.config.ts",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"clean": "rimraf dist",
|
|
51
|
+
"lint": "biome check --write .",
|
|
52
|
+
"format": "biome format --write ."
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { Db } from "@statewalker/db-api";
|
|
2
|
+
import { runDbConformance } from "@statewalker/db-tests";
|
|
3
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { newBrowserSqliteDb } from "./browser-sqlite-db.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The libSQL-WASM browser adapter, exercised in a REAL browser (Vitest browser
|
|
8
|
+
* mode + Playwright/Chromium). Launched only by `vitest.browser.config.ts` (the
|
|
9
|
+
* `test:browser` script) — the Node config excludes `*.browser.test.ts`.
|
|
10
|
+
*
|
|
11
|
+
* The engine is served from `/sqlite3.wasm` by the config's `publicDir`, so the
|
|
12
|
+
* adapter's default `wasmUrl` resolves. Everything runs in-memory; the node-only
|
|
13
|
+
* file-persistence scenario is skipped via `skipFilePersistence`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Shared contract: CRUD, parameter binding, injection safety, cardinality,
|
|
17
|
+
// close, error paths. File persistence is node-only, hence skipped here.
|
|
18
|
+
runDbConformance(() => newBrowserSqliteDb(), {
|
|
19
|
+
// libsql dialect: `?` placeholder; the spread normalizer drops libsql Row's
|
|
20
|
+
// non-enumerable positional keys so a plain `toEqual` matches.
|
|
21
|
+
placeholder: "?",
|
|
22
|
+
normalizeRow: (row) => ({ ...row }),
|
|
23
|
+
skipFilePersistence: true,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Dialect-specific browser behavior the shared suite does not cover: libSQL's
|
|
27
|
+
// FTS5 full-text search and native vector index.
|
|
28
|
+
describe("newBrowserSqliteDb", () => {
|
|
29
|
+
let db: Db;
|
|
30
|
+
|
|
31
|
+
afterEach(async () => {
|
|
32
|
+
if (db) {
|
|
33
|
+
await db.close();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("FTS5", () => {
|
|
38
|
+
it("returns empty results for unmatched search terms", async () => {
|
|
39
|
+
db = await newBrowserSqliteDb();
|
|
40
|
+
await db.exec("CREATE VIRTUAL TABLE docs2_fts USING fts5(content)");
|
|
41
|
+
await db.exec(
|
|
42
|
+
"INSERT INTO docs2_fts (rowid, content) VALUES (1, 'hello world'), (2, 'goodbye world')",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const rows = await db.query<{ content: string }>(
|
|
46
|
+
"SELECT content FROM docs2_fts WHERE docs2_fts MATCH 'nonexistent'",
|
|
47
|
+
);
|
|
48
|
+
expect(rows).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("searches across multiple text columns", async () => {
|
|
52
|
+
db = await newBrowserSqliteDb();
|
|
53
|
+
await db.exec("CREATE VIRTUAL TABLE articles_fts USING fts5(title, body, tokenize='porter')");
|
|
54
|
+
await db.exec(`
|
|
55
|
+
INSERT INTO articles_fts (rowid, title, body) VALUES
|
|
56
|
+
(1, 'Travel Guide', 'Visit the beautiful lakes of Switzerland'),
|
|
57
|
+
(2, 'Cooking Tips', 'How to make a perfect lake trout'),
|
|
58
|
+
(3, 'Programming', 'Learn about SQL databases')
|
|
59
|
+
`);
|
|
60
|
+
|
|
61
|
+
const rows = await db.query<{ rowid: number; title: string; score: number }>(`
|
|
62
|
+
SELECT rowid, title, bm25(articles_fts) AS score
|
|
63
|
+
FROM articles_fts
|
|
64
|
+
WHERE articles_fts MATCH 'lake'
|
|
65
|
+
ORDER BY score
|
|
66
|
+
`);
|
|
67
|
+
expect(rows).toHaveLength(2);
|
|
68
|
+
const titles = rows.map((r) => r.title);
|
|
69
|
+
expect(titles).toContain("Travel Guide");
|
|
70
|
+
expect(titles).toContain("Cooking Tips");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("creates an FTS5 index and performs full-text search", async () => {
|
|
74
|
+
db = await newBrowserSqliteDb();
|
|
75
|
+
await db.exec("CREATE VIRTUAL TABLE docs_fts USING fts5(content)");
|
|
76
|
+
await db.exec(`
|
|
77
|
+
INSERT INTO docs_fts (rowid, content) VALUES
|
|
78
|
+
(1, 'the quick brown fox jumps over the lazy dog'),
|
|
79
|
+
(2, 'a lazy cat sleeps on the mat'),
|
|
80
|
+
(3, 'the fox and the hound are friends')
|
|
81
|
+
`);
|
|
82
|
+
|
|
83
|
+
const rows = await db.query<{ rowid: number; content: string; score: number }>(`
|
|
84
|
+
SELECT rowid, content, bm25(docs_fts) AS score
|
|
85
|
+
FROM docs_fts
|
|
86
|
+
WHERE docs_fts MATCH 'fox'
|
|
87
|
+
ORDER BY score
|
|
88
|
+
`);
|
|
89
|
+
|
|
90
|
+
expect(rows.length).toBeGreaterThanOrEqual(1);
|
|
91
|
+
const rowids = rows.map((r) => r.rowid);
|
|
92
|
+
expect(rowids).toContain(1);
|
|
93
|
+
expect(rowids).toContain(3);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("vector search", () => {
|
|
98
|
+
it("creates index and performs vector similarity search", async () => {
|
|
99
|
+
db = await newBrowserSqliteDb();
|
|
100
|
+
|
|
101
|
+
await db.exec("CREATE TABLE embeddings (id INTEGER PRIMARY KEY, vec F32_BLOB(3))");
|
|
102
|
+
await db.exec("CREATE INDEX vec_idx ON embeddings (libsql_vector_idx(vec))");
|
|
103
|
+
await db.exec(`
|
|
104
|
+
INSERT INTO embeddings VALUES
|
|
105
|
+
(1, vector32('[1.0, 0.0, 0.0]')),
|
|
106
|
+
(2, vector32('[0.0, 1.0, 0.0]')),
|
|
107
|
+
(3, vector32('[0.0, 0.0, 1.0]'))
|
|
108
|
+
`);
|
|
109
|
+
|
|
110
|
+
const rows = await db.query<{ id: number }>(
|
|
111
|
+
"SELECT e.id FROM vector_top_k('vec_idx', '[1.0, 0.1, 0.0]', 1) AS v JOIN embeddings e ON e.rowid = v.id",
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(rows).toHaveLength(1);
|
|
115
|
+
expect(rows[0]?.id).toBe(1);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("returns k nearest neighbors ordered by distance", async () => {
|
|
119
|
+
db = await newBrowserSqliteDb();
|
|
120
|
+
|
|
121
|
+
await db.exec("CREATE TABLE vectors (id INTEGER PRIMARY KEY, vec F32_BLOB(3))");
|
|
122
|
+
await db.exec("CREATE INDEX vec_idx2 ON vectors (libsql_vector_idx(vec))");
|
|
123
|
+
await db.exec(`
|
|
124
|
+
INSERT INTO vectors VALUES
|
|
125
|
+
(1, vector32('[1.0, 0.0, 0.0]')),
|
|
126
|
+
(2, vector32('[0.9, 0.1, 0.0]')),
|
|
127
|
+
(3, vector32('[0.0, 1.0, 0.0]')),
|
|
128
|
+
(4, vector32('[0.0, 0.0, 1.0]'))
|
|
129
|
+
`);
|
|
130
|
+
|
|
131
|
+
const rows = await db.query<{ id: number }>(
|
|
132
|
+
"SELECT e.id FROM vector_top_k('vec_idx2', '[1.0, 0.0, 0.0]', 2) AS v JOIN vectors e ON e.rowid = v.id",
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
expect(rows).toHaveLength(2);
|
|
136
|
+
const ids = rows.map((r) => r.id);
|
|
137
|
+
expect(ids).toContain(1);
|
|
138
|
+
expect(ids).toContain(2);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("works without index (brute-force scan)", async () => {
|
|
142
|
+
db = await newBrowserSqliteDb();
|
|
143
|
+
|
|
144
|
+
await db.exec("CREATE TABLE vecs_no_idx (id INTEGER PRIMARY KEY, vec F32_BLOB(3))");
|
|
145
|
+
await db.exec(`
|
|
146
|
+
INSERT INTO vecs_no_idx VALUES
|
|
147
|
+
(1, vector32('[1.0, 0.0, 0.0]')),
|
|
148
|
+
(2, vector32('[0.0, 1.0, 0.0]'))
|
|
149
|
+
`);
|
|
150
|
+
|
|
151
|
+
const rows = await db.query<{ id: number }>(
|
|
152
|
+
"SELECT id FROM vecs_no_idx ORDER BY vector_distance_cos(vec, vector32('[0.0, 0.9, 0.1]')) LIMIT 1",
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(rows).toHaveLength(1);
|
|
156
|
+
expect(rows[0]?.id).toBe(2);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Node-safe smoke test. The real browser coverage (shared conformance suite +
|
|
5
|
+
* FTS5/vector) lives in `browser-sqlite-db.browser.test.ts`, run via Vitest
|
|
6
|
+
* browser mode (`pnpm test:browser`); this file only checks the module loads
|
|
7
|
+
* and exports its factory, since libSQL-WASM cannot instantiate under Node.
|
|
8
|
+
*/
|
|
9
|
+
describe("browser-sqlite-db module", () => {
|
|
10
|
+
it("exports newBrowserSqliteDb function", async () => {
|
|
11
|
+
const mod = await import("./browser-sqlite-db.js");
|
|
12
|
+
expect(typeof mod.newBrowserSqliteDb).toBe("function");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Database, Sqlite3Static } from "@libsql/libsql-wasm-experimental";
|
|
2
|
+
import sqlite3InitModule from "@libsql/libsql-wasm-experimental";
|
|
3
|
+
import type { Db, DbEntry, DbOptions } from "@statewalker/db-api";
|
|
4
|
+
|
|
5
|
+
export type BrowserDbOptions = DbOptions & {
|
|
6
|
+
/** URL to the sqlite3.wasm file. Defaults to "/sqlite3.wasm". */
|
|
7
|
+
wasmUrl?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let sqlite3Promise: Promise<Sqlite3Static> | undefined;
|
|
11
|
+
|
|
12
|
+
function getSqlite3(wasmUrl: string): Promise<Sqlite3Static> {
|
|
13
|
+
if (!sqlite3Promise) {
|
|
14
|
+
sqlite3Promise = sqlite3InitModule({
|
|
15
|
+
locateFile: (file: string) => (file.endsWith(".wasm") ? wasmUrl : file),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return sqlite3Promise;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a libSQL WASM-backed {@link Db} in the browser.
|
|
23
|
+
*
|
|
24
|
+
* Uses `@libsql/libsql-wasm-experimental` which provides the full libSQL
|
|
25
|
+
* engine (FTS5, vector search) compiled to WebAssembly.
|
|
26
|
+
*
|
|
27
|
+
* @param options.path File path for persistent storage. Omit for in-memory.
|
|
28
|
+
* @param options.wasmUrl URL to the sqlite3.wasm file. Defaults to "/sqlite3.wasm".
|
|
29
|
+
*/
|
|
30
|
+
export async function newBrowserSqliteDb(options?: BrowserDbOptions): Promise<Db> {
|
|
31
|
+
const wasmUrl = options?.wasmUrl ?? "/sqlite3.wasm";
|
|
32
|
+
const sqlite3 = await getSqlite3(wasmUrl);
|
|
33
|
+
const dbPath = options?.path ?? ":memory:";
|
|
34
|
+
const db: Database = new sqlite3.oo1.DB(dbPath, "c");
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
async query<T = DbEntry>(sql: string, params?: unknown[]): Promise<T[]> {
|
|
38
|
+
const rows = db.exec(sql, {
|
|
39
|
+
returnValue: "resultRows",
|
|
40
|
+
rowMode: "object",
|
|
41
|
+
bind: params as never[],
|
|
42
|
+
});
|
|
43
|
+
return rows as unknown as T[];
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
async exec(sql: string): Promise<void> {
|
|
47
|
+
db.exec(sql);
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async close(): Promise<void> {
|
|
51
|
+
db.close();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
package/src/index.ts
ADDED