pluresdb 1.0.1 → 1.3.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/README.md +100 -5
- package/dist/.tsbuildinfo +1 -1
- package/dist/better-sqlite3-shared.d.ts +12 -0
- package/dist/better-sqlite3-shared.d.ts.map +1 -0
- package/dist/better-sqlite3-shared.js +143 -0
- package/dist/better-sqlite3-shared.js.map +1 -0
- package/dist/better-sqlite3.d.ts +4 -0
- package/dist/better-sqlite3.d.ts.map +1 -0
- package/dist/better-sqlite3.js +8 -0
- package/dist/better-sqlite3.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +21 -16
- package/dist/cli.js.map +1 -1
- package/dist/node-index.d.ts +98 -2
- package/dist/node-index.d.ts.map +1 -1
- package/dist/node-index.js +312 -6
- package/dist/node-index.js.map +1 -1
- package/dist/node-wrapper.d.ts.map +1 -1
- package/dist/node-wrapper.js +5 -3
- package/dist/node-wrapper.js.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/node-types.d.ts +12 -0
- package/dist/types/node-types.d.ts.map +1 -1
- package/dist/types/node-types.js.map +1 -1
- package/dist/vscode/extension.d.ts.map +1 -1
- package/dist/vscode/extension.js +4 -4
- package/dist/vscode/extension.js.map +1 -1
- package/examples/basic-usage.d.ts +1 -1
- package/examples/vscode-extension-example/src/extension.ts +15 -6
- package/examples/vscode-extension-integration.d.ts +24 -17
- package/examples/vscode-extension-integration.js +140 -106
- package/examples/vscode-extension-integration.ts +1 -1
- package/{src → legacy}/benchmarks/memory-benchmarks.ts +85 -51
- package/{src → legacy}/benchmarks/run-benchmarks.ts +32 -10
- package/legacy/better-sqlite3-shared.ts +157 -0
- package/legacy/better-sqlite3.ts +4 -0
- package/{src → legacy}/cli.ts +14 -4
- package/{src → legacy}/config.ts +2 -1
- package/{src → legacy}/core/crdt.ts +4 -1
- package/{src → legacy}/core/database.ts +57 -22
- package/{src → legacy}/healthcheck.ts +11 -5
- package/{src → legacy}/http/api-server.ts +125 -21
- package/{src → legacy}/index.ts +2 -2
- package/{src → legacy}/logic/rules.ts +3 -1
- package/{src → legacy}/main.ts +11 -4
- package/legacy/node-index.ts +823 -0
- package/{src → legacy}/node-wrapper.ts +18 -9
- package/{src → legacy}/sqlite-compat.ts +63 -16
- package/{src → legacy}/sqlite3-compat.ts +2 -2
- package/{src → legacy}/storage/kv-storage.ts +3 -1
- package/{src → legacy}/tests/core.test.ts +37 -13
- package/{src → legacy}/tests/fixtures/test-data.json +6 -1
- package/{src → legacy}/tests/integration/api-server.test.ts +110 -8
- package/{src → legacy}/tests/integration/mesh-network.test.ts +8 -2
- package/{src → legacy}/tests/logic.test.ts +6 -2
- package/{src → legacy}/tests/performance/load.test.ts +4 -2
- package/{src → legacy}/tests/security/input-validation.test.ts +5 -1
- package/{src → legacy}/tests/unit/core.test.ts +13 -3
- package/{src → legacy}/tests/unit/subscriptions.test.ts +1 -1
- package/{src → legacy}/tests/vscode_extension_test.ts +39 -11
- package/{src → legacy}/types/node-types.ts +14 -0
- package/{src → legacy}/vscode/extension.ts +37 -14
- package/package.json +19 -9
- package/scripts/compiled-crud-verify.ts +3 -1
- package/scripts/dogfood.ts +55 -16
- package/scripts/postinstall.js +4 -3
- package/scripts/release-check.js +190 -0
- package/scripts/run-tests.ts +5 -2
- package/scripts/update-changelog.js +214 -0
- package/web/svelte/package.json +5 -5
- package/src/node-index.ts +0 -385
- /package/{src → legacy}/main.rs +0 -0
- /package/{src → legacy}/network/websocket-server.ts +0 -0
- /package/{src → legacy}/tests/fixtures/performance-data.json +0 -0
- /package/{src → legacy}/tests/unit/vector-search.test.ts +0 -0
- /package/{src → legacy}/types/index.ts +0 -0
- /package/{src → legacy}/util/debug.ts +0 -0
- /package/{src → legacy}/vector/index.ts +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* This module provides a Node.js-compatible API for VSCode extensions
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { ChildProcess, spawn } from "node:child_process";
|
|
7
7
|
import { EventEmitter } from "node:events";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import * as fs from "node:fs";
|
|
@@ -66,17 +66,21 @@ export class PluresNode extends EventEmitter {
|
|
|
66
66
|
if (fs.existsSync(denoPath) || this.isCommandAvailable(denoPath)) {
|
|
67
67
|
return denoPath;
|
|
68
68
|
}
|
|
69
|
-
} catch
|
|
69
|
+
} catch {
|
|
70
70
|
// Continue to next path
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
throw new Error(
|
|
74
|
+
throw new Error(
|
|
75
|
+
"Deno not found. Please install Deno from https://deno.land/",
|
|
76
|
+
);
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
private isCommandAvailable(command: string): boolean {
|
|
78
80
|
try {
|
|
79
|
-
require("child_process").execSync(`"${command}" --version`, {
|
|
81
|
+
require("child_process").execSync(`"${command}" --version`, {
|
|
82
|
+
stdio: "ignore",
|
|
83
|
+
});
|
|
80
84
|
return true;
|
|
81
85
|
} catch {
|
|
82
86
|
return false;
|
|
@@ -169,7 +173,7 @@ export class PluresNode extends EventEmitter {
|
|
|
169
173
|
if (response.ok) {
|
|
170
174
|
return;
|
|
171
175
|
}
|
|
172
|
-
} catch
|
|
176
|
+
} catch {
|
|
173
177
|
// Server not ready yet
|
|
174
178
|
}
|
|
175
179
|
|
|
@@ -243,7 +247,9 @@ export class PluresNode extends EventEmitter {
|
|
|
243
247
|
}
|
|
244
248
|
|
|
245
249
|
async get(key: string): Promise<any> {
|
|
246
|
-
const response = await fetch(
|
|
250
|
+
const response = await fetch(
|
|
251
|
+
`${this.apiUrl}/api/data/${encodeURIComponent(key)}`,
|
|
252
|
+
);
|
|
247
253
|
|
|
248
254
|
if (!response.ok) {
|
|
249
255
|
if (response.status === 404) {
|
|
@@ -256,9 +262,12 @@ export class PluresNode extends EventEmitter {
|
|
|
256
262
|
}
|
|
257
263
|
|
|
258
264
|
async delete(key: string): Promise<void> {
|
|
259
|
-
const response = await fetch(
|
|
260
|
-
|
|
261
|
-
|
|
265
|
+
const response = await fetch(
|
|
266
|
+
`${this.apiUrl}/api/data/${encodeURIComponent(key)}`,
|
|
267
|
+
{
|
|
268
|
+
method: "DELETE",
|
|
269
|
+
},
|
|
270
|
+
);
|
|
262
271
|
|
|
263
272
|
if (!response.ok) {
|
|
264
273
|
throw new Error(`Delete failed: ${response.statusText}`);
|
|
@@ -96,7 +96,10 @@ export class Database {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
async run(
|
|
99
|
+
async run(
|
|
100
|
+
sql: string,
|
|
101
|
+
params: any[] = [],
|
|
102
|
+
): Promise<{ lastID: number; changes: number }> {
|
|
100
103
|
if (!this.isOpen) {
|
|
101
104
|
throw new Error("Database is not open");
|
|
102
105
|
}
|
|
@@ -137,7 +140,11 @@ export class Database {
|
|
|
137
140
|
}
|
|
138
141
|
}
|
|
139
142
|
|
|
140
|
-
async each(
|
|
143
|
+
async each(
|
|
144
|
+
sql: string,
|
|
145
|
+
params: any[] = [],
|
|
146
|
+
callback: (row: any) => void,
|
|
147
|
+
): Promise<number> {
|
|
141
148
|
if (!this.isOpen) {
|
|
142
149
|
throw new Error("Database is not open");
|
|
143
150
|
}
|
|
@@ -190,7 +197,9 @@ export class Database {
|
|
|
190
197
|
return statements.map((statement) => ({ sql: statement }));
|
|
191
198
|
}
|
|
192
199
|
|
|
193
|
-
private async executeStatement(
|
|
200
|
+
private async executeStatement(
|
|
201
|
+
statement: { sql: string; params?: any[] },
|
|
202
|
+
): Promise<any> {
|
|
194
203
|
const sql = statement.sql.toLowerCase().trim();
|
|
195
204
|
|
|
196
205
|
if (sql.startsWith("create table")) {
|
|
@@ -203,7 +212,10 @@ export class Database {
|
|
|
203
212
|
return await this.update(statement.sql, statement.params || []);
|
|
204
213
|
} else if (sql.startsWith("delete")) {
|
|
205
214
|
return await this.delete(statement.sql, statement.params || []);
|
|
206
|
-
} else if (
|
|
215
|
+
} else if (
|
|
216
|
+
sql.startsWith("begin") || sql.startsWith("commit") ||
|
|
217
|
+
sql.startsWith("rollback")
|
|
218
|
+
) {
|
|
207
219
|
// Transaction commands - handled by transaction method
|
|
208
220
|
return { changes: 0 };
|
|
209
221
|
} else {
|
|
@@ -223,7 +235,9 @@ export class Database {
|
|
|
223
235
|
|
|
224
236
|
private async createTable(sql: string): Promise<void> {
|
|
225
237
|
// Extract table name and columns from CREATE TABLE statement
|
|
226
|
-
const tableMatch = sql.match(
|
|
238
|
+
const tableMatch = sql.match(
|
|
239
|
+
/CREATE TABLE\s+(?:IF NOT EXISTS\s+)?(\w+)\s*\(([^)]+)\)/i,
|
|
240
|
+
);
|
|
227
241
|
if (!tableMatch) {
|
|
228
242
|
throw new Error(`Invalid CREATE TABLE statement: ${sql}`);
|
|
229
243
|
}
|
|
@@ -265,7 +279,10 @@ export class Database {
|
|
|
265
279
|
}
|
|
266
280
|
}
|
|
267
281
|
|
|
268
|
-
private async insert(
|
|
282
|
+
private async insert(
|
|
283
|
+
sql: string,
|
|
284
|
+
params: any[],
|
|
285
|
+
): Promise<{ lastID: number; changes: number }> {
|
|
269
286
|
const insertMatch = sql.match(
|
|
270
287
|
/INSERT\s+(?:INTO\s+)?(\w+)\s*\(([^)]+)\)\s*VALUES\s*\(([^)]+)\)/i,
|
|
271
288
|
);
|
|
@@ -295,7 +312,9 @@ export class Database {
|
|
|
295
312
|
});
|
|
296
313
|
|
|
297
314
|
// Generate unique ID
|
|
298
|
-
const id = `${tableName}:${Date.now()}:${
|
|
315
|
+
const id = `${tableName}:${Date.now()}:${
|
|
316
|
+
Math.random().toString(36).substr(2, 9)
|
|
317
|
+
}`;
|
|
299
318
|
row.id = id;
|
|
300
319
|
row.created_at = new Date().toISOString();
|
|
301
320
|
|
|
@@ -309,8 +328,13 @@ export class Database {
|
|
|
309
328
|
return { lastID: 0, changes: 1 };
|
|
310
329
|
}
|
|
311
330
|
|
|
312
|
-
private async update(
|
|
313
|
-
|
|
331
|
+
private async update(
|
|
332
|
+
sql: string,
|
|
333
|
+
params: any[],
|
|
334
|
+
): Promise<{ changes: number }> {
|
|
335
|
+
const updateMatch = sql.match(
|
|
336
|
+
/UPDATE\s+(\w+)\s+SET\s+([^WHERE]+)(?:\s+WHERE\s+(.+))?/i,
|
|
337
|
+
);
|
|
314
338
|
if (!updateMatch) {
|
|
315
339
|
throw new Error(`Invalid UPDATE statement: ${sql}`);
|
|
316
340
|
}
|
|
@@ -342,13 +366,21 @@ export class Database {
|
|
|
342
366
|
if (whereClause) {
|
|
343
367
|
// Simple WHERE clause evaluation (basic implementation)
|
|
344
368
|
if (this.evaluateWhereClause(row, whereClause, params)) {
|
|
345
|
-
const updatedRow = {
|
|
369
|
+
const updatedRow = {
|
|
370
|
+
...row,
|
|
371
|
+
...updates,
|
|
372
|
+
updated_at: new Date().toISOString(),
|
|
373
|
+
};
|
|
346
374
|
await this.plures.put(getRowId(row), updatedRow);
|
|
347
375
|
changes++;
|
|
348
376
|
}
|
|
349
377
|
} else {
|
|
350
378
|
// Update all rows
|
|
351
|
-
const updatedRow = {
|
|
379
|
+
const updatedRow = {
|
|
380
|
+
...row,
|
|
381
|
+
...updates,
|
|
382
|
+
updated_at: new Date().toISOString(),
|
|
383
|
+
};
|
|
352
384
|
await this.plures.put(getRowId(row), updatedRow);
|
|
353
385
|
changes++;
|
|
354
386
|
}
|
|
@@ -361,7 +393,10 @@ export class Database {
|
|
|
361
393
|
return { changes };
|
|
362
394
|
}
|
|
363
395
|
|
|
364
|
-
private async delete(
|
|
396
|
+
private async delete(
|
|
397
|
+
sql: string,
|
|
398
|
+
params: any[],
|
|
399
|
+
): Promise<{ changes: number }> {
|
|
365
400
|
const deleteMatch = sql.match(/DELETE\s+FROM\s+(\w+)(?:\s+WHERE\s+(.+))?/i);
|
|
366
401
|
if (!deleteMatch) {
|
|
367
402
|
throw new Error(`Invalid DELETE statement: ${sql}`);
|
|
@@ -415,7 +450,9 @@ export class Database {
|
|
|
415
450
|
|
|
416
451
|
// Apply WHERE clause
|
|
417
452
|
if (whereClause) {
|
|
418
|
-
results = results.filter((row) =>
|
|
453
|
+
results = results.filter((row) =>
|
|
454
|
+
this.evaluateWhereClause(row, whereClause, params)
|
|
455
|
+
);
|
|
419
456
|
}
|
|
420
457
|
|
|
421
458
|
// Apply ORDER BY
|
|
@@ -449,7 +486,11 @@ export class Database {
|
|
|
449
486
|
return results;
|
|
450
487
|
}
|
|
451
488
|
|
|
452
|
-
private evaluateWhereClause(
|
|
489
|
+
private evaluateWhereClause(
|
|
490
|
+
row: RowRecord,
|
|
491
|
+
whereClause: string,
|
|
492
|
+
params: any[],
|
|
493
|
+
): boolean {
|
|
453
494
|
// Simple WHERE clause evaluation
|
|
454
495
|
// This is a basic implementation - in production, you'd want a proper SQL parser
|
|
455
496
|
|
|
@@ -504,7 +545,10 @@ function compareValues(a: unknown, b: unknown, desc = false): number {
|
|
|
504
545
|
if (typeof value === "string") {
|
|
505
546
|
return value;
|
|
506
547
|
}
|
|
507
|
-
if (
|
|
548
|
+
if (
|
|
549
|
+
typeof value === "number" || typeof value === "boolean" ||
|
|
550
|
+
typeof value === "bigint"
|
|
551
|
+
) {
|
|
508
552
|
return value.toString();
|
|
509
553
|
}
|
|
510
554
|
try {
|
|
@@ -545,7 +589,10 @@ export class PreparedStatement {
|
|
|
545
589
|
return await this.db.all(this.sql, params);
|
|
546
590
|
}
|
|
547
591
|
|
|
548
|
-
async each(
|
|
592
|
+
async each(
|
|
593
|
+
params: any[] = [],
|
|
594
|
+
callback: (row: any) => void,
|
|
595
|
+
): Promise<number> {
|
|
549
596
|
return await this.db.each(this.sql, params, callback);
|
|
550
597
|
}
|
|
551
598
|
|
|
@@ -27,7 +27,7 @@ export class Database extends PluresDBDatabase {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
configure(
|
|
30
|
+
configure(_option: string, _value: any): void {
|
|
31
31
|
// No-op for compatibility
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -35,7 +35,7 @@ export class Database extends PluresDBDatabase {
|
|
|
35
35
|
// No-op for compatibility
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
loadExtension(
|
|
38
|
+
loadExtension(_path: string, callback?: (err: Error | null) => void): void {
|
|
39
39
|
if (callback) {
|
|
40
40
|
callback(new Error("Extensions not supported in PluresDB"), null);
|
|
41
41
|
}
|
|
@@ -56,7 +56,9 @@ export class KvStorage {
|
|
|
56
56
|
|
|
57
57
|
async *listNodeHistory(id: string): AsyncIterable<NodeRecord> {
|
|
58
58
|
const kv = this.ensureKv();
|
|
59
|
-
for await (
|
|
59
|
+
for await (
|
|
60
|
+
const entry of kv.list<NodeRecord>({ prefix: ["history", id] })
|
|
61
|
+
) {
|
|
60
62
|
if (entry.value) yield entry.value;
|
|
61
63
|
}
|
|
62
64
|
}
|
|
@@ -24,7 +24,11 @@ Deno.test("put and get returns stored data", async () => {
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
Deno.test(
|
|
27
|
-
{
|
|
27
|
+
{
|
|
28
|
+
name: "subscription receives updates",
|
|
29
|
+
sanitizeOps: false,
|
|
30
|
+
sanitizeResources: false,
|
|
31
|
+
},
|
|
28
32
|
async () => {
|
|
29
33
|
const db = new GunDB();
|
|
30
34
|
try {
|
|
@@ -36,13 +40,15 @@ Deno.test(
|
|
|
36
40
|
const updated = new Promise((resolve) =>
|
|
37
41
|
db.on(
|
|
38
42
|
"user:bob",
|
|
39
|
-
(n) =>
|
|
40
|
-
|
|
43
|
+
(n) =>
|
|
44
|
+
n && (n.data as Record<string, unknown>).age === 42 &&
|
|
45
|
+
resolve(true),
|
|
46
|
+
)
|
|
41
47
|
);
|
|
42
48
|
await db.put("user:bob", { name: "Bob", age: 41 });
|
|
43
49
|
await db.put("user:bob", { name: "Bob", age: 42 });
|
|
44
50
|
const timeout = new Promise((_, rej) =>
|
|
45
|
-
setTimeout(() => rej(new Error("timeout: subscription")), 2000)
|
|
51
|
+
setTimeout(() => rej(new Error("timeout: subscription")), 2000)
|
|
46
52
|
);
|
|
47
53
|
await Promise.race([updated, timeout]);
|
|
48
54
|
} finally {
|
|
@@ -72,7 +78,11 @@ Deno.test("vector search returns relevant notes", async () => {
|
|
|
72
78
|
});
|
|
73
79
|
|
|
74
80
|
Deno.test(
|
|
75
|
-
{
|
|
81
|
+
{
|
|
82
|
+
name: "delete emits subscription with null",
|
|
83
|
+
sanitizeOps: false,
|
|
84
|
+
sanitizeResources: false,
|
|
85
|
+
},
|
|
76
86
|
async () => {
|
|
77
87
|
const db = new GunDB();
|
|
78
88
|
try {
|
|
@@ -83,11 +93,11 @@ Deno.test(
|
|
|
83
93
|
await db.ready(kvPath);
|
|
84
94
|
await db.put("user:carol", { name: "Carol" });
|
|
85
95
|
const deleted = new Promise((resolve) =>
|
|
86
|
-
db.on("user:carol", (n) => n === null && resolve(true))
|
|
96
|
+
db.on("user:carol", (n) => n === null && resolve(true))
|
|
87
97
|
);
|
|
88
98
|
await db.delete("user:carol");
|
|
89
99
|
const timeout = new Promise((_, rej) =>
|
|
90
|
-
setTimeout(() => rej(new Error("timeout: delete")), 2000)
|
|
100
|
+
setTimeout(() => rej(new Error("timeout: delete")), 2000)
|
|
91
101
|
);
|
|
92
102
|
await Promise.race([deleted, timeout]);
|
|
93
103
|
} finally {
|
|
@@ -121,7 +131,7 @@ Deno.test(
|
|
|
121
131
|
await dbA.put("mesh:one", { text: "hello from A" });
|
|
122
132
|
|
|
123
133
|
const receivedSnapshot = new Promise((resolve) =>
|
|
124
|
-
dbB.on("mesh:one", (n) => n && resolve(true))
|
|
134
|
+
dbB.on("mesh:one", (n) => n && resolve(true))
|
|
125
135
|
);
|
|
126
136
|
dbB.connect(serverUrl);
|
|
127
137
|
await receivedSnapshot;
|
|
@@ -129,8 +139,10 @@ Deno.test(
|
|
|
129
139
|
const receivedOnA = new Promise((resolve) =>
|
|
130
140
|
dbA.on(
|
|
131
141
|
"mesh:fromB",
|
|
132
|
-
(n) =>
|
|
133
|
-
|
|
142
|
+
(n) =>
|
|
143
|
+
n && (n.data as Record<string, unknown>).who === "B" &&
|
|
144
|
+
resolve(true),
|
|
145
|
+
)
|
|
134
146
|
);
|
|
135
147
|
await dbB.put("mesh:fromB", { who: "B", text: "hi A" });
|
|
136
148
|
await receivedOnA;
|
|
@@ -204,7 +216,12 @@ Deno.test("CRDT merge: equal timestamps deterministic merge", () => {
|
|
|
204
216
|
const merged = mergeNodes(local, incoming);
|
|
205
217
|
assertEquals(merged.id, "n1");
|
|
206
218
|
assertEquals(merged.timestamp, t);
|
|
207
|
-
assertEquals(merged.data, {
|
|
219
|
+
assertEquals(merged.data, {
|
|
220
|
+
a: 1,
|
|
221
|
+
shared: 2,
|
|
222
|
+
b: 2,
|
|
223
|
+
nested: { x: 1, y: 2, z: 3 },
|
|
224
|
+
});
|
|
208
225
|
assertEquals(merged.type, "TypeA");
|
|
209
226
|
assertEquals(merged.vector, [0.1, 0.2]);
|
|
210
227
|
assertEquals(merged.vectorClock.peerA, 2);
|
|
@@ -237,7 +254,11 @@ Deno.test("CRDT merge: LWW on differing timestamps", () => {
|
|
|
237
254
|
});
|
|
238
255
|
|
|
239
256
|
Deno.test(
|
|
240
|
-
{
|
|
257
|
+
{
|
|
258
|
+
name: "off stops receiving events",
|
|
259
|
+
sanitizeOps: false,
|
|
260
|
+
sanitizeResources: false,
|
|
261
|
+
},
|
|
241
262
|
async () => {
|
|
242
263
|
const db = new GunDB();
|
|
243
264
|
try {
|
|
@@ -265,7 +286,10 @@ Deno.test(
|
|
|
265
286
|
Deno.test("type system helpers: setType + instancesOf", async () => {
|
|
266
287
|
const db = new GunDB();
|
|
267
288
|
try {
|
|
268
|
-
const kvPath = await Deno.makeTempFile({
|
|
289
|
+
const kvPath = await Deno.makeTempFile({
|
|
290
|
+
prefix: "kv_",
|
|
291
|
+
suffix: ".sqlite",
|
|
292
|
+
});
|
|
269
293
|
await db.ready(kvPath);
|
|
270
294
|
await db.put("t:1", { name: "Alice" });
|
|
271
295
|
await db.setType("t:1", "Person");
|
|
@@ -33,7 +33,12 @@
|
|
|
33
33
|
"profile": {
|
|
34
34
|
"bio": "Product manager with a focus on developer tools",
|
|
35
35
|
"location": "Seattle, WA",
|
|
36
|
-
"interests": [
|
|
36
|
+
"interests": [
|
|
37
|
+
"Product",
|
|
38
|
+
"Strategy",
|
|
39
|
+
"Developer Experience",
|
|
40
|
+
"Leadership"
|
|
41
|
+
]
|
|
37
42
|
}
|
|
38
43
|
}
|
|
39
44
|
],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import { assertEquals, assertExists } from "jsr:@std/assert@1.0.14";
|
|
3
3
|
import { GunDB } from "../../core/database.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { type ApiServerHandle, startApiServer } from "../../http/api-server.ts";
|
|
5
5
|
|
|
6
6
|
function randomPort(): number {
|
|
7
7
|
return 18000 + Math.floor(Math.random() * 10000);
|
|
@@ -88,8 +88,8 @@ Deno.test("API Server - Vector Search Endpoint", async () => {
|
|
|
88
88
|
}),
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
assertEquals(searchResponse.status, 200);
|
|
92
|
+
const results = await searchResponse.json();
|
|
93
93
|
assertExists(results);
|
|
94
94
|
assertEquals(Array.isArray(results), true);
|
|
95
95
|
} finally {
|
|
@@ -119,7 +119,10 @@ Deno.test("API Server - WebSocket Connection", async () => {
|
|
|
119
119
|
const ws = new WebSocket(wsUrl);
|
|
120
120
|
|
|
121
121
|
const connectionPromise = new Promise((resolve, reject) => {
|
|
122
|
-
const timer = setTimeout(
|
|
122
|
+
const timer = setTimeout(
|
|
123
|
+
() => reject(new Error("Connection timeout")),
|
|
124
|
+
5000,
|
|
125
|
+
);
|
|
123
126
|
ws.onopen = () => {
|
|
124
127
|
clearTimeout(timer);
|
|
125
128
|
resolve(true);
|
|
@@ -221,10 +224,109 @@ Deno.test("API Server - CORS Headers", async () => {
|
|
|
221
224
|
},
|
|
222
225
|
});
|
|
223
226
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
227
|
+
assertEquals(optionsResponse.status, 200);
|
|
228
|
+
assertExists(optionsResponse.headers.get("Access-Control-Allow-Origin"));
|
|
229
|
+
assertExists(optionsResponse.headers.get("Access-Control-Allow-Methods"));
|
|
230
|
+
await optionsResponse.body?.cancel();
|
|
231
|
+
} finally {
|
|
232
|
+
api?.close();
|
|
233
|
+
await db.close();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
Deno.test("API Server - P2P API Endpoints", async () => {
|
|
238
|
+
const db = new GunDB();
|
|
239
|
+
let api: ApiServerHandle | null = null;
|
|
240
|
+
try {
|
|
241
|
+
const kvPath = await Deno.makeTempFile({
|
|
242
|
+
prefix: "kv_",
|
|
243
|
+
suffix: ".sqlite",
|
|
244
|
+
});
|
|
245
|
+
await db.ready(kvPath);
|
|
246
|
+
|
|
247
|
+
const port = randomPort();
|
|
248
|
+
const apiPort = port + 1;
|
|
249
|
+
db.serve({ port });
|
|
250
|
+
api = startApiServer({ port: apiPort, db });
|
|
251
|
+
|
|
252
|
+
const baseUrl = `http://localhost:${apiPort}`;
|
|
253
|
+
|
|
254
|
+
// Test createIdentity endpoint
|
|
255
|
+
const identityResponse = await fetch(`${baseUrl}/api/identity`, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers: { "Content-Type": "application/json" },
|
|
258
|
+
body: JSON.stringify({ name: "John Doe", email: "john@example.com" }),
|
|
259
|
+
});
|
|
260
|
+
assertEquals(identityResponse.status, 200);
|
|
261
|
+
const identity = await identityResponse.json();
|
|
262
|
+
assertExists(identity.id);
|
|
263
|
+
assertExists(identity.publicKey);
|
|
264
|
+
assertEquals(identity.name, "John Doe");
|
|
265
|
+
assertEquals(identity.email, "john@example.com");
|
|
266
|
+
|
|
267
|
+
// Test searchPeers endpoint
|
|
268
|
+
const peersResponse = await fetch(`${baseUrl}/api/peers/search?q=developer`);
|
|
269
|
+
assertEquals(peersResponse.status, 200);
|
|
270
|
+
const peers = await peersResponse.json();
|
|
271
|
+
assertEquals(Array.isArray(peers), true);
|
|
272
|
+
|
|
273
|
+
// Test shareNode endpoint
|
|
274
|
+
const shareResponse = await fetch(`${baseUrl}/api/share`, {
|
|
275
|
+
method: "POST",
|
|
276
|
+
headers: { "Content-Type": "application/json" },
|
|
277
|
+
body: JSON.stringify({
|
|
278
|
+
nodeId: "node:123",
|
|
279
|
+
targetPeerId: "peer:456",
|
|
280
|
+
accessLevel: "read-only",
|
|
281
|
+
}),
|
|
282
|
+
});
|
|
283
|
+
assertEquals(shareResponse.status, 200);
|
|
284
|
+
const shareData = await shareResponse.json();
|
|
285
|
+
assertExists(shareData.sharedNodeId);
|
|
286
|
+
assertEquals(shareData.nodeId, "node:123");
|
|
287
|
+
assertEquals(shareData.targetPeerId, "peer:456");
|
|
288
|
+
assertEquals(shareData.accessLevel, "read-only");
|
|
289
|
+
|
|
290
|
+
// Test acceptSharedNode endpoint
|
|
291
|
+
const acceptResponse = await fetch(`${baseUrl}/api/share/accept`, {
|
|
292
|
+
method: "POST",
|
|
293
|
+
headers: { "Content-Type": "application/json" },
|
|
294
|
+
body: JSON.stringify({ sharedNodeId: "shared:789" }),
|
|
295
|
+
});
|
|
296
|
+
assertEquals(acceptResponse.status, 200);
|
|
297
|
+
const acceptData = await acceptResponse.json();
|
|
298
|
+
assertEquals(acceptData.success, true);
|
|
299
|
+
assertEquals(acceptData.sharedNodeId, "shared:789");
|
|
300
|
+
|
|
301
|
+
// Test addDevice endpoint
|
|
302
|
+
const deviceResponse = await fetch(`${baseUrl}/api/devices`, {
|
|
303
|
+
method: "POST",
|
|
304
|
+
headers: { "Content-Type": "application/json" },
|
|
305
|
+
body: JSON.stringify({ name: "My Laptop", type: "laptop" }),
|
|
306
|
+
});
|
|
307
|
+
assertEquals(deviceResponse.status, 200);
|
|
308
|
+
const device = await deviceResponse.json();
|
|
309
|
+
assertExists(device.id);
|
|
310
|
+
assertEquals(device.name, "My Laptop");
|
|
311
|
+
assertEquals(device.type, "laptop");
|
|
312
|
+
assertEquals(device.status, "online");
|
|
313
|
+
|
|
314
|
+
// Test syncWithDevice endpoint
|
|
315
|
+
const syncResponse = await fetch(`${baseUrl}/api/devices/sync`, {
|
|
316
|
+
method: "POST",
|
|
317
|
+
headers: { "Content-Type": "application/json" },
|
|
318
|
+
body: JSON.stringify({ deviceId: "device:123" }),
|
|
319
|
+
});
|
|
320
|
+
assertEquals(syncResponse.status, 200);
|
|
321
|
+
const syncData = await syncResponse.json();
|
|
322
|
+
assertEquals(syncData.success, true);
|
|
323
|
+
assertEquals(syncData.deviceId, "device:123");
|
|
324
|
+
|
|
325
|
+
// Test GET devices list
|
|
326
|
+
const devicesListResponse = await fetch(`${baseUrl}/api/devices`);
|
|
327
|
+
assertEquals(devicesListResponse.status, 200);
|
|
328
|
+
const devicesList = await devicesListResponse.json();
|
|
329
|
+
assertEquals(Array.isArray(devicesList), true);
|
|
228
330
|
} finally {
|
|
229
331
|
api?.close();
|
|
230
332
|
await db.close();
|
|
@@ -84,7 +84,10 @@ meshTest("Mesh Network - Basic Connection and Sync", async () => {
|
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
dbB.connect(serverUrl);
|
|
87
|
-
await withTimeout(
|
|
87
|
+
await withTimeout(
|
|
88
|
+
receivedData as Promise<unknown>,
|
|
89
|
+
"Timed out waiting for initial mesh sync",
|
|
90
|
+
);
|
|
88
91
|
|
|
89
92
|
// Verify data was received
|
|
90
93
|
const syncedData = await dbB.get("mesh:test");
|
|
@@ -280,7 +283,10 @@ meshTest("Mesh Network - Connection Error Handling", async () => {
|
|
|
280
283
|
const db = new GunDB();
|
|
281
284
|
|
|
282
285
|
try {
|
|
283
|
-
const kvPath = await Deno.makeTempFile({
|
|
286
|
+
const kvPath = await Deno.makeTempFile({
|
|
287
|
+
prefix: "kv_",
|
|
288
|
+
suffix: ".sqlite",
|
|
289
|
+
});
|
|
284
290
|
await db.ready(kvPath);
|
|
285
291
|
|
|
286
292
|
// Try to connect to non-existent server
|
|
@@ -4,14 +4,18 @@ import type { Rule } from "../logic/rules.ts";
|
|
|
4
4
|
Deno.test("rule engine classification: Person.age >= 18 -> adult = true", async () => {
|
|
5
5
|
const db = new GunDB();
|
|
6
6
|
try {
|
|
7
|
-
const kvPath = await Deno.makeTempFile({
|
|
7
|
+
const kvPath = await Deno.makeTempFile({
|
|
8
|
+
prefix: "kv_",
|
|
9
|
+
suffix: ".sqlite",
|
|
10
|
+
});
|
|
8
11
|
await db.ready(kvPath);
|
|
9
12
|
|
|
10
13
|
const rule: Rule = {
|
|
11
14
|
name: "adultClassifier",
|
|
12
15
|
whenType: "Person",
|
|
13
16
|
predicate: (node) =>
|
|
14
|
-
typeof (node.data as any).age === "number" &&
|
|
17
|
+
typeof (node.data as any).age === "number" &&
|
|
18
|
+
(node.data as any).age >= 18,
|
|
15
19
|
action: async (ctx, node) => {
|
|
16
20
|
const data = { ...(node.data as Record<string, unknown>), adult: true };
|
|
17
21
|
await ctx.db.put(node.id, data);
|
|
@@ -118,8 +118,10 @@ Deno.test("Performance - Vector Search Operations", async () => {
|
|
|
118
118
|
const count = 500;
|
|
119
119
|
for (let i = 0; i < count; i++) {
|
|
120
120
|
await db.put(`doc:${i}`, {
|
|
121
|
-
text:
|
|
122
|
-
|
|
121
|
+
text:
|
|
122
|
+
`Document ${i} about machine learning and artificial intelligence`,
|
|
123
|
+
content:
|
|
124
|
+
`This is document number ${i} containing information about AI and ML algorithms`,
|
|
123
125
|
});
|
|
124
126
|
}
|
|
125
127
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
assertEquals,
|
|
4
|
+
assertExists,
|
|
5
|
+
assertRejects,
|
|
6
|
+
} from "jsr:@std/assert@1.0.14";
|
|
3
7
|
import { GunDB } from "../../core/database.ts";
|
|
4
8
|
|
|
5
9
|
Deno.test("Security - SQL Injection Prevention", async () => {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
assertEquals,
|
|
4
|
+
assertExists,
|
|
5
|
+
assertRejects,
|
|
6
|
+
} from "jsr:@std/assert@1.0.14";
|
|
3
7
|
import { GunDB } from "../../core/database.ts";
|
|
4
8
|
import { mergeNodes } from "../../core/crdt.ts";
|
|
5
9
|
import type { NodeRecord } from "../../types/index.ts";
|
|
@@ -104,7 +108,9 @@ Deno.test("Core Database - Vector Search", async () => {
|
|
|
104
108
|
await db.ready(kvPath);
|
|
105
109
|
|
|
106
110
|
// Add documents with text content
|
|
107
|
-
await db.put("doc:1", {
|
|
111
|
+
await db.put("doc:1", {
|
|
112
|
+
text: "Machine learning and artificial intelligence",
|
|
113
|
+
});
|
|
108
114
|
await db.put("doc:2", { text: "Cooking recipes and food preparation" });
|
|
109
115
|
await db.put("doc:3", { text: "Deep learning neural networks" });
|
|
110
116
|
|
|
@@ -184,7 +190,11 @@ Deno.test("Core Database - Error Handling", async () => {
|
|
|
184
190
|
const db = new GunDB();
|
|
185
191
|
|
|
186
192
|
// Test operations before ready
|
|
187
|
-
await assertRejects(
|
|
193
|
+
await assertRejects(
|
|
194
|
+
() => db.put("test", { value: 1 }),
|
|
195
|
+
Error,
|
|
196
|
+
"Database not ready",
|
|
197
|
+
);
|
|
188
198
|
|
|
189
199
|
await assertRejects(() => db.get("test"), Error, "Database not ready");
|
|
190
200
|
|
|
@@ -100,7 +100,7 @@ Deno.test("Subscriptions - Multiple Subscribers", async () => {
|
|
|
100
100
|
}
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
Deno.test("Subscriptions - Error Handling",
|
|
103
|
+
Deno.test("Subscriptions - Error Handling", () => {
|
|
104
104
|
const db = new GunDB();
|
|
105
105
|
|
|
106
106
|
// Test subscription before ready
|