pluresdb 1.0.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 +72 -0
- package/README.md +322 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +253 -0
- package/dist/cli.js.map +1 -0
- package/dist/node-index.d.ts +52 -0
- package/dist/node-index.d.ts.map +1 -0
- package/dist/node-index.js +359 -0
- package/dist/node-index.js.map +1 -0
- package/dist/node-wrapper.d.ts +44 -0
- package/dist/node-wrapper.d.ts.map +1 -0
- package/dist/node-wrapper.js +294 -0
- package/dist/node-wrapper.js.map +1 -0
- package/dist/types/index.d.ts +28 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/node-types.d.ts +59 -0
- package/dist/types/node-types.d.ts.map +1 -0
- package/dist/types/node-types.js +6 -0
- package/dist/types/node-types.js.map +1 -0
- package/dist/vscode/extension.d.ts +81 -0
- package/dist/vscode/extension.d.ts.map +1 -0
- package/dist/vscode/extension.js +309 -0
- package/dist/vscode/extension.js.map +1 -0
- package/examples/basic-usage.d.ts +2 -0
- package/examples/basic-usage.d.ts.map +1 -0
- package/examples/basic-usage.js +26 -0
- package/examples/basic-usage.js.map +1 -0
- package/examples/basic-usage.ts +29 -0
- package/examples/vscode-extension-example/README.md +95 -0
- package/examples/vscode-extension-example/package.json +49 -0
- package/examples/vscode-extension-example/src/extension.ts +163 -0
- package/examples/vscode-extension-example/tsconfig.json +12 -0
- package/examples/vscode-extension-integration.d.ts +24 -0
- package/examples/vscode-extension-integration.d.ts.map +1 -0
- package/examples/vscode-extension-integration.js +285 -0
- package/examples/vscode-extension-integration.js.map +1 -0
- package/examples/vscode-extension-integration.ts +41 -0
- package/package.json +115 -0
- package/scripts/compiled-crud-verify.ts +28 -0
- package/scripts/dogfood.ts +258 -0
- package/scripts/postinstall.js +155 -0
- package/scripts/run-tests.ts +175 -0
- package/scripts/setup-libclang.ps1 +209 -0
- package/src/benchmarks/memory-benchmarks.ts +316 -0
- package/src/benchmarks/run-benchmarks.ts +293 -0
- package/src/cli.ts +231 -0
- package/src/config.ts +49 -0
- package/src/core/crdt.ts +104 -0
- package/src/core/database.ts +494 -0
- package/src/healthcheck.ts +156 -0
- package/src/http/api-server.ts +334 -0
- package/src/index.ts +28 -0
- package/src/logic/rules.ts +44 -0
- package/src/main.rs +3 -0
- package/src/main.ts +190 -0
- package/src/network/websocket-server.ts +115 -0
- package/src/node-index.ts +385 -0
- package/src/node-wrapper.ts +320 -0
- package/src/sqlite-compat.ts +586 -0
- package/src/sqlite3-compat.ts +55 -0
- package/src/storage/kv-storage.ts +71 -0
- package/src/tests/core.test.ts +281 -0
- package/src/tests/fixtures/performance-data.json +71 -0
- package/src/tests/fixtures/test-data.json +124 -0
- package/src/tests/integration/api-server.test.ts +232 -0
- package/src/tests/integration/mesh-network.test.ts +297 -0
- package/src/tests/logic.test.ts +30 -0
- package/src/tests/performance/load.test.ts +288 -0
- package/src/tests/security/input-validation.test.ts +282 -0
- package/src/tests/unit/core.test.ts +216 -0
- package/src/tests/unit/subscriptions.test.ts +135 -0
- package/src/tests/unit/vector-search.test.ts +173 -0
- package/src/tests/vscode_extension_test.ts +253 -0
- package/src/types/index.ts +32 -0
- package/src/types/node-types.ts +66 -0
- package/src/util/debug.ts +14 -0
- package/src/vector/index.ts +59 -0
- package/src/vscode/extension.ts +364 -0
- package/web/README.md +27 -0
- package/web/svelte/package.json +31 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { assertEquals, assertExists } from "jsr:@std/assert@1.0.14";
|
|
3
|
+
import { GunDB } from "../../core/database.ts";
|
|
4
|
+
|
|
5
|
+
Deno.test("Vector Search - Basic Functionality", async () => {
|
|
6
|
+
const db = new GunDB();
|
|
7
|
+
try {
|
|
8
|
+
const kvPath = await Deno.makeTempFile({
|
|
9
|
+
prefix: "kv_",
|
|
10
|
+
suffix: ".sqlite",
|
|
11
|
+
});
|
|
12
|
+
await db.ready(kvPath);
|
|
13
|
+
|
|
14
|
+
// Add documents with different content
|
|
15
|
+
await db.put("doc:1", {
|
|
16
|
+
text: "Machine learning and artificial intelligence algorithms",
|
|
17
|
+
content: "Deep learning neural networks for pattern recognition",
|
|
18
|
+
});
|
|
19
|
+
await db.put("doc:2", {
|
|
20
|
+
text: "Cooking recipes and food preparation techniques",
|
|
21
|
+
content: "Italian pasta recipes and cooking methods",
|
|
22
|
+
});
|
|
23
|
+
await db.put("doc:3", {
|
|
24
|
+
text: "Web development and JavaScript programming",
|
|
25
|
+
content: "React and TypeScript for modern web applications",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Test search with text field
|
|
29
|
+
const results1 = await db.vectorSearch("machine learning", 2);
|
|
30
|
+
assertExists(results1);
|
|
31
|
+
assertEquals(results1.length, 2);
|
|
32
|
+
|
|
33
|
+
// Test search with content field
|
|
34
|
+
const results2 = await db.vectorSearch("neural networks", 1);
|
|
35
|
+
assertExists(results2);
|
|
36
|
+
assertEquals(results2.length, 1);
|
|
37
|
+
assertEquals(results2[0].id, "doc:1");
|
|
38
|
+
|
|
39
|
+
// Test search with different query
|
|
40
|
+
const results3 = await db.vectorSearch("cooking food", 1);
|
|
41
|
+
assertExists(results3);
|
|
42
|
+
assertEquals(results3.length, 1);
|
|
43
|
+
assertEquals(results3[0].id, "doc:2");
|
|
44
|
+
} finally {
|
|
45
|
+
await db.close();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
Deno.test("Vector Search - Similarity Scoring", async () => {
|
|
50
|
+
const db = new GunDB();
|
|
51
|
+
try {
|
|
52
|
+
const kvPath = await Deno.makeTempFile({
|
|
53
|
+
prefix: "kv_",
|
|
54
|
+
suffix: ".sqlite",
|
|
55
|
+
});
|
|
56
|
+
await db.ready(kvPath);
|
|
57
|
+
|
|
58
|
+
await db.put("doc:1", { text: "Machine learning algorithms" });
|
|
59
|
+
await db.put("doc:2", { text: "Machine learning and AI" });
|
|
60
|
+
await db.put("doc:3", { text: "Cooking recipes" });
|
|
61
|
+
|
|
62
|
+
const results = await db.vectorSearch("machine learning", 3);
|
|
63
|
+
assertExists(results);
|
|
64
|
+
assertEquals(results.length, 3);
|
|
65
|
+
|
|
66
|
+
// Results should be ordered by similarity (highest first)
|
|
67
|
+
assertExists(results[0].similarity);
|
|
68
|
+
assertExists(results[1].similarity);
|
|
69
|
+
assertExists(results[2].similarity);
|
|
70
|
+
|
|
71
|
+
// First result should have highest similarity
|
|
72
|
+
assertEquals(results[0].similarity >= results[1].similarity, true);
|
|
73
|
+
assertEquals(results[1].similarity >= results[2].similarity, true);
|
|
74
|
+
} finally {
|
|
75
|
+
await db.close();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
Deno.test("Vector Search - Limit Parameter", async () => {
|
|
80
|
+
const db = new GunDB();
|
|
81
|
+
try {
|
|
82
|
+
const kvPath = await Deno.makeTempFile({
|
|
83
|
+
prefix: "kv_",
|
|
84
|
+
suffix: ".sqlite",
|
|
85
|
+
});
|
|
86
|
+
await db.ready(kvPath);
|
|
87
|
+
|
|
88
|
+
// Add multiple documents
|
|
89
|
+
for (let i = 1; i <= 10; i++) {
|
|
90
|
+
await db.put(`doc:${i}`, {
|
|
91
|
+
text: `Document ${i} about machine learning and AI`,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Test different limits
|
|
96
|
+
const results1 = await db.vectorSearch("machine learning", 3);
|
|
97
|
+
assertEquals(results1.length, 3);
|
|
98
|
+
|
|
99
|
+
const results2 = await db.vectorSearch("machine learning", 5);
|
|
100
|
+
assertEquals(results2.length, 5);
|
|
101
|
+
|
|
102
|
+
const results3 = await db.vectorSearch("machine learning", 1);
|
|
103
|
+
assertEquals(results3.length, 1);
|
|
104
|
+
} finally {
|
|
105
|
+
await db.close();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
Deno.test("Vector Search - Empty Results", async () => {
|
|
110
|
+
const db = new GunDB();
|
|
111
|
+
try {
|
|
112
|
+
const kvPath = await Deno.makeTempFile({
|
|
113
|
+
prefix: "kv_",
|
|
114
|
+
suffix: ".sqlite",
|
|
115
|
+
});
|
|
116
|
+
await db.ready(kvPath);
|
|
117
|
+
|
|
118
|
+
// Search in empty database
|
|
119
|
+
const results = await db.vectorSearch("anything", 5);
|
|
120
|
+
assertEquals(results.length, 0);
|
|
121
|
+
} finally {
|
|
122
|
+
await db.close();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
Deno.test("Vector Search - Custom Vector Input", async () => {
|
|
127
|
+
const db = new GunDB();
|
|
128
|
+
try {
|
|
129
|
+
const kvPath = await Deno.makeTempFile({
|
|
130
|
+
prefix: "kv_",
|
|
131
|
+
suffix: ".sqlite",
|
|
132
|
+
});
|
|
133
|
+
await db.ready(kvPath);
|
|
134
|
+
|
|
135
|
+
// Add document with custom vector
|
|
136
|
+
const customVector = [0.1, 0.2, 0.3, 0.4, 0.5];
|
|
137
|
+
await db.put("doc:custom", {
|
|
138
|
+
text: "Custom vector document",
|
|
139
|
+
vector: customVector,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Search with custom vector
|
|
143
|
+
const results = await db.vectorSearch(customVector, 1);
|
|
144
|
+
assertExists(results);
|
|
145
|
+
assertEquals(results.length, 1);
|
|
146
|
+
assertEquals(results[0].id, "doc:custom");
|
|
147
|
+
} finally {
|
|
148
|
+
await db.close();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
Deno.test("Vector Search - No Text Content", async () => {
|
|
153
|
+
const db = new GunDB();
|
|
154
|
+
try {
|
|
155
|
+
const kvPath = await Deno.makeTempFile({
|
|
156
|
+
prefix: "kv_",
|
|
157
|
+
suffix: ".sqlite",
|
|
158
|
+
});
|
|
159
|
+
await db.ready(kvPath);
|
|
160
|
+
|
|
161
|
+
// Add document without text or content
|
|
162
|
+
await db.put("doc:no-text", {
|
|
163
|
+
name: "Document without text",
|
|
164
|
+
value: 123,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Search should return empty results
|
|
168
|
+
const results = await db.vectorSearch("anything", 5);
|
|
169
|
+
assertEquals(results.length, 0);
|
|
170
|
+
} finally {
|
|
171
|
+
await db.close();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { assert, assertEquals, assertMatch } from "jsr:@std/assert@1.0.14";
|
|
3
|
+
import {
|
|
4
|
+
createPluresExtension,
|
|
5
|
+
ExtensionContextLike,
|
|
6
|
+
PluresVSCodeExtension,
|
|
7
|
+
VSCodeAPI,
|
|
8
|
+
} from "../vscode/extension.ts";
|
|
9
|
+
|
|
10
|
+
interface RecordedDocument {
|
|
11
|
+
content: string;
|
|
12
|
+
language: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface RecordedMessage {
|
|
16
|
+
type: "info" | "error";
|
|
17
|
+
text: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type CommandHandler = (...args: unknown[]) => unknown;
|
|
21
|
+
|
|
22
|
+
type TestHarness = {
|
|
23
|
+
vscode: VSCodeAPI;
|
|
24
|
+
context: ExtensionContextLike;
|
|
25
|
+
commands: Map<string, CommandHandler>;
|
|
26
|
+
docs: RecordedDocument[];
|
|
27
|
+
messages: RecordedMessage[];
|
|
28
|
+
shownTargets: string[];
|
|
29
|
+
queueInputs: (...values: (string | undefined)[]) => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function createHarness(storageDir: string): TestHarness {
|
|
33
|
+
const commands = new Map<string, CommandHandler>();
|
|
34
|
+
const docs: RecordedDocument[] = [];
|
|
35
|
+
const messages: RecordedMessage[] = [];
|
|
36
|
+
const shownTargets: string[] = [];
|
|
37
|
+
const inputQueue: Array<string | undefined> = [];
|
|
38
|
+
|
|
39
|
+
const vscode: VSCodeAPI = {
|
|
40
|
+
commands: {
|
|
41
|
+
registerCommand(command, handler) {
|
|
42
|
+
commands.set(command, handler);
|
|
43
|
+
return {
|
|
44
|
+
dispose() {
|
|
45
|
+
commands.delete(command);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
window: {
|
|
51
|
+
async showInformationMessage(message: string) {
|
|
52
|
+
messages.push({ type: "info", text: message });
|
|
53
|
+
},
|
|
54
|
+
async showErrorMessage(message: string) {
|
|
55
|
+
messages.push({ type: "error", text: message });
|
|
56
|
+
},
|
|
57
|
+
async showInputBox() {
|
|
58
|
+
return inputQueue.shift();
|
|
59
|
+
},
|
|
60
|
+
async showTextDocument(doc: unknown) {
|
|
61
|
+
if (typeof doc === "object" && doc && "content" in doc && "language" in doc) {
|
|
62
|
+
const record = doc as RecordedDocument;
|
|
63
|
+
docs.push({ content: record.content, language: record.language });
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
workspace: {
|
|
68
|
+
async openTextDocument(init) {
|
|
69
|
+
docs.push(init as RecordedDocument);
|
|
70
|
+
return init;
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
env: {
|
|
74
|
+
async openExternal(target) {
|
|
75
|
+
shownTargets.push(typeof target === "string" ? target : target.toString());
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
Uri: {
|
|
79
|
+
parse(target: string) {
|
|
80
|
+
return target;
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const context: ExtensionContextLike = {
|
|
86
|
+
subscriptions: [],
|
|
87
|
+
globalStorageUri: { fsPath: storageDir },
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
vscode,
|
|
92
|
+
context,
|
|
93
|
+
commands,
|
|
94
|
+
docs,
|
|
95
|
+
messages,
|
|
96
|
+
shownTargets,
|
|
97
|
+
queueInputs: (...values: (string | undefined)[]) => {
|
|
98
|
+
inputQueue.push(...values);
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function getFreePort(): Promise<number> {
|
|
104
|
+
const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 });
|
|
105
|
+
const { port } = listener.addr as Deno.NetAddr;
|
|
106
|
+
listener.close();
|
|
107
|
+
return port;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function waitFor(predicate: () => Promise<boolean>, timeout = 10_000, interval = 200) {
|
|
111
|
+
const deadline = Date.now() + timeout;
|
|
112
|
+
while (Date.now() < deadline) {
|
|
113
|
+
if (await predicate()) return;
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
115
|
+
}
|
|
116
|
+
throw new Error("Timed out waiting for condition");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function removeDirWithRetry(target: string) {
|
|
120
|
+
const delays = [0, 200, 400, 800, 1600, 3200];
|
|
121
|
+
for (let i = 0; i < delays.length; i++) {
|
|
122
|
+
if (delays[i] > 0) {
|
|
123
|
+
await new Promise((resolve) => setTimeout(resolve, delays[i]));
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
await Deno.remove(target, { recursive: true });
|
|
127
|
+
return;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
const retryable = error instanceof Error && /used by another process/i.test(error.message);
|
|
130
|
+
if (!retryable || i === delays.length - 1) {
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
Deno.test("VSCode integration dogfood workflow", async () => {
|
|
138
|
+
const storageDir = await Deno.makeTempDir({ prefix: "plures-vscode-" });
|
|
139
|
+
const apiPort = await getFreePort();
|
|
140
|
+
const webPort = await getFreePort();
|
|
141
|
+
const apiUrl = `http://127.0.0.1:${apiPort}`;
|
|
142
|
+
|
|
143
|
+
const harness = createHarness(storageDir);
|
|
144
|
+
const extension = createPluresExtension(harness.vscode, harness.context, {
|
|
145
|
+
config: {
|
|
146
|
+
host: "127.0.0.1",
|
|
147
|
+
port: apiPort,
|
|
148
|
+
webPort,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
try {
|
|
154
|
+
await extension.activate();
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error("Activation failed", harness.messages);
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await waitFor(async () => {
|
|
161
|
+
try {
|
|
162
|
+
const res = await fetch(`${apiUrl}/api/list`);
|
|
163
|
+
return res.ok;
|
|
164
|
+
} catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const seed = await fetch(`${apiUrl}/api/put`, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: { "content-type": "application/json" },
|
|
172
|
+
body: JSON.stringify({
|
|
173
|
+
id: "extension:vector-doc",
|
|
174
|
+
data: {
|
|
175
|
+
type: "VSCodeDocument",
|
|
176
|
+
title: "Integration Test",
|
|
177
|
+
tags: ["extension", "sqlite"],
|
|
178
|
+
vector: [0.12, 0.24, 0.48, 0.96],
|
|
179
|
+
},
|
|
180
|
+
}),
|
|
181
|
+
});
|
|
182
|
+
assert(seed.ok, "Failed to seed vector test data");
|
|
183
|
+
|
|
184
|
+
const openWebUICmd = harness.commands.get("pluresdb.openWebUI");
|
|
185
|
+
if (!openWebUICmd) {
|
|
186
|
+
throw new Error("openWebUI command should be registered");
|
|
187
|
+
}
|
|
188
|
+
await openWebUICmd();
|
|
189
|
+
assertEquals(harness.shownTargets.at(-1), `http://127.0.0.1:${webPort}`);
|
|
190
|
+
|
|
191
|
+
harness.queueInputs("SELECT name FROM pragma_table_info('settings')");
|
|
192
|
+
const execQuery = harness.commands.get("pluresdb.executeQuery");
|
|
193
|
+
if (!execQuery) {
|
|
194
|
+
throw new Error("executeQuery command should be registered");
|
|
195
|
+
}
|
|
196
|
+
await execQuery();
|
|
197
|
+
const schemaDoc = harness.docs.at(-1);
|
|
198
|
+
if (!schemaDoc) {
|
|
199
|
+
throw new Error("Expected schema document to open");
|
|
200
|
+
}
|
|
201
|
+
const schemaRows = JSON.parse(schemaDoc.content) as Array<Record<string, unknown>>;
|
|
202
|
+
const columnNames = schemaRows.map((row) => String(row.name));
|
|
203
|
+
assert(columnNames.includes("key"), "Settings table should expose key column");
|
|
204
|
+
|
|
205
|
+
harness.queueInputs("user:alpha", '{"name":"Ada","vector":[0.2,0.1,0.3,0.4],"role":"builder"}');
|
|
206
|
+
const storeCommand = harness.commands.get("pluresdb.storeData");
|
|
207
|
+
if (!storeCommand) {
|
|
208
|
+
throw new Error("storeData command should be registered");
|
|
209
|
+
}
|
|
210
|
+
await storeCommand();
|
|
211
|
+
assertMatch(harness.messages.at(-1)?.text ?? "", /Stored data for key: user:alpha/);
|
|
212
|
+
|
|
213
|
+
harness.queueInputs("user:alpha");
|
|
214
|
+
const retrieveCommand = harness.commands.get("pluresdb.retrieveData");
|
|
215
|
+
if (!retrieveCommand) {
|
|
216
|
+
throw new Error("retrieveData command should be registered");
|
|
217
|
+
}
|
|
218
|
+
await retrieveCommand();
|
|
219
|
+
const retrievedDoc = harness.docs.at(-1);
|
|
220
|
+
if (!retrievedDoc) {
|
|
221
|
+
throw new Error("Expected retrieved document to open");
|
|
222
|
+
}
|
|
223
|
+
const retrieved = JSON.parse(retrievedDoc.content);
|
|
224
|
+
assertEquals(retrieved.name, "Ada");
|
|
225
|
+
|
|
226
|
+
harness.queueInputs("sqlite builder search");
|
|
227
|
+
const vectorCommand = harness.commands.get("pluresdb.vectorSearch");
|
|
228
|
+
if (!vectorCommand) {
|
|
229
|
+
throw new Error("vectorSearch command should be registered");
|
|
230
|
+
}
|
|
231
|
+
await vectorCommand();
|
|
232
|
+
const vectorDoc = harness.docs.at(-1);
|
|
233
|
+
if (!vectorDoc) {
|
|
234
|
+
throw new Error("Expected vector search document to open");
|
|
235
|
+
}
|
|
236
|
+
const vectorResults = JSON.parse(vectorDoc.content) as Array<Record<string, unknown>>;
|
|
237
|
+
assert(vectorResults.length > 0, "Vector search should return at least one result");
|
|
238
|
+
|
|
239
|
+
const settingsRows = await extension.executeSQL(
|
|
240
|
+
"SELECT name FROM pragma_table_info('documents')",
|
|
241
|
+
);
|
|
242
|
+
assert(Array.isArray(settingsRows));
|
|
243
|
+
} finally {
|
|
244
|
+
await extension.deactivate();
|
|
245
|
+
try {
|
|
246
|
+
await removeDirWithRetry(storageDir);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.warn(
|
|
249
|
+
`⚠️ Failed to remove temp dir: ${error instanceof Error ? error.message : String(error)}`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface VectorClock {
|
|
2
|
+
[peerId: string]: number;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface NodeRecord {
|
|
6
|
+
id: string;
|
|
7
|
+
data: Record<string, unknown>;
|
|
8
|
+
vector?: number[];
|
|
9
|
+
type?: string;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
state?: Record<string, number>;
|
|
12
|
+
vectorClock: VectorClock;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PutMessage {
|
|
16
|
+
type: "put";
|
|
17
|
+
originId?: string;
|
|
18
|
+
node: NodeRecord;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DeleteMessage {
|
|
22
|
+
type: "delete";
|
|
23
|
+
originId?: string;
|
|
24
|
+
id: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SyncRequestMessage {
|
|
28
|
+
type: "sync_request";
|
|
29
|
+
originId?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type MeshMessage = PutMessage | DeleteMessage | SyncRequestMessage;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js-specific types for PluresDB
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface PluresDBConfig {
|
|
6
|
+
port?: number;
|
|
7
|
+
host?: string;
|
|
8
|
+
dataDir?: string;
|
|
9
|
+
webPort?: number;
|
|
10
|
+
logLevel?: "debug" | "info" | "warn" | "error";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PluresDBOptions {
|
|
14
|
+
config?: PluresDBConfig;
|
|
15
|
+
autoStart?: boolean;
|
|
16
|
+
denoPath?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface QueryResult {
|
|
20
|
+
rows: any[];
|
|
21
|
+
columns: string[];
|
|
22
|
+
changes: number;
|
|
23
|
+
lastInsertRowId: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface VectorSearchResult {
|
|
27
|
+
id: string;
|
|
28
|
+
content: string;
|
|
29
|
+
score: number;
|
|
30
|
+
metadata?: any;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Peer {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
email: string;
|
|
37
|
+
publicKey: string;
|
|
38
|
+
lastSeen: Date;
|
|
39
|
+
status: "online" | "offline" | "connecting";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SharedNode {
|
|
43
|
+
id: string;
|
|
44
|
+
nodeId: string;
|
|
45
|
+
peerId: string;
|
|
46
|
+
accessLevel: "read-only" | "read-write" | "admin";
|
|
47
|
+
encrypted: boolean;
|
|
48
|
+
createdAt: Date;
|
|
49
|
+
expiresAt?: Date;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface Device {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
type: "laptop" | "phone" | "server" | "desktop";
|
|
56
|
+
lastSync: Date;
|
|
57
|
+
status: "online" | "offline" | "syncing";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface SyncStatus {
|
|
61
|
+
isOnline: boolean;
|
|
62
|
+
lastSync: Date | null;
|
|
63
|
+
pendingChanges: number;
|
|
64
|
+
connectedPeers: number;
|
|
65
|
+
syncProgress: number;
|
|
66
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const DEBUG_ENABLED: boolean = (() => {
|
|
2
|
+
try {
|
|
3
|
+
const v = Deno.env.get("PLURESDB_DEBUG") ?? "";
|
|
4
|
+
return v === "1" || v.toLowerCase() === "true";
|
|
5
|
+
} catch {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
})();
|
|
9
|
+
|
|
10
|
+
export function debugLog(...args: unknown[]): void {
|
|
11
|
+
if (!DEBUG_ENABLED) return;
|
|
12
|
+
// deno-lint-ignore no-console
|
|
13
|
+
console.log("[pluresdb]", ...args);
|
|
14
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface VectorIndexResult {
|
|
2
|
+
id: string;
|
|
3
|
+
score: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface VectorIndex {
|
|
7
|
+
upsert(id: string, vector: number[]): void;
|
|
8
|
+
remove(id: string): void;
|
|
9
|
+
search(vector: number[], k: number): VectorIndexResult[];
|
|
10
|
+
clear(): void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class BruteForceVectorIndex implements VectorIndex {
|
|
14
|
+
private readonly idToVector = new Map<string, Float32Array>();
|
|
15
|
+
|
|
16
|
+
upsert(id: string, vector: number[]): void {
|
|
17
|
+
const normed = normalizeVector(vector);
|
|
18
|
+
this.idToVector.set(id, normed);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
remove(id: string): void {
|
|
22
|
+
this.idToVector.delete(id);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
clear(): void {
|
|
26
|
+
this.idToVector.clear();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
search(vector: number[], k: number): VectorIndexResult[] {
|
|
30
|
+
const q = normalizeVector(vector);
|
|
31
|
+
const results: VectorIndexResult[] = [];
|
|
32
|
+
for (const [id, v] of this.idToVector) {
|
|
33
|
+
const score = cosine(q, v);
|
|
34
|
+
if (Number.isFinite(score)) results.push({ id, score });
|
|
35
|
+
}
|
|
36
|
+
results.sort((a, b) => b.score - a.score);
|
|
37
|
+
return results.slice(0, k);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeVector(vector: number[]): Float32Array {
|
|
42
|
+
const arr = new Float32Array(vector.length);
|
|
43
|
+
let n = 0;
|
|
44
|
+
for (let i = 0; i < vector.length; i++) {
|
|
45
|
+
const v = vector[i] ?? 0;
|
|
46
|
+
arr[i] = v;
|
|
47
|
+
n += v * v;
|
|
48
|
+
}
|
|
49
|
+
const norm = Math.sqrt(n) || 1;
|
|
50
|
+
for (let i = 0; i < arr.length; i++) arr[i] /= norm;
|
|
51
|
+
return arr;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function cosine(a: Float32Array, b: Float32Array): number {
|
|
55
|
+
const len = Math.min(a.length, b.length);
|
|
56
|
+
let dot = 0;
|
|
57
|
+
for (let i = 0; i < len; i++) dot += a[i] * b[i];
|
|
58
|
+
return dot;
|
|
59
|
+
}
|