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.
Files changed (84) hide show
  1. package/LICENSE +72 -0
  2. package/README.md +322 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/cli.d.ts +7 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +253 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/node-index.d.ts +52 -0
  9. package/dist/node-index.d.ts.map +1 -0
  10. package/dist/node-index.js +359 -0
  11. package/dist/node-index.js.map +1 -0
  12. package/dist/node-wrapper.d.ts +44 -0
  13. package/dist/node-wrapper.d.ts.map +1 -0
  14. package/dist/node-wrapper.js +294 -0
  15. package/dist/node-wrapper.js.map +1 -0
  16. package/dist/types/index.d.ts +28 -0
  17. package/dist/types/index.d.ts.map +1 -0
  18. package/dist/types/index.js +3 -0
  19. package/dist/types/index.js.map +1 -0
  20. package/dist/types/node-types.d.ts +59 -0
  21. package/dist/types/node-types.d.ts.map +1 -0
  22. package/dist/types/node-types.js +6 -0
  23. package/dist/types/node-types.js.map +1 -0
  24. package/dist/vscode/extension.d.ts +81 -0
  25. package/dist/vscode/extension.d.ts.map +1 -0
  26. package/dist/vscode/extension.js +309 -0
  27. package/dist/vscode/extension.js.map +1 -0
  28. package/examples/basic-usage.d.ts +2 -0
  29. package/examples/basic-usage.d.ts.map +1 -0
  30. package/examples/basic-usage.js +26 -0
  31. package/examples/basic-usage.js.map +1 -0
  32. package/examples/basic-usage.ts +29 -0
  33. package/examples/vscode-extension-example/README.md +95 -0
  34. package/examples/vscode-extension-example/package.json +49 -0
  35. package/examples/vscode-extension-example/src/extension.ts +163 -0
  36. package/examples/vscode-extension-example/tsconfig.json +12 -0
  37. package/examples/vscode-extension-integration.d.ts +24 -0
  38. package/examples/vscode-extension-integration.d.ts.map +1 -0
  39. package/examples/vscode-extension-integration.js +285 -0
  40. package/examples/vscode-extension-integration.js.map +1 -0
  41. package/examples/vscode-extension-integration.ts +41 -0
  42. package/package.json +115 -0
  43. package/scripts/compiled-crud-verify.ts +28 -0
  44. package/scripts/dogfood.ts +258 -0
  45. package/scripts/postinstall.js +155 -0
  46. package/scripts/run-tests.ts +175 -0
  47. package/scripts/setup-libclang.ps1 +209 -0
  48. package/src/benchmarks/memory-benchmarks.ts +316 -0
  49. package/src/benchmarks/run-benchmarks.ts +293 -0
  50. package/src/cli.ts +231 -0
  51. package/src/config.ts +49 -0
  52. package/src/core/crdt.ts +104 -0
  53. package/src/core/database.ts +494 -0
  54. package/src/healthcheck.ts +156 -0
  55. package/src/http/api-server.ts +334 -0
  56. package/src/index.ts +28 -0
  57. package/src/logic/rules.ts +44 -0
  58. package/src/main.rs +3 -0
  59. package/src/main.ts +190 -0
  60. package/src/network/websocket-server.ts +115 -0
  61. package/src/node-index.ts +385 -0
  62. package/src/node-wrapper.ts +320 -0
  63. package/src/sqlite-compat.ts +586 -0
  64. package/src/sqlite3-compat.ts +55 -0
  65. package/src/storage/kv-storage.ts +71 -0
  66. package/src/tests/core.test.ts +281 -0
  67. package/src/tests/fixtures/performance-data.json +71 -0
  68. package/src/tests/fixtures/test-data.json +124 -0
  69. package/src/tests/integration/api-server.test.ts +232 -0
  70. package/src/tests/integration/mesh-network.test.ts +297 -0
  71. package/src/tests/logic.test.ts +30 -0
  72. package/src/tests/performance/load.test.ts +288 -0
  73. package/src/tests/security/input-validation.test.ts +282 -0
  74. package/src/tests/unit/core.test.ts +216 -0
  75. package/src/tests/unit/subscriptions.test.ts +135 -0
  76. package/src/tests/unit/vector-search.test.ts +173 -0
  77. package/src/tests/vscode_extension_test.ts +253 -0
  78. package/src/types/index.ts +32 -0
  79. package/src/types/node-types.ts +66 -0
  80. package/src/util/debug.ts +14 -0
  81. package/src/vector/index.ts +59 -0
  82. package/src/vscode/extension.ts +364 -0
  83. package/web/README.md +27 -0
  84. package/web/svelte/package.json +31 -0
@@ -0,0 +1,281 @@
1
+ // @ts-nocheck
2
+ import { assertEquals } from "jsr:@std/assert@1.0.14";
3
+ import { GunDB } from "../core/database.ts";
4
+ import { mergeNodes } from "../core/crdt.ts";
5
+ import type { NodeRecord } from "../types/index.ts";
6
+ import type { Rule } from "../logic/rules.ts";
7
+
8
+ Deno.test("put and get returns stored data", async () => {
9
+ const db = new GunDB();
10
+ try {
11
+ const kvPath = await Deno.makeTempFile({
12
+ prefix: "kv_",
13
+ suffix: ".sqlite",
14
+ });
15
+ await db.ready(kvPath);
16
+ const user = { name: "Alice", age: 30 } as const;
17
+ await db.put("user:alice", user as unknown as Record<string, unknown>);
18
+ const got = await db.get<typeof user>("user:alice");
19
+ assertEquals(got?.name, "Alice");
20
+ assertEquals(got?.age, 30);
21
+ } finally {
22
+ await db.close();
23
+ }
24
+ });
25
+
26
+ Deno.test(
27
+ { name: "subscription receives updates", sanitizeOps: false, sanitizeResources: false },
28
+ async () => {
29
+ const db = new GunDB();
30
+ try {
31
+ const kvPath = await Deno.makeTempFile({
32
+ prefix: "kv_",
33
+ suffix: ".sqlite",
34
+ });
35
+ await db.ready(kvPath);
36
+ const updated = new Promise((resolve) =>
37
+ db.on(
38
+ "user:bob",
39
+ (n) => n && (n.data as Record<string, unknown>).age === 42 && resolve(true),
40
+ ),
41
+ );
42
+ await db.put("user:bob", { name: "Bob", age: 41 });
43
+ await db.put("user:bob", { name: "Bob", age: 42 });
44
+ const timeout = new Promise((_, rej) =>
45
+ setTimeout(() => rej(new Error("timeout: subscription")), 2000),
46
+ );
47
+ await Promise.race([updated, timeout]);
48
+ } finally {
49
+ await db.close();
50
+ }
51
+ },
52
+ );
53
+
54
+ Deno.test("vector search returns relevant notes", async () => {
55
+ const db = new GunDB();
56
+ try {
57
+ const kvPath = await Deno.makeTempFile({
58
+ prefix: "kv_",
59
+ suffix: ".sqlite",
60
+ });
61
+ await db.ready(kvPath);
62
+ await db.put("note:london1", { text: "Museums and galleries in London" });
63
+ await db.put("note:newyork1", { text: "Pizza places in New York" });
64
+ const results = await db.vectorSearch("London", 1);
65
+ if (results.length === 0) throw new Error("No vector results");
66
+ if (results[0].id !== "note:london1") {
67
+ throw new Error(`Expected note:london1 got ${results[0].id}`);
68
+ }
69
+ } finally {
70
+ await db.close();
71
+ }
72
+ });
73
+
74
+ Deno.test(
75
+ { name: "delete emits subscription with null", sanitizeOps: false, sanitizeResources: false },
76
+ async () => {
77
+ const db = new GunDB();
78
+ try {
79
+ const kvPath = await Deno.makeTempFile({
80
+ prefix: "kv_",
81
+ suffix: ".sqlite",
82
+ });
83
+ await db.ready(kvPath);
84
+ await db.put("user:carol", { name: "Carol" });
85
+ const deleted = new Promise((resolve) =>
86
+ db.on("user:carol", (n) => n === null && resolve(true)),
87
+ );
88
+ await db.delete("user:carol");
89
+ const timeout = new Promise((_, rej) =>
90
+ setTimeout(() => rej(new Error("timeout: delete")), 2000),
91
+ );
92
+ await Promise.race([deleted, timeout]);
93
+ } finally {
94
+ await db.close();
95
+ }
96
+ },
97
+ );
98
+
99
+ Deno.test(
100
+ {
101
+ name: "mesh snapshot sync and propagation",
102
+ sanitizeOps: false,
103
+ sanitizeResources: false,
104
+ },
105
+ async () => {
106
+ function randomPort() {
107
+ return 18000 + Math.floor(Math.random() * 10000);
108
+ }
109
+ const port = randomPort();
110
+ const serverUrl = `ws://localhost:${port}`;
111
+
112
+ const dbA = new GunDB();
113
+ const dbB = new GunDB();
114
+ try {
115
+ const kvA = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
116
+ const kvB = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
117
+ await dbA.ready(kvA);
118
+ await dbB.ready(kvB);
119
+ await dbA.serve({ port });
120
+
121
+ await dbA.put("mesh:one", { text: "hello from A" });
122
+
123
+ const receivedSnapshot = new Promise((resolve) =>
124
+ dbB.on("mesh:one", (n) => n && resolve(true)),
125
+ );
126
+ dbB.connect(serverUrl);
127
+ await receivedSnapshot;
128
+
129
+ const receivedOnA = new Promise((resolve) =>
130
+ dbA.on(
131
+ "mesh:fromB",
132
+ (n) => n && (n.data as Record<string, unknown>).who === "B" && resolve(true),
133
+ ),
134
+ );
135
+ await dbB.put("mesh:fromB", { who: "B", text: "hi A" });
136
+ await receivedOnA;
137
+ } finally {
138
+ await dbB.close();
139
+ await dbA.close();
140
+ }
141
+ },
142
+ );
143
+
144
+ // --- Additional tests to cover remaining checklist items ---
145
+
146
+ Deno.test("persists across restarts", async () => {
147
+ const kvPath = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
148
+ const id = "persist:one";
149
+
150
+ const db1 = new GunDB();
151
+ await db1.ready(kvPath);
152
+ await db1.put(id, { value: 123 });
153
+ await db1.close();
154
+
155
+ const db2 = new GunDB();
156
+ await db2.ready(kvPath);
157
+ const got = await db2.get<{ value: number }>(id);
158
+ await db2.close();
159
+
160
+ if (!got) throw new Error("Expected value after restart");
161
+ assertEquals(got.value, 123);
162
+ });
163
+
164
+ Deno.test("vector clock increments on local puts", async () => {
165
+ const kvPath = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
166
+ const id = "vc:counter";
167
+ const db = new GunDB();
168
+ await db.ready(kvPath);
169
+ await db.put(id, { n: 1 });
170
+ await db.put(id, { n: 2 });
171
+ await db.put(id, { n: 3 });
172
+ await db.close();
173
+
174
+ // Inspect underlying record
175
+ const { KvStorage } = await import("../storage/kv-storage.ts");
176
+ const kv = new KvStorage();
177
+ await kv.open(kvPath);
178
+ const node = await kv.getNode(id);
179
+ await kv.close();
180
+ if (!node) throw new Error("Missing node for vector clock check");
181
+ const clockValues = Object.values(node.vectorClock);
182
+ assertEquals(clockValues.length, 1);
183
+ assertEquals(clockValues[0], 3);
184
+ });
185
+
186
+ Deno.test("CRDT merge: equal timestamps deterministic merge", () => {
187
+ const t = Date.now();
188
+ const local: NodeRecord = {
189
+ id: "n1",
190
+ data: { a: 1, shared: 1, nested: { x: 1, y: 1 }, toDelete: 1 },
191
+ vector: [0.1, 0.2],
192
+ type: "TypeA",
193
+ timestamp: t,
194
+ vectorClock: { peerA: 2 },
195
+ };
196
+ const incoming: NodeRecord = {
197
+ id: "n1",
198
+ data: { b: 2, shared: 2, nested: { y: 2, z: 3 }, toDelete: null },
199
+ // vector and type intentionally undefined to test fallback
200
+ timestamp: t,
201
+ vectorClock: { peerB: 3 },
202
+ } as unknown as NodeRecord;
203
+
204
+ const merged = mergeNodes(local, incoming);
205
+ assertEquals(merged.id, "n1");
206
+ assertEquals(merged.timestamp, t);
207
+ assertEquals(merged.data, { a: 1, shared: 2, b: 2, nested: { x: 1, y: 2, z: 3 } });
208
+ assertEquals(merged.type, "TypeA");
209
+ assertEquals(merged.vector, [0.1, 0.2]);
210
+ assertEquals(merged.vectorClock.peerA, 2);
211
+ assertEquals(merged.vectorClock.peerB, 3);
212
+ });
213
+
214
+ Deno.test("CRDT merge: LWW on differing timestamps", () => {
215
+ const t1 = 1000;
216
+ const t2 = 2000;
217
+ const base: NodeRecord = {
218
+ id: "n2",
219
+ data: { a: 1 },
220
+ timestamp: t1,
221
+ vectorClock: { p1: 1 },
222
+ } as unknown as NodeRecord;
223
+ const newer: NodeRecord = {
224
+ id: "n2",
225
+ data: { a: 999, b: 2 },
226
+ timestamp: t2,
227
+ vectorClock: { p2: 1 },
228
+ } as unknown as NodeRecord;
229
+
230
+ const up = mergeNodes(base, newer);
231
+ assertEquals(up.data, { a: 999, b: 2 });
232
+ assertEquals(up.timestamp, t2);
233
+
234
+ const down = mergeNodes(newer, base);
235
+ assertEquals(down.data, { a: 999, b: 2 });
236
+ assertEquals(down.timestamp, t2);
237
+ });
238
+
239
+ Deno.test(
240
+ { name: "off stops receiving events", sanitizeOps: false, sanitizeResources: false },
241
+ async () => {
242
+ const db = new GunDB();
243
+ try {
244
+ const kvPath = await Deno.makeTempFile({
245
+ prefix: "kv_",
246
+ suffix: ".sqlite",
247
+ });
248
+ await db.ready(kvPath);
249
+ let called = false;
250
+ const id = "user:dave";
251
+ const cb = () => {
252
+ called = true;
253
+ };
254
+ db.on(id, cb);
255
+ db.off(id, cb);
256
+ await db.put(id, { name: "Dave" });
257
+ await new Promise((r) => setTimeout(r, 200));
258
+ assertEquals(called, false);
259
+ } finally {
260
+ await db.close();
261
+ }
262
+ },
263
+ );
264
+
265
+ Deno.test("type system helpers: setType + instancesOf", async () => {
266
+ const db = new GunDB();
267
+ try {
268
+ const kvPath = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
269
+ await db.ready(kvPath);
270
+ await db.put("t:1", { name: "Alice" });
271
+ await db.setType("t:1", "Person");
272
+ await db.put("t:2", { name: "Acme" });
273
+ await db.setType("t:2", "Company");
274
+ const people = await db.instancesOf("Person");
275
+ if (people.length !== 1 || people[0].id !== "t:1") {
276
+ throw new Error("Expected exactly one Person: t:1");
277
+ }
278
+ } finally {
279
+ await db.close();
280
+ }
281
+ });
@@ -0,0 +1,71 @@
1
+ {
2
+ "load_test_scenarios": [
3
+ {
4
+ "name": "small_records",
5
+ "description": "Small records (100 bytes each)",
6
+ "record_size": 100,
7
+ "record_count": 1000,
8
+ "expected_ops_per_sec": 1000
9
+ },
10
+ {
11
+ "name": "medium_records",
12
+ "description": "Medium records (1KB each)",
13
+ "record_size": 1024,
14
+ "record_count": 1000,
15
+ "expected_ops_per_sec": 500
16
+ },
17
+ {
18
+ "name": "large_records",
19
+ "description": "Large records (10KB each)",
20
+ "record_size": 10240,
21
+ "record_count": 100,
22
+ "expected_ops_per_sec": 100
23
+ },
24
+ {
25
+ "name": "vector_data",
26
+ "description": "Vector data (100 dimensions)",
27
+ "vector_dimensions": 100,
28
+ "record_count": 500,
29
+ "expected_ops_per_sec": 200
30
+ }
31
+ ],
32
+ "memory_limits": {
33
+ "max_record_size": 10485760,
34
+ "max_subscriptions": 10000,
35
+ "max_memory_usage_mb": 100,
36
+ "memory_per_record_bytes": 5000
37
+ },
38
+ "performance_thresholds": {
39
+ "crud_operations_per_sec": 1000,
40
+ "vector_search_per_sec": 100,
41
+ "subscription_updates_per_sec": 500,
42
+ "network_connections_per_sec": 50,
43
+ "max_response_time_ms": 100
44
+ },
45
+ "security_test_cases": [
46
+ {
47
+ "name": "sql_injection",
48
+ "payloads": [
49
+ "'; DROP TABLE users; --",
50
+ "' OR '1'='1",
51
+ "'; INSERT INTO users VALUES ('hacker', 'password'); --"
52
+ ]
53
+ },
54
+ {
55
+ "name": "xss_attacks",
56
+ "payloads": [
57
+ "<script>alert('xss')</script>",
58
+ "javascript:alert('xss')",
59
+ "<img src=x onerror=alert('xss')>"
60
+ ]
61
+ },
62
+ {
63
+ "name": "path_traversal",
64
+ "payloads": [
65
+ "../../../etc/passwd",
66
+ "..\\..\\..\\windows\\system32\\drivers\\etc\\hosts",
67
+ "/etc/passwd"
68
+ ]
69
+ }
70
+ ]
71
+ }
@@ -0,0 +1,124 @@
1
+ {
2
+ "users": [
3
+ {
4
+ "id": "user:1",
5
+ "name": "Alice Johnson",
6
+ "email": "alice@example.com",
7
+ "age": 30,
8
+ "type": "Person",
9
+ "profile": {
10
+ "bio": "Software engineer passionate about machine learning",
11
+ "location": "San Francisco, CA",
12
+ "interests": ["AI", "ML", "Rust", "TypeScript"]
13
+ }
14
+ },
15
+ {
16
+ "id": "user:2",
17
+ "name": "Bob Smith",
18
+ "email": "bob@example.com",
19
+ "age": 25,
20
+ "type": "Person",
21
+ "profile": {
22
+ "bio": "Data scientist working on NLP applications",
23
+ "location": "New York, NY",
24
+ "interests": ["NLP", "Python", "Statistics", "Research"]
25
+ }
26
+ },
27
+ {
28
+ "id": "user:3",
29
+ "name": "Carol Davis",
30
+ "email": "carol@example.com",
31
+ "age": 35,
32
+ "type": "Person",
33
+ "profile": {
34
+ "bio": "Product manager with a focus on developer tools",
35
+ "location": "Seattle, WA",
36
+ "interests": ["Product", "Strategy", "Developer Experience", "Leadership"]
37
+ }
38
+ }
39
+ ],
40
+ "companies": [
41
+ {
42
+ "id": "company:1",
43
+ "name": "TechCorp Inc",
44
+ "type": "Company",
45
+ "industry": "Technology",
46
+ "employees": 500,
47
+ "location": "San Francisco, CA",
48
+ "description": "Leading provider of AI-powered solutions"
49
+ },
50
+ {
51
+ "id": "company:2",
52
+ "name": "DataFlow Systems",
53
+ "type": "Company",
54
+ "industry": "Data Analytics",
55
+ "employees": 150,
56
+ "location": "New York, NY",
57
+ "description": "Specialized in real-time data processing and analytics"
58
+ }
59
+ ],
60
+ "documents": [
61
+ {
62
+ "id": "doc:1",
63
+ "title": "Introduction to Machine Learning",
64
+ "text": "Machine learning is a subset of artificial intelligence that focuses on algorithms that can learn from data without being explicitly programmed.",
65
+ "content": "This comprehensive guide covers the fundamentals of machine learning, including supervised learning, unsupervised learning, and reinforcement learning techniques.",
66
+ "type": "Document",
67
+ "tags": ["machine learning", "AI", "tutorial", "beginner"],
68
+ "author": "user:1",
69
+ "created": "2024-01-15T10:00:00Z"
70
+ },
71
+ {
72
+ "id": "doc:2",
73
+ "title": "Advanced Neural Networks",
74
+ "text": "Deep learning neural networks have revolutionized the field of artificial intelligence with their ability to process complex patterns in data.",
75
+ "content": "This advanced tutorial explores deep learning architectures including CNNs, RNNs, and Transformers, with practical implementation examples.",
76
+ "type": "Document",
77
+ "tags": ["deep learning", "neural networks", "advanced", "tutorial"],
78
+ "author": "user:2",
79
+ "created": "2024-01-20T14:30:00Z"
80
+ },
81
+ {
82
+ "id": "doc:3",
83
+ "title": "Product Strategy for Developer Tools",
84
+ "text": "Building successful developer tools requires understanding the unique needs and workflows of software developers.",
85
+ "content": "This guide covers product strategy, user research, and go-to-market approaches specifically tailored for developer-focused products.",
86
+ "type": "Document",
87
+ "tags": ["product strategy", "developer tools", "business", "strategy"],
88
+ "author": "user:3",
89
+ "created": "2024-01-25T09:15:00Z"
90
+ }
91
+ ],
92
+ "projects": [
93
+ {
94
+ "id": "project:1",
95
+ "name": "PluresDB Database",
96
+ "type": "Project",
97
+ "description": "P2P graph database with SQLite compatibility",
98
+ "status": "active",
99
+ "technologies": ["Rust", "TypeScript", "Deno", "WebAssembly"],
100
+ "team": ["user:1", "user:2"],
101
+ "created": "2024-01-01T00:00:00Z"
102
+ },
103
+ {
104
+ "id": "project:2",
105
+ "name": "AI Research Platform",
106
+ "type": "Project",
107
+ "description": "Platform for collaborative AI research and experimentation",
108
+ "status": "planning",
109
+ "technologies": ["Python", "TensorFlow", "PyTorch", "Docker"],
110
+ "team": ["user:2", "user:3"],
111
+ "created": "2024-02-01T00:00:00Z"
112
+ }
113
+ ],
114
+ "vector_queries": [
115
+ "machine learning algorithms",
116
+ "artificial intelligence research",
117
+ "neural network architectures",
118
+ "deep learning applications",
119
+ "data science techniques",
120
+ "product management strategy",
121
+ "developer experience design",
122
+ "software engineering best practices"
123
+ ]
124
+ }
@@ -0,0 +1,232 @@
1
+ // @ts-nocheck
2
+ import { assertEquals, assertExists } from "jsr:@std/assert@1.0.14";
3
+ import { GunDB } from "../../core/database.ts";
4
+ import { startApiServer, type ApiServerHandle } from "../../http/api-server.ts";
5
+
6
+ function randomPort(): number {
7
+ return 18000 + Math.floor(Math.random() * 10000);
8
+ }
9
+
10
+ Deno.test("API Server - HTTP Endpoints", async () => {
11
+ const db = new GunDB();
12
+ let api: ApiServerHandle | null = null;
13
+ try {
14
+ const kvPath = await Deno.makeTempFile({
15
+ prefix: "kv_",
16
+ suffix: ".sqlite",
17
+ });
18
+ await db.ready(kvPath);
19
+
20
+ const port = randomPort();
21
+ const apiPort = port + 1;
22
+ db.serve({ port });
23
+ api = startApiServer({ port: apiPort, db });
24
+
25
+ const baseUrl = `http://localhost:${apiPort}`;
26
+
27
+ // Test PUT endpoint
28
+ const putResponse = await fetch(`${baseUrl}/api/nodes/test:1`, {
29
+ method: "PUT",
30
+ headers: { "Content-Type": "application/json" },
31
+ body: JSON.stringify({ name: "Test Node", value: 123 }),
32
+ });
33
+ assertEquals(putResponse.status, 200);
34
+ await putResponse.body?.cancel();
35
+
36
+ // Test GET endpoint
37
+ const getResponse = await fetch(`${baseUrl}/api/nodes/test:1`);
38
+ assertEquals(getResponse.status, 200);
39
+ const data = await getResponse.json();
40
+ assertEquals(data.name, "Test Node");
41
+ assertEquals(data.value, 123);
42
+
43
+ // Test DELETE endpoint
44
+ const deleteResponse = await fetch(`${baseUrl}/api/nodes/test:1`, {
45
+ method: "DELETE",
46
+ });
47
+ assertEquals(deleteResponse.status, 200);
48
+ await deleteResponse.body?.cancel();
49
+
50
+ // Verify deletion
51
+ const getAfterDelete = await fetch(`${baseUrl}/api/nodes/test:1`);
52
+ assertEquals(getAfterDelete.status, 404);
53
+ await getAfterDelete.body?.cancel();
54
+ } finally {
55
+ api?.close();
56
+ await db.close();
57
+ }
58
+ });
59
+
60
+ Deno.test("API Server - Vector Search Endpoint", async () => {
61
+ const db = new GunDB();
62
+ let api: ApiServerHandle | null = null;
63
+ try {
64
+ const kvPath = await Deno.makeTempFile({
65
+ prefix: "kv_",
66
+ suffix: ".sqlite",
67
+ });
68
+ await db.ready(kvPath);
69
+
70
+ // Add test data
71
+ await db.put("doc:1", { text: "Machine learning algorithms" });
72
+ await db.put("doc:2", { text: "Cooking recipes and food" });
73
+
74
+ const port = randomPort();
75
+ const apiPort = port + 1;
76
+ db.serve({ port });
77
+ api = startApiServer({ port: apiPort, db });
78
+
79
+ const baseUrl = `http://localhost:${apiPort}`;
80
+
81
+ // Test vector search endpoint
82
+ const searchResponse = await fetch(`${baseUrl}/api/search`, {
83
+ method: "POST",
84
+ headers: { "Content-Type": "application/json" },
85
+ body: JSON.stringify({
86
+ query: "machine learning",
87
+ limit: 5,
88
+ }),
89
+ });
90
+
91
+ assertEquals(searchResponse.status, 200);
92
+ const results = await searchResponse.json();
93
+ assertExists(results);
94
+ assertEquals(Array.isArray(results), true);
95
+ } finally {
96
+ api?.close();
97
+ await db.close();
98
+ }
99
+ });
100
+
101
+ Deno.test("API Server - WebSocket Connection", async () => {
102
+ const db = new GunDB();
103
+ let api: ApiServerHandle | null = null;
104
+ try {
105
+ const kvPath = await Deno.makeTempFile({
106
+ prefix: "kv_",
107
+ suffix: ".sqlite",
108
+ });
109
+ await db.ready(kvPath);
110
+
111
+ const port = randomPort();
112
+ const apiPort = port + 1;
113
+ db.serve({ port });
114
+ api = startApiServer({ port: apiPort, db });
115
+
116
+ const wsUrl = `ws://localhost:${port}/ws`;
117
+
118
+ // Test WebSocket connection
119
+ const ws = new WebSocket(wsUrl);
120
+
121
+ const connectionPromise = new Promise((resolve, reject) => {
122
+ const timer = setTimeout(() => reject(new Error("Connection timeout")), 5000);
123
+ ws.onopen = () => {
124
+ clearTimeout(timer);
125
+ resolve(true);
126
+ };
127
+ ws.onerror = (error) => {
128
+ clearTimeout(timer);
129
+ reject(error);
130
+ };
131
+ });
132
+
133
+ await connectionPromise;
134
+
135
+ // Test sending data via WebSocket
136
+ const messagePromise = new Promise((resolve) => {
137
+ ws.onmessage = (event) => {
138
+ const data = JSON.parse(event.data);
139
+ if (data.type === "put") {
140
+ resolve(data);
141
+ }
142
+ };
143
+ });
144
+
145
+ // Send a message
146
+ ws.send(
147
+ JSON.stringify({
148
+ type: "put",
149
+ id: "ws:test",
150
+ data: { message: "Hello WebSocket" },
151
+ }),
152
+ );
153
+
154
+ await messagePromise;
155
+ ws.close();
156
+ } finally {
157
+ api?.close();
158
+ await db.close();
159
+ }
160
+ });
161
+
162
+ Deno.test("API Server - Error Handling", async () => {
163
+ const db = new GunDB();
164
+ let api: ApiServerHandle | null = null;
165
+ try {
166
+ const kvPath = await Deno.makeTempFile({
167
+ prefix: "kv_",
168
+ suffix: ".sqlite",
169
+ });
170
+ await db.ready(kvPath);
171
+
172
+ const port = randomPort();
173
+ const apiPort = port + 1;
174
+ db.serve({ port });
175
+ api = startApiServer({ port: apiPort, db });
176
+
177
+ const baseUrl = `http://localhost:${apiPort}`;
178
+
179
+ // Test 404 for non-existent node
180
+ const notFoundResponse = await fetch(`${baseUrl}/api/nodes/nonexistent`);
181
+ assertEquals(notFoundResponse.status, 404);
182
+ await notFoundResponse.body?.cancel();
183
+
184
+ // Test 400 for invalid JSON
185
+ const invalidJsonResponse = await fetch(`${baseUrl}/api/nodes/test`, {
186
+ method: "PUT",
187
+ headers: { "Content-Type": "application/json" },
188
+ body: "invalid json",
189
+ });
190
+ assertEquals(invalidJsonResponse.status, 400);
191
+ await invalidJsonResponse.body?.cancel();
192
+ } finally {
193
+ api?.close();
194
+ await db.close();
195
+ }
196
+ });
197
+
198
+ Deno.test("API Server - CORS Headers", async () => {
199
+ const db = new GunDB();
200
+ let api: ApiServerHandle | null = null;
201
+ try {
202
+ const kvPath = await Deno.makeTempFile({
203
+ prefix: "kv_",
204
+ suffix: ".sqlite",
205
+ });
206
+ await db.ready(kvPath);
207
+
208
+ const port = randomPort();
209
+ const apiPort = port + 1;
210
+ db.serve({ port });
211
+ api = startApiServer({ port: apiPort, db });
212
+
213
+ const baseUrl = `http://localhost:${apiPort}`;
214
+
215
+ // Test OPTIONS request for CORS
216
+ const optionsResponse = await fetch(`${baseUrl}/api/nodes/test`, {
217
+ method: "OPTIONS",
218
+ headers: {
219
+ Origin: "http://localhost:3000",
220
+ "Access-Control-Request-Method": "PUT",
221
+ },
222
+ });
223
+
224
+ assertEquals(optionsResponse.status, 200);
225
+ assertExists(optionsResponse.headers.get("Access-Control-Allow-Origin"));
226
+ assertExists(optionsResponse.headers.get("Access-Control-Allow-Methods"));
227
+ await optionsResponse.body?.cancel();
228
+ } finally {
229
+ api?.close();
230
+ await db.close();
231
+ }
232
+ });