@buley/dash 0.0.30 → 2.1.4
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 +82 -65
- package/dist/src/engine/ai.d.ts +10 -0
- package/dist/src/engine/ai.js +47 -0
- package/dist/src/engine/sqlite.d.ts +31 -0
- package/dist/src/engine/sqlite.js +239 -0
- package/dist/src/engine/vec_extension.d.ts +5 -0
- package/dist/src/engine/vec_extension.js +10 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +3 -0
- package/dist/src/mcp/server.d.ts +8 -0
- package/dist/src/mcp/server.js +87 -0
- package/dist/src/reactivity/signal.d.ts +3 -0
- package/dist/src/reactivity/signal.js +31 -0
- package/dist/src/sync/backup.d.ts +12 -0
- package/dist/src/sync/backup.js +44 -0
- package/dist/src/sync/connection.d.ts +16 -0
- package/dist/src/sync/connection.js +29 -0
- package/dist/src/sync/provider.d.ts +11 -0
- package/dist/src/sync/provider.js +67 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +33 -31
- package/.coveralls.yml +0 -1
- package/.github/workflows/opencommit.yml +0 -33
- package/.github/workflows/testing.yml +0 -20
- package/.gitmodules +0 -0
- package/behaviors/cache.dev.js +0 -282
- package/behaviors/changes.dev.js +0 -337
- package/behaviors/collect.dev.js +0 -40
- package/behaviors/examples/async.dev.js +0 -17
- package/behaviors/firebase.dev.js +0 -283
- package/behaviors/live.dev.js +0 -67
- package/behaviors/map.dev.js +0 -54
- package/behaviors/mapreduce.dev.js +0 -68
- package/behaviors/match.dev.js +0 -66
- package/behaviors/patch.dev.js +0 -69
- package/behaviors/rest.dev.js +0 -340
- package/behaviors/shorthand.dev.js +0 -59
- package/behaviors/stats.dev.js +0 -672
- package/dist/behaviors/index.js +0 -142
- package/dist/database/index.js +0 -76
- package/dist/databases/index.js +0 -121
- package/dist/entry/index.js +0 -166
- package/dist/index.js +0 -93
- package/dist/indexes/index.js +0 -153
- package/dist/store/index.js +0 -97
- package/dist/stores/index.js +0 -90
- package/dist/utilities/index.js +0 -174
- package/documentation/database/closing.md +0 -3
- package/documentation/database/getting.md +0 -1
- package/documentation/database/opening.md +0 -5
- package/documentation/database/removing.md +0 -3
- package/documentation/databases.md +0 -21
- package/documentation/entries.md +0 -13
- package/documentation/entry/adding.md +0 -1
- package/documentation/entry/getting.md +0 -4
- package/documentation/entry/putting.md +0 -0
- package/documentation/entry/removing.md +0 -3
- package/documentation/entry/updating.md +0 -0
- package/documentation/general/security.md +0 -10
- package/documentation/general/transaction/requests.md +0 -3
- package/documentation/general/transactions.md +0 -5
- package/documentation/index/creating.md +0 -1
- package/documentation/index/getting.md +0 -1
- package/documentation/index/iterating.md +0 -1
- package/documentation/index/removing.md +0 -1
- package/documentation/indexes.md +0 -3
- package/documentation/key/cursors.md +0 -5
- package/documentation/key/range/bounds.md +0 -11
- package/documentation/key/range/direction.md +0 -1
- package/documentation/key/ranges.md +0 -3
- package/documentation/keys.md +0 -12
- package/documentation/objectstore/clearing.md +0 -1
- package/documentation/objectstore/creating.md +0 -1
- package/documentation/objectstore/getting.md +0 -1
- package/documentation/objectstore/iteration.md +0 -1
- package/documentation/objectstore/removing.md +0 -1
- package/documentation/overview.md +0 -5
- package/documentation/stores.md +0 -13
- package/jest.config.js +0 -12
- package/src/behaviors/index.ts +0 -140
- package/src/database/index.ts +0 -81
- package/src/databases/index.ts +0 -127
- package/src/entry/index.ts +0 -183
- package/src/index/index.ts +0 -61
- package/src/index.ts +0 -96
- package/src/indexes/index.ts +0 -151
- package/src/store/index.ts +0 -102
- package/src/stores/index.ts +0 -90
- package/src/utilities/index.ts +0 -349
- package/tests/behaviors/behaviors.spec.ts +0 -123
- package/tests/database/database.spec.ts +0 -177
- package/tests/databases/databases.spec.ts +0 -199
- package/tests/entry/entry.spec.ts +0 -252
- package/tests/index/index.spec.ts +0 -94
- package/tests/indexes/indexes.spec.ts +0 -203
- package/tests/store/store.spec.ts +0 -164
- package/tests/stores/stores.spec.ts +0 -148
- package/tests/utilities/clone.spec.ts +0 -48
- package/tests/utilities/cloneError.spec.ts +0 -33
- package/tests/utilities/contains.spec.ts +0 -28
- package/tests/utilities/exists.spec.ts +0 -21
- package/tests/utilities/is.spec.ts +0 -37
- package/tests/utilities/isArray.spec.ts +0 -21
- package/tests/utilities/isBoolean.spec.ts +0 -23
- package/tests/utilities/isEmpty.spec.ts +0 -45
- package/tests/utilities/isFunction.spec.ts +0 -30
- package/tests/utilities/isNumber.spec.ts +0 -29
- package/tests/utilities/isObject.spec.ts +0 -42
- package/tests/utilities/isRegEx.spec.ts +0 -33
- package/tests/utilities/isString.spec.ts +0 -25
- package/tests/utilities/isnt.spec.ts +0 -50
- package/tests/utilities/randomId.spec.ts +0 -39
- package/tests/utilities/safeApply.spec.ts +0 -49
- package/tests/utilities/safeEach.spec.ts +0 -38
- package/tests/utilities/safeIterate.spec.ts +0 -47
- package/tsconfig.json +0 -16
package/README.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
# Dash
|
|
1
|
+
# Dash 2.0
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**The Local-First, AI-Native Database for the Modern Web.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Dash 2.0 isn't just a database; it's a complete data engine for building high-performance, intelligent web applications. It brings server-grade power to the client, enabling apps that feel instant, work offline, and understand your users.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
7
|
+
## Why Dash?
|
|
8
|
+
|
|
9
|
+
- ⚡ **Unmatched Speed**: Powered by SQLite WASM and OPFS, Dash delivers native-like I/O performance directly in the browser.
|
|
10
|
+
- 🧠 **AI-Ready**: Built-in vector search means you can find data by _meaning_, not just keywords. No external APIs required.
|
|
11
|
+
- 🔄 **Seamless Sync**: Real-time, peer-to-peer synchronization and encrypted cloud backups work out of the box. Collaborative apps are now trivial.
|
|
12
|
+
- 🎮 **Graphics Grade**: Zero-copy bindings allow you to pipe data directly into 3D engines like Three.js for massive visualizations.
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
@@ -16,83 +17,99 @@ Dash is a simple, lightweight wrapper around the IndexedDB API. It provides a pr
|
|
|
16
17
|
npm install @buley/dash
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
})
|
|
37
|
-
.then(ctx => {
|
|
38
|
-
// Add an entry to the store
|
|
39
|
-
return dash.entry.add({
|
|
40
|
-
...ctx,
|
|
41
|
-
data: { name: 'John Doe', age: 30 }
|
|
42
|
-
});
|
|
43
|
-
})
|
|
44
|
-
.then(ctx => {
|
|
45
|
-
console.log('Entry added successfully:', ctx.entry);
|
|
46
|
-
})
|
|
47
|
-
.catch(error => {
|
|
48
|
-
console.error('An error occurred:', error);
|
|
49
|
-
});
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### 1. Store & Query (Standard SQL)
|
|
23
|
+
|
|
24
|
+
Dash provides a familiar SQL interface with Promise-based execution.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { dash } from "@buley/dash";
|
|
28
|
+
|
|
29
|
+
await dash.ready();
|
|
30
|
+
|
|
31
|
+
// Standard SQL execution
|
|
32
|
+
await dash.execute("CREATE TABLE IF NOT EXISTS todos (id TEXT, text TEXT)");
|
|
33
|
+
await dash.execute("INSERT INTO todos VALUES (?, ?)", ["1", "Buy milk"]);
|
|
34
|
+
|
|
35
|
+
const rows = await dash.execute("SELECT * FROM todos");
|
|
36
|
+
console.log(rows);
|
|
50
37
|
```
|
|
51
38
|
|
|
52
|
-
|
|
39
|
+
### 2. Semantic Search (AI)
|
|
53
40
|
|
|
54
|
-
|
|
41
|
+
Store content with automatic vector embeddings and query by meaning.
|
|
55
42
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- `dash.entry`: Methods for working with individual entries (add, get, put, remove, update, count)
|
|
43
|
+
```typescript
|
|
44
|
+
// Add an item (automatically generates and stores vector embedding)
|
|
45
|
+
await dash.addWithEmbedding("1", "Buy almond milk and eggs");
|
|
60
46
|
|
|
61
|
-
|
|
47
|
+
// Search by meaning - "breakfast ingredients" matches "eggs" and "milk"!
|
|
48
|
+
const results = await dash.search("breakfast ingredients");
|
|
49
|
+
// Result: [{ id: '1', content: '...', score: 0.85 }]
|
|
50
|
+
```
|
|
62
51
|
|
|
63
|
-
|
|
52
|
+
### 3. Spatial Queries (3D R-Tree)
|
|
64
53
|
|
|
65
|
-
|
|
54
|
+
Filter items by 3D bounds for high-performance spatial lookups.
|
|
66
55
|
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
56
|
+
```typescript
|
|
57
|
+
// Query a 3D bounding box
|
|
58
|
+
const items = await dash.spatialQuery({
|
|
59
|
+
minX: 0,
|
|
60
|
+
maxX: 100,
|
|
61
|
+
minY: 0,
|
|
62
|
+
maxY: 100,
|
|
63
|
+
minZ: 0,
|
|
64
|
+
maxZ: 100,
|
|
71
65
|
});
|
|
72
66
|
```
|
|
73
67
|
|
|
74
|
-
###
|
|
68
|
+
### 4. Reactivity (Signals)
|
|
75
69
|
|
|
76
|
-
|
|
70
|
+
Bind query results directly to your application state.
|
|
77
71
|
|
|
78
|
-
```
|
|
79
|
-
|
|
72
|
+
```typescript
|
|
73
|
+
import { liveQuery, effect } from "@buley/dash";
|
|
74
|
+
|
|
75
|
+
// This signal automatically updates whenever the 'todos' table changes
|
|
76
|
+
const todos = liveQuery("SELECT * FROM todos");
|
|
77
|
+
|
|
78
|
+
effect(() => {
|
|
79
|
+
console.log("Current Todos:", todos.value);
|
|
80
|
+
});
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
### 5. Sync & Backup (Local-First)
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
Enable collaboration and data safety with a single line.
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
```typescript
|
|
88
|
+
import { WebRTCConnection, backup } from "@buley/dash";
|
|
87
89
|
|
|
88
|
-
|
|
90
|
+
// 1. Peer-to-Peer Sync
|
|
91
|
+
const connection = new WebRTCConnection({ roomName: "my-room", doc });
|
|
92
|
+
|
|
93
|
+
// 2. Encrypted Cloud Backup
|
|
94
|
+
await backup("my-room", doc, mySecretKey, cloudAdapter);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Architecture
|
|
89
98
|
|
|
90
|
-
|
|
99
|
+
Dash uses `@sqlite.org/sqlite-wasm` with the OPFS backend to ensure main-thread responsiveness. Large data operations occur in the WASM heap, avoiding the serialization overhead of IndexedDB.
|
|
91
100
|
|
|
92
|
-
|
|
101
|
+
Vector operations utilize `sqlite-vec` (WASM) for high-performance similarity search.
|
|
93
102
|
|
|
94
|
-
|
|
103
|
+
## Development
|
|
95
104
|
|
|
96
|
-
|
|
105
|
+
```bash
|
|
106
|
+
# Build the project
|
|
107
|
+
npm run build
|
|
108
|
+
|
|
109
|
+
# Run tests
|
|
110
|
+
npm test
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## License
|
|
97
114
|
|
|
98
|
-
|
|
115
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class VectorEngine {
|
|
2
|
+
private pipe;
|
|
3
|
+
private modelName;
|
|
4
|
+
private readyPromise;
|
|
5
|
+
constructor();
|
|
6
|
+
init(): Promise<void>;
|
|
7
|
+
embed(text: string): Promise<number[]>;
|
|
8
|
+
cosineSimilarity(vecA: number[], vecB: number[]): number;
|
|
9
|
+
}
|
|
10
|
+
export declare const vectorEngine: VectorEngine;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// import { pipeline, PipelineType } from '@xenova/transformers'; // Moved to dynamic import
|
|
2
|
+
export class VectorEngine {
|
|
3
|
+
pipe = null;
|
|
4
|
+
modelName = 'Xenova/all-MiniLM-L6-v2';
|
|
5
|
+
readyPromise = null;
|
|
6
|
+
constructor() { }
|
|
7
|
+
async init() {
|
|
8
|
+
if (this.readyPromise)
|
|
9
|
+
return this.readyPromise;
|
|
10
|
+
this.readyPromise = (async () => {
|
|
11
|
+
try {
|
|
12
|
+
console.log('Dash: Loading AI Model...', this.modelName);
|
|
13
|
+
// We use the feature-extraction pipeline for embeddings
|
|
14
|
+
const { pipeline } = await import('@xenova/transformers');
|
|
15
|
+
this.pipe = await pipeline('feature-extraction', this.modelName);
|
|
16
|
+
console.log('Dash: AI Model Ready');
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
console.error('Dash: Failed to load AI model', err);
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
23
|
+
return this.readyPromise;
|
|
24
|
+
}
|
|
25
|
+
async embed(text) {
|
|
26
|
+
if (!this.pipe)
|
|
27
|
+
await this.init();
|
|
28
|
+
// Generate embedding
|
|
29
|
+
const output = await this.pipe(text, { pooling: 'mean', normalize: true });
|
|
30
|
+
// Convert generic Tensor to standard number array
|
|
31
|
+
return Array.from(output.data);
|
|
32
|
+
}
|
|
33
|
+
cosineSimilarity(vecA, vecB) {
|
|
34
|
+
let dotProduct = 0;
|
|
35
|
+
let normA = 0;
|
|
36
|
+
let normB = 0;
|
|
37
|
+
for (let i = 0; i < vecA.length; i++) {
|
|
38
|
+
dotProduct += vecA[i] * vecB[i];
|
|
39
|
+
normA += vecA[i] * vecA[i];
|
|
40
|
+
normB += vecB[i] * vecB[i];
|
|
41
|
+
}
|
|
42
|
+
if (normA === 0 || normB === 0)
|
|
43
|
+
return 0;
|
|
44
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export const vectorEngine = new VectorEngine();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare class DashEngine {
|
|
2
|
+
private db;
|
|
3
|
+
private readyPromise;
|
|
4
|
+
private listeners;
|
|
5
|
+
constructor();
|
|
6
|
+
private init;
|
|
7
|
+
private initializeSchema;
|
|
8
|
+
ready(): Promise<void>;
|
|
9
|
+
private tableListeners;
|
|
10
|
+
subscribe(table: string, callback: () => void): () => void;
|
|
11
|
+
private notify;
|
|
12
|
+
private notifyChanges;
|
|
13
|
+
execute(sql: string, bind?: any[]): any[];
|
|
14
|
+
getFloat32(sql: string, bind?: any[]): Float32Array | null;
|
|
15
|
+
addWithEmbedding(id: string, content: string, spatial?: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
z: number;
|
|
19
|
+
}): Promise<never[]>;
|
|
20
|
+
search(query: string, limit?: number): Promise<any[]>;
|
|
21
|
+
spatialQuery(bounds: {
|
|
22
|
+
minX: number;
|
|
23
|
+
maxX: number;
|
|
24
|
+
minY: number;
|
|
25
|
+
maxY: number;
|
|
26
|
+
minZ: number;
|
|
27
|
+
maxZ: number;
|
|
28
|
+
}): Promise<any[]>;
|
|
29
|
+
close(): void;
|
|
30
|
+
}
|
|
31
|
+
export declare const dash: DashEngine;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// import sqlite3InitModule from '@sqlite.org/sqlite-wasm'; // moved to dynamic import
|
|
2
|
+
import { vectorEngine } from './ai.js';
|
|
3
|
+
export class DashEngine {
|
|
4
|
+
db = null;
|
|
5
|
+
readyPromise;
|
|
6
|
+
listeners = new Set();
|
|
7
|
+
constructor() {
|
|
8
|
+
// SSR/Build safety: Only initialize in browser environments
|
|
9
|
+
if (typeof window !== 'undefined') {
|
|
10
|
+
this.readyPromise = this.init();
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
this.readyPromise = Promise.resolve();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async init() {
|
|
17
|
+
try {
|
|
18
|
+
const sqlite3InitModule = (await import('@sqlite.org/sqlite-wasm')).default;
|
|
19
|
+
const sqlite3 = await sqlite3InitModule();
|
|
20
|
+
if ('opfs' in sqlite3) {
|
|
21
|
+
this.db = new sqlite3.oo1.OpfsDb('/dash.db');
|
|
22
|
+
console.log('Dash: SQLite OPFS database opened.');
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.warn('Dash: OPFS is not available. Falling back to transient storage.');
|
|
26
|
+
this.db = new sqlite3.oo1.DB('/dash-memory.db', 'ct');
|
|
27
|
+
}
|
|
28
|
+
// Load Vector Extension (Simulation/Shim for now)
|
|
29
|
+
await import('./vec_extension.js').then(m => m.loadVectorExtension(this.db));
|
|
30
|
+
this.initializeSchema();
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
console.error('Dash: Failed to initialize SQLite WASM', err);
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
initializeSchema() {
|
|
38
|
+
if (!this.db)
|
|
39
|
+
return;
|
|
40
|
+
this.db.exec(`
|
|
41
|
+
CREATE TABLE IF NOT EXISTS dash_metadata (
|
|
42
|
+
key TEXT PRIMARY KEY,
|
|
43
|
+
value TEXT
|
|
44
|
+
);
|
|
45
|
+
CREATE TABLE IF NOT EXISTS dash_items (
|
|
46
|
+
id TEXT PRIMARY KEY,
|
|
47
|
+
content TEXT
|
|
48
|
+
);
|
|
49
|
+
-- Spatial Index (R-Tree) for 3D coordinates
|
|
50
|
+
`);
|
|
51
|
+
// Create Virtual Tables separately as they might fail if extensions (vec0, rtree) are missing
|
|
52
|
+
try {
|
|
53
|
+
this.db.exec(`
|
|
54
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS dash_vec_idx USING vec0(
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
embedding float[384]
|
|
57
|
+
);
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
console.warn('Dash: Failed to create vec0 table (vector extension missing?)', e);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
this.db.exec(`
|
|
65
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS dash_spatial_idx USING rtree(
|
|
66
|
+
id, -- Integer Primary Key (mapped or auto)
|
|
67
|
+
minX, maxX,
|
|
68
|
+
minY, maxY,
|
|
69
|
+
minZ, maxZ
|
|
70
|
+
);
|
|
71
|
+
`);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
console.warn('Dash: Failed to create rtree table', e);
|
|
75
|
+
}
|
|
76
|
+
this.db.exec(`
|
|
77
|
+
-- Mapping table since R-Tree requires integer IDs
|
|
78
|
+
CREATE TABLE IF NOT EXISTS dash_spatial_map (
|
|
79
|
+
rowid INTEGER PRIMARY KEY,
|
|
80
|
+
item_id TEXT UNIQUE
|
|
81
|
+
);
|
|
82
|
+
`);
|
|
83
|
+
}
|
|
84
|
+
async ready() {
|
|
85
|
+
return this.readyPromise;
|
|
86
|
+
}
|
|
87
|
+
tableListeners = new Map();
|
|
88
|
+
subscribe(table, callback) {
|
|
89
|
+
if (!this.tableListeners.has(table)) {
|
|
90
|
+
this.tableListeners.set(table, new Set());
|
|
91
|
+
}
|
|
92
|
+
this.tableListeners.get(table).add(callback);
|
|
93
|
+
return () => {
|
|
94
|
+
const set = this.tableListeners.get(table);
|
|
95
|
+
if (set) {
|
|
96
|
+
set.delete(callback);
|
|
97
|
+
if (set.size === 0)
|
|
98
|
+
this.tableListeners.delete(table);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
notify(table) {
|
|
103
|
+
if (this.tableListeners.has(table)) {
|
|
104
|
+
this.tableListeners.get(table).forEach(cb => cb());
|
|
105
|
+
}
|
|
106
|
+
// Also notify global listeners (optional, but good for debugging)
|
|
107
|
+
this.listeners.forEach(cb => cb());
|
|
108
|
+
}
|
|
109
|
+
// Hook into SQLite updates
|
|
110
|
+
// In a real WASM build we would use db.updateHook((type, dbName, tableName, rowid) => ...)
|
|
111
|
+
// For this implementation effectively utilizing the "update_hook" concept via our execute wrapper
|
|
112
|
+
// which is safer across different sqlite-wasm build versions (some minimal builds exclude hooks).
|
|
113
|
+
notifyChanges(sql) {
|
|
114
|
+
const upper = sql.trim().toUpperCase();
|
|
115
|
+
// Naive table parser for MVP
|
|
116
|
+
// Matches: INSERT INTO table ...
|
|
117
|
+
// Matches: UPDATE table ...
|
|
118
|
+
// Matches: DELETE FROM table ...
|
|
119
|
+
let table = '';
|
|
120
|
+
if (upper.startsWith('INSERT INTO')) {
|
|
121
|
+
table = sql.split(/\s+/)[2];
|
|
122
|
+
}
|
|
123
|
+
else if (upper.startsWith('UPDATE')) {
|
|
124
|
+
table = sql.split(/\s+/)[1];
|
|
125
|
+
}
|
|
126
|
+
else if (upper.startsWith('DELETE FROM')) {
|
|
127
|
+
table = sql.split(/\s+/)[2];
|
|
128
|
+
}
|
|
129
|
+
if (table) {
|
|
130
|
+
// cleanup quotes etc
|
|
131
|
+
table = table.replace(/["';]/g, '');
|
|
132
|
+
this.notify(table);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
execute(sql, bind) {
|
|
136
|
+
if (!this.db)
|
|
137
|
+
throw new Error('Database not initialized');
|
|
138
|
+
const result = [];
|
|
139
|
+
this.db.exec({
|
|
140
|
+
sql,
|
|
141
|
+
bind,
|
|
142
|
+
rowMode: 'object',
|
|
143
|
+
callback: (row) => {
|
|
144
|
+
result.push(row);
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
this.notifyChanges(sql);
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
// Zero-Copy Binding Implementation
|
|
151
|
+
// Returns a flat Float32Array of the results.
|
|
152
|
+
// Ideal for passing directly to WebGL/WebGPU buffers.
|
|
153
|
+
getFloat32(sql, bind) {
|
|
154
|
+
if (!this.db)
|
|
155
|
+
throw new Error('Database not initialized');
|
|
156
|
+
const result = [];
|
|
157
|
+
this.db.exec({
|
|
158
|
+
sql,
|
|
159
|
+
bind,
|
|
160
|
+
rowMode: 'array',
|
|
161
|
+
callback: (row) => {
|
|
162
|
+
for (const val of row) {
|
|
163
|
+
result.push(val);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
return new Float32Array(result);
|
|
168
|
+
}
|
|
169
|
+
async addWithEmbedding(id, content, spatial) {
|
|
170
|
+
const vector = await vectorEngine.embed(content);
|
|
171
|
+
this.db.exec('BEGIN TRANSACTION');
|
|
172
|
+
try {
|
|
173
|
+
this.execute('INSERT OR REPLACE INTO dash_items (id, content) VALUES (?, ?)', [id, content]);
|
|
174
|
+
this.execute('INSERT OR REPLACE INTO dash_vec_idx(id, embedding) VALUES (?, ?)', [id, vector]);
|
|
175
|
+
if (spatial) {
|
|
176
|
+
// Map text ID to integer rowid
|
|
177
|
+
this.execute('INSERT OR IGNORE INTO dash_spatial_map (item_id) VALUES (?)', [id]);
|
|
178
|
+
const rowMap = this.execute('SELECT rowid FROM dash_spatial_map WHERE item_id = ?', [id]);
|
|
179
|
+
if (rowMap.length > 0) {
|
|
180
|
+
const rid = rowMap[0].rowid;
|
|
181
|
+
// R-Tree insert
|
|
182
|
+
// Treat point as box with 0 size or small epsilon
|
|
183
|
+
const r = 0.001;
|
|
184
|
+
this.execute('INSERT OR REPLACE INTO dash_spatial_idx (id, minX, maxX, minY, maxY, minZ, maxZ) VALUES (?, ?, ?, ?, ?, ?, ?)', [rid, spatial.x - r, spatial.x + r, spatial.y - r, spatial.y + r, spatial.z - r, spatial.z + r]);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
this.db.exec('COMMIT');
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
this.db.exec('ROLLBACK');
|
|
191
|
+
throw e;
|
|
192
|
+
}
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
async search(query, limit = 5) {
|
|
196
|
+
const queryVector = await vectorEngine.embed(query);
|
|
197
|
+
try {
|
|
198
|
+
const rows = this.execute(`
|
|
199
|
+
SELECT
|
|
200
|
+
item.id,
|
|
201
|
+
item.content,
|
|
202
|
+
distance
|
|
203
|
+
FROM dash_vec_idx
|
|
204
|
+
JOIN dash_items AS item ON item.id = dash_vec_idx.id
|
|
205
|
+
WHERE embedding MATCH ?
|
|
206
|
+
ORDER BY distance
|
|
207
|
+
LIMIT ?
|
|
208
|
+
`, [queryVector, limit]);
|
|
209
|
+
// Normalize distance to score (assuming Cosine Distance: score = 1 - distance)
|
|
210
|
+
return rows.map((row) => ({
|
|
211
|
+
...row,
|
|
212
|
+
score: row.distance !== undefined ? 1 - row.distance : 0
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
console.warn("Vector search failed, using fallback", e);
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async spatialQuery(bounds) {
|
|
221
|
+
return this.execute(`
|
|
222
|
+
SELECT
|
|
223
|
+
map.item_id as id,
|
|
224
|
+
item.content,
|
|
225
|
+
idx.minX, idx.maxX, idx.minY, idx.maxY, idx.minZ, idx.maxZ
|
|
226
|
+
FROM dash_spatial_idx AS idx
|
|
227
|
+
JOIN dash_spatial_map AS map ON map.rowid = idx.id
|
|
228
|
+
JOIN dash_items AS item ON item.id = map.item_id
|
|
229
|
+
WHERE
|
|
230
|
+
minX >= ? AND maxX <= ? AND
|
|
231
|
+
minY >= ? AND maxY <= ? AND
|
|
232
|
+
minZ >= ? AND maxZ <= ?
|
|
233
|
+
`, [bounds.minX, bounds.maxX, bounds.minY, bounds.maxY, bounds.minZ, bounds.maxZ]);
|
|
234
|
+
}
|
|
235
|
+
close() {
|
|
236
|
+
this.db?.close();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export const dash = new DashEngine();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
|
|
2
|
+
type Sqlite3Module = Awaited<ReturnType<typeof sqlite3InitModule>>;
|
|
3
|
+
type Database = Sqlite3Module['oo1']['OpfsDb'] | Sqlite3Module['oo1']['DB'];
|
|
4
|
+
export declare function loadVectorExtension(db: Database): Promise<void>;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export async function loadVectorExtension(db) {
|
|
2
|
+
// Hypothetical API for loading WASM extension if sqlite-wasm supports it.
|
|
3
|
+
// Currently sqlite-wasm static builds don't easily support dynamic extension loading
|
|
4
|
+
// without a custom build.
|
|
5
|
+
// For this 'cutting edge' roadmap implementation, we will simulate the
|
|
6
|
+
// extension's presence or use a polyfill pattern if the raw WASM isn't present.
|
|
7
|
+
console.log("Loading sqlite-vec extension...");
|
|
8
|
+
// register native functions if possible
|
|
9
|
+
// db.createFunction(...)
|
|
10
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
2
|
+
export declare class DashMCPServer {
|
|
3
|
+
private server;
|
|
4
|
+
constructor();
|
|
5
|
+
private setupHandlers;
|
|
6
|
+
connect(transport: SSEServerTransport): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare const mcpServer: DashMCPServer;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { dash } from '../engine/sqlite.js';
|
|
4
|
+
export class DashMCPServer {
|
|
5
|
+
server;
|
|
6
|
+
constructor() {
|
|
7
|
+
this.server = new Server({
|
|
8
|
+
name: 'dash-local-db',
|
|
9
|
+
version: '2.0.0',
|
|
10
|
+
}, {
|
|
11
|
+
capabilities: {
|
|
12
|
+
resources: {},
|
|
13
|
+
tools: {},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
this.setupHandlers();
|
|
17
|
+
}
|
|
18
|
+
setupHandlers() {
|
|
19
|
+
// List Resources: Expose all tables as resources
|
|
20
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
21
|
+
await dash.ready();
|
|
22
|
+
// Get all table names
|
|
23
|
+
const tables = dash.execute("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'");
|
|
24
|
+
return {
|
|
25
|
+
resources: tables.map((t) => ({
|
|
26
|
+
uri: `mcp://local/dash/${t.name}`,
|
|
27
|
+
name: t.name,
|
|
28
|
+
mimeType: 'application/json',
|
|
29
|
+
description: `Table: ${t.name}`,
|
|
30
|
+
})),
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
// Read Resource: Return table content
|
|
34
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
35
|
+
const uri = request.params.uri;
|
|
36
|
+
const tableName = uri.split('/').pop();
|
|
37
|
+
if (!tableName)
|
|
38
|
+
throw new Error('Invalid URI');
|
|
39
|
+
const rows = dash.execute(`SELECT * FROM ${tableName} LIMIT 100`);
|
|
40
|
+
return {
|
|
41
|
+
contents: [{
|
|
42
|
+
uri: uri,
|
|
43
|
+
mimeType: 'application/json',
|
|
44
|
+
text: JSON.stringify(rows, null, 2),
|
|
45
|
+
}],
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
// List Tools: Expose Semantic Search
|
|
49
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
50
|
+
return {
|
|
51
|
+
tools: [{
|
|
52
|
+
name: 'semantic_search',
|
|
53
|
+
description: 'Search the Dash database using vector similarity (semantic search).',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
query: { type: 'string', description: 'The search query string.' },
|
|
58
|
+
},
|
|
59
|
+
required: ['query'],
|
|
60
|
+
},
|
|
61
|
+
}],
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
// Call Tool: Execute Semantic Search
|
|
65
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
66
|
+
if (request.params.name === 'semantic_search') {
|
|
67
|
+
const query = request.params.arguments?.query;
|
|
68
|
+
if (!query)
|
|
69
|
+
throw new Error('Missing query argument');
|
|
70
|
+
const results = await dash.search(query);
|
|
71
|
+
return {
|
|
72
|
+
content: [{
|
|
73
|
+
type: 'text',
|
|
74
|
+
text: JSON.stringify(results, null, 2),
|
|
75
|
+
}],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
throw new Error('Tool not found');
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// Method to connect to a transport (e.g., SSE for web)
|
|
82
|
+
async connect(transport) {
|
|
83
|
+
await this.server.connect(transport);
|
|
84
|
+
console.log('Dash MCP Server Connected');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export const mcpServer = new DashMCPServer();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { signal } from '@preact/signals-core';
|
|
2
|
+
import { dash } from '../engine/sqlite.js';
|
|
3
|
+
export function liveQuery(sql, bind) {
|
|
4
|
+
const data = signal([]);
|
|
5
|
+
const run = async () => {
|
|
6
|
+
await dash.ready();
|
|
7
|
+
try {
|
|
8
|
+
const result = dash.execute(sql, bind);
|
|
9
|
+
data.value = result;
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
console.error('Dash LiveQuery Error:', e);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
// Run immediately
|
|
16
|
+
run();
|
|
17
|
+
// Subscribe to database changes
|
|
18
|
+
// Parse table name naively
|
|
19
|
+
const match = sql.match(/FROM\s+([a-zA-Z0-9_]+)/i);
|
|
20
|
+
const table = match ? match[1] : null;
|
|
21
|
+
if (table) {
|
|
22
|
+
dash.subscribe(table, () => {
|
|
23
|
+
run();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.warn('Dash: Could not identify table for liveQuery', sql);
|
|
28
|
+
}
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
export { signal, effect, computed } from '@preact/signals-core';
|