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,297 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { assertEquals, assertExists } from "jsr:@std/assert@1.0.14";
|
|
3
|
+
import { GunDB } from "../../core/database.ts";
|
|
4
|
+
|
|
5
|
+
const shouldRunMeshTests =
|
|
6
|
+
(Deno.env.get("RUN_MESH_TESTS") ?? "").toLowerCase() === "true";
|
|
7
|
+
const defaultTimeoutMs = Number(
|
|
8
|
+
Deno.env.get("RUN_MESH_TEST_TIMEOUT_MS") ?? "10000",
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
if (!shouldRunMeshTests) {
|
|
12
|
+
console.warn(
|
|
13
|
+
"[mesh-network.test] Skipping mesh networking integration tests. Set RUN_MESH_TESTS=true to enable.",
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function withTimeout<T>(
|
|
18
|
+
promise: Promise<T>,
|
|
19
|
+
message: string,
|
|
20
|
+
timeoutMs = defaultTimeoutMs,
|
|
21
|
+
): Promise<T> {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const timer = setTimeout(() => {
|
|
24
|
+
reject(new Error(message));
|
|
25
|
+
}, timeoutMs);
|
|
26
|
+
|
|
27
|
+
promise.then(
|
|
28
|
+
(value) => {
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
resolve(value);
|
|
31
|
+
},
|
|
32
|
+
(error) => {
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
reject(error);
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function meshTest(name: string, fn: () => Promise<void>) {
|
|
41
|
+
Deno.test({
|
|
42
|
+
name,
|
|
43
|
+
ignore: !shouldRunMeshTests,
|
|
44
|
+
sanitizeOps: false,
|
|
45
|
+
sanitizeResources: false,
|
|
46
|
+
fn,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function randomPort(): number {
|
|
51
|
+
return 18000 + Math.floor(Math.random() * 10000);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
meshTest("Mesh Network - Basic Connection and Sync", async () => {
|
|
55
|
+
const port = randomPort();
|
|
56
|
+
const serverUrl = `ws://localhost:${port}`;
|
|
57
|
+
|
|
58
|
+
const dbA = new GunDB();
|
|
59
|
+
const dbB = new GunDB();
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const kvA = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
|
|
63
|
+
const kvB = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
|
|
64
|
+
|
|
65
|
+
await dbA.ready(kvA);
|
|
66
|
+
await dbB.ready(kvB);
|
|
67
|
+
|
|
68
|
+
// Start server
|
|
69
|
+
await dbA.serve({ port });
|
|
70
|
+
|
|
71
|
+
// Add data to server
|
|
72
|
+
await dbA.put("mesh:test", {
|
|
73
|
+
text: "Hello from server A",
|
|
74
|
+
timestamp: Date.now(),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Connect client and wait for sync
|
|
78
|
+
const receivedData = new Promise((resolve) => {
|
|
79
|
+
dbB.on("mesh:test", (node) => {
|
|
80
|
+
if (node && (node.data as any).text === "Hello from server A") {
|
|
81
|
+
resolve(true);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
dbB.connect(serverUrl);
|
|
87
|
+
await withTimeout(receivedData as Promise<unknown>, "Timed out waiting for initial mesh sync");
|
|
88
|
+
|
|
89
|
+
// Verify data was received
|
|
90
|
+
const syncedData = await dbB.get("mesh:test");
|
|
91
|
+
assertExists(syncedData);
|
|
92
|
+
assertEquals((syncedData as any).text, "Hello from server A");
|
|
93
|
+
} finally {
|
|
94
|
+
await dbB.close();
|
|
95
|
+
await dbA.close();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
meshTest("Mesh Network - Bidirectional Sync", async () => {
|
|
100
|
+
const port = randomPort();
|
|
101
|
+
const serverUrl = `ws://localhost:${port}`;
|
|
102
|
+
|
|
103
|
+
const dbA = new GunDB();
|
|
104
|
+
const dbB = new GunDB();
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const kvA = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
|
|
108
|
+
const kvB = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
|
|
109
|
+
|
|
110
|
+
await dbA.ready(kvA);
|
|
111
|
+
await dbB.ready(kvB);
|
|
112
|
+
await dbA.serve({ port });
|
|
113
|
+
|
|
114
|
+
// Connect B to A
|
|
115
|
+
dbB.connect(serverUrl);
|
|
116
|
+
await withTimeout(
|
|
117
|
+
new Promise((resolve) => setTimeout(resolve, 100)),
|
|
118
|
+
"Timed out waiting for mesh connection",
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// A sends data to B
|
|
122
|
+
await dbA.put("from:A", { message: "Hello from A" });
|
|
123
|
+
|
|
124
|
+
const receivedFromA = new Promise((resolve) => {
|
|
125
|
+
dbB.on("from:A", (node) => {
|
|
126
|
+
if (node && (node.data as any).message === "Hello from A") {
|
|
127
|
+
resolve(true);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
await withTimeout(
|
|
132
|
+
receivedFromA as Promise<unknown>,
|
|
133
|
+
"Timed out waiting for data from node A",
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// B sends data to A
|
|
137
|
+
await dbB.put("from:B", { message: "Hello from B" });
|
|
138
|
+
|
|
139
|
+
const receivedFromB = new Promise((resolve) => {
|
|
140
|
+
dbA.on("from:B", (node) => {
|
|
141
|
+
if (node && (node.data as any).message === "Hello from B") {
|
|
142
|
+
resolve(true);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
await withTimeout(
|
|
147
|
+
receivedFromB as Promise<unknown>,
|
|
148
|
+
"Timed out waiting for data from node B",
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Verify both received data
|
|
152
|
+
const dataFromA = await dbB.get("from:A");
|
|
153
|
+
const dataFromB = await dbA.get("from:B");
|
|
154
|
+
|
|
155
|
+
assertExists(dataFromA);
|
|
156
|
+
assertExists(dataFromB);
|
|
157
|
+
assertEquals((dataFromA as any).message, "Hello from A");
|
|
158
|
+
assertEquals((dataFromB as any).message, "Hello from B");
|
|
159
|
+
} finally {
|
|
160
|
+
await dbB.close();
|
|
161
|
+
await dbA.close();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
meshTest("Mesh Network - Conflict Resolution", async () => {
|
|
166
|
+
const port = randomPort();
|
|
167
|
+
const serverUrl = `ws://localhost:${port}`;
|
|
168
|
+
|
|
169
|
+
const dbA = new GunDB();
|
|
170
|
+
const dbB = new GunDB();
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const kvA = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
|
|
174
|
+
const kvB = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
|
|
175
|
+
|
|
176
|
+
await dbA.ready(kvA);
|
|
177
|
+
await dbB.ready(kvB);
|
|
178
|
+
await dbA.serve({ port });
|
|
179
|
+
|
|
180
|
+
// Connect B to A
|
|
181
|
+
dbB.connect(serverUrl);
|
|
182
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
183
|
+
|
|
184
|
+
// Both modify the same key simultaneously
|
|
185
|
+
const timestamp = Date.now();
|
|
186
|
+
await dbA.put("conflict:test", {
|
|
187
|
+
value: "from A",
|
|
188
|
+
timestamp: timestamp + 1000,
|
|
189
|
+
});
|
|
190
|
+
await dbB.put("conflict:test", {
|
|
191
|
+
value: "from B",
|
|
192
|
+
timestamp: timestamp + 2000,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Wait for sync
|
|
196
|
+
await withTimeout(
|
|
197
|
+
new Promise((resolve) => setTimeout(resolve, 500)),
|
|
198
|
+
"Timed out waiting for conflict resolution",
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Check that both databases have the same final state
|
|
202
|
+
const finalA = await dbA.get("conflict:test");
|
|
203
|
+
const finalB = await dbB.get("conflict:test");
|
|
204
|
+
|
|
205
|
+
assertExists(finalA);
|
|
206
|
+
assertExists(finalB);
|
|
207
|
+
|
|
208
|
+
// Should have the newer timestamp (from B)
|
|
209
|
+
assertEquals((finalA as any).value, "from B");
|
|
210
|
+
assertEquals((finalB as any).value, "from B");
|
|
211
|
+
} finally {
|
|
212
|
+
await dbB.close();
|
|
213
|
+
await dbA.close();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
meshTest("Mesh Network - Multiple Clients", async () => {
|
|
218
|
+
const port = randomPort();
|
|
219
|
+
const serverUrl = `ws://localhost:${port}`;
|
|
220
|
+
|
|
221
|
+
const dbA = new GunDB(); // Server
|
|
222
|
+
const dbB = new GunDB(); // Client 1
|
|
223
|
+
const dbC = new GunDB(); // Client 2
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const kvA = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
|
|
227
|
+
const kvB = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
|
|
228
|
+
const kvC = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
|
|
229
|
+
|
|
230
|
+
await dbA.ready(kvA);
|
|
231
|
+
await dbB.ready(kvB);
|
|
232
|
+
await dbC.ready(kvC);
|
|
233
|
+
await dbA.serve({ port });
|
|
234
|
+
|
|
235
|
+
// Connect both clients
|
|
236
|
+
dbB.connect(serverUrl);
|
|
237
|
+
dbC.connect(serverUrl);
|
|
238
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
239
|
+
|
|
240
|
+
// B sends data
|
|
241
|
+
await dbB.put("multi:test", { from: "B", message: "Hello from B" });
|
|
242
|
+
|
|
243
|
+
// Wait for both A and C to receive
|
|
244
|
+
const receivedOnA = new Promise((resolve) => {
|
|
245
|
+
dbA.on("multi:test", (node) => {
|
|
246
|
+
if (node && (node.data as any).from === "B") resolve(true);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const receivedOnC = new Promise((resolve) => {
|
|
251
|
+
dbC.on("multi:test", (node) => {
|
|
252
|
+
if (node && (node.data as any).from === "B") resolve(true);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
await withTimeout(
|
|
257
|
+
Promise.all([receivedOnA, receivedOnC]),
|
|
258
|
+
"Timed out waiting for multi-client propagation",
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// Verify all have the data
|
|
262
|
+
const dataA = await dbA.get("multi:test");
|
|
263
|
+
const dataB = await dbB.get("multi:test");
|
|
264
|
+
const dataC = await dbC.get("multi:test");
|
|
265
|
+
|
|
266
|
+
assertExists(dataA);
|
|
267
|
+
assertExists(dataB);
|
|
268
|
+
assertExists(dataC);
|
|
269
|
+
assertEquals((dataA as any).from, "B");
|
|
270
|
+
assertEquals((dataB as any).from, "B");
|
|
271
|
+
assertEquals((dataC as any).from, "B");
|
|
272
|
+
} finally {
|
|
273
|
+
await dbC.close();
|
|
274
|
+
await dbB.close();
|
|
275
|
+
await dbA.close();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
meshTest("Mesh Network - Connection Error Handling", async () => {
|
|
280
|
+
const db = new GunDB();
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
const kvPath = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
|
|
284
|
+
await db.ready(kvPath);
|
|
285
|
+
|
|
286
|
+
// Try to connect to non-existent server
|
|
287
|
+
db.connect("ws://localhost:99999");
|
|
288
|
+
|
|
289
|
+
// Should not throw error, but connection should fail gracefully
|
|
290
|
+
await withTimeout(
|
|
291
|
+
new Promise((resolve) => setTimeout(resolve, 1000)),
|
|
292
|
+
"Timed out waiting for graceful connection failure",
|
|
293
|
+
);
|
|
294
|
+
} finally {
|
|
295
|
+
await db.close();
|
|
296
|
+
}
|
|
297
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { GunDB } from "../core/database.ts";
|
|
2
|
+
import type { Rule } from "../logic/rules.ts";
|
|
3
|
+
|
|
4
|
+
Deno.test("rule engine classification: Person.age >= 18 -> adult = true", async () => {
|
|
5
|
+
const db = new GunDB();
|
|
6
|
+
try {
|
|
7
|
+
const kvPath = await Deno.makeTempFile({ prefix: "kv_", suffix: ".sqlite" });
|
|
8
|
+
await db.ready(kvPath);
|
|
9
|
+
|
|
10
|
+
const rule: Rule = {
|
|
11
|
+
name: "adultClassifier",
|
|
12
|
+
whenType: "Person",
|
|
13
|
+
predicate: (node) =>
|
|
14
|
+
typeof (node.data as any).age === "number" && (node.data as any).age >= 18,
|
|
15
|
+
action: async (ctx, node) => {
|
|
16
|
+
const data = { ...(node.data as Record<string, unknown>), adult: true };
|
|
17
|
+
await ctx.db.put(node.id, data);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
db.addRule(rule);
|
|
21
|
+
|
|
22
|
+
await db.put("p:alice", { name: "Alice", age: 20, type: "Person" });
|
|
23
|
+
const got = await db.get<{ adult?: boolean }>("p:alice");
|
|
24
|
+
if (!got || got.adult !== true) {
|
|
25
|
+
throw new Error("Expected adult flag set by rule");
|
|
26
|
+
}
|
|
27
|
+
} finally {
|
|
28
|
+
await db.close();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { assertEquals, assertExists } from "jsr:@std/assert@1.0.14";
|
|
3
|
+
import { GunDB } from "../../core/database.ts";
|
|
4
|
+
|
|
5
|
+
interface PerformanceMetrics {
|
|
6
|
+
operation: string;
|
|
7
|
+
count: number;
|
|
8
|
+
totalTime: number;
|
|
9
|
+
averageTime: number;
|
|
10
|
+
operationsPerSecond: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function measureOperation(
|
|
14
|
+
operation: () => Promise<void>,
|
|
15
|
+
count: number,
|
|
16
|
+
): Promise<PerformanceMetrics> {
|
|
17
|
+
return new Promise(async (resolve) => {
|
|
18
|
+
const startTime = performance.now();
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < count; i++) {
|
|
21
|
+
await operation();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const endTime = performance.now();
|
|
25
|
+
const totalTime = endTime - startTime;
|
|
26
|
+
const averageTime = totalTime / count;
|
|
27
|
+
const operationsPerSecond = (count / totalTime) * 1000;
|
|
28
|
+
|
|
29
|
+
resolve({
|
|
30
|
+
operation: "unknown",
|
|
31
|
+
count,
|
|
32
|
+
totalTime,
|
|
33
|
+
averageTime,
|
|
34
|
+
operationsPerSecond,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Deno.test("Performance - Bulk Insert Operations", async () => {
|
|
40
|
+
const db = new GunDB();
|
|
41
|
+
try {
|
|
42
|
+
const kvPath = await Deno.makeTempFile({
|
|
43
|
+
prefix: "kv_",
|
|
44
|
+
suffix: ".sqlite",
|
|
45
|
+
});
|
|
46
|
+
await db.ready(kvPath);
|
|
47
|
+
|
|
48
|
+
const count = 1000;
|
|
49
|
+
let currentCount = 0;
|
|
50
|
+
|
|
51
|
+
const metrics = await measureOperation(async () => {
|
|
52
|
+
await db.put(`perf:${currentCount++}`, {
|
|
53
|
+
id: currentCount,
|
|
54
|
+
data: `Performance test data ${currentCount}`,
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
});
|
|
57
|
+
}, count);
|
|
58
|
+
|
|
59
|
+
console.log(`Bulk Insert Performance:`, metrics);
|
|
60
|
+
|
|
61
|
+
// Verify all data was inserted
|
|
62
|
+
const allNodes = await db.getAll();
|
|
63
|
+
assertEquals(allNodes.length, count);
|
|
64
|
+
|
|
65
|
+
// Performance assertions
|
|
66
|
+
assertEquals(metrics.operationsPerSecond > 100, true); // At least 100 ops/sec
|
|
67
|
+
assertEquals(metrics.averageTime < 10, true); // Less than 10ms per operation
|
|
68
|
+
} finally {
|
|
69
|
+
await db.close();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
Deno.test("Performance - Bulk Read Operations", async () => {
|
|
74
|
+
const db = new GunDB();
|
|
75
|
+
try {
|
|
76
|
+
const kvPath = await Deno.makeTempFile({
|
|
77
|
+
prefix: "kv_",
|
|
78
|
+
suffix: ".sqlite",
|
|
79
|
+
});
|
|
80
|
+
await db.ready(kvPath);
|
|
81
|
+
|
|
82
|
+
// Pre-populate with data
|
|
83
|
+
const count = 1000;
|
|
84
|
+
for (let i = 0; i < count; i++) {
|
|
85
|
+
await db.put(`read:${i}`, {
|
|
86
|
+
id: i,
|
|
87
|
+
data: `Read test data ${i}`,
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let currentCount = 0;
|
|
93
|
+
const metrics = await measureOperation(async () => {
|
|
94
|
+
const data = await db.get(`read:${currentCount++}`);
|
|
95
|
+
assertExists(data);
|
|
96
|
+
}, count);
|
|
97
|
+
|
|
98
|
+
console.log(`Bulk Read Performance:`, metrics);
|
|
99
|
+
|
|
100
|
+
// Performance assertions
|
|
101
|
+
assertEquals(metrics.operationsPerSecond > 500, true); // At least 500 ops/sec
|
|
102
|
+
assertEquals(metrics.averageTime < 2, true); // Less than 2ms per operation
|
|
103
|
+
} finally {
|
|
104
|
+
await db.close();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
Deno.test("Performance - Vector Search Operations", async () => {
|
|
109
|
+
const db = new GunDB();
|
|
110
|
+
try {
|
|
111
|
+
const kvPath = await Deno.makeTempFile({
|
|
112
|
+
prefix: "kv_",
|
|
113
|
+
suffix: ".sqlite",
|
|
114
|
+
});
|
|
115
|
+
await db.ready(kvPath);
|
|
116
|
+
|
|
117
|
+
// Pre-populate with documents for vector search
|
|
118
|
+
const count = 500;
|
|
119
|
+
for (let i = 0; i < count; i++) {
|
|
120
|
+
await db.put(`doc:${i}`, {
|
|
121
|
+
text: `Document ${i} about machine learning and artificial intelligence`,
|
|
122
|
+
content: `This is document number ${i} containing information about AI and ML algorithms`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const searchQueries = [
|
|
127
|
+
"machine learning algorithms",
|
|
128
|
+
"artificial intelligence",
|
|
129
|
+
"neural networks",
|
|
130
|
+
"deep learning",
|
|
131
|
+
"data science",
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
let queryCount = 0;
|
|
135
|
+
const metrics = await measureOperation(async () => {
|
|
136
|
+
const query = searchQueries[queryCount % searchQueries.length];
|
|
137
|
+
const results = await db.vectorSearch(query, 10);
|
|
138
|
+
assertExists(results);
|
|
139
|
+
queryCount++;
|
|
140
|
+
}, 100);
|
|
141
|
+
|
|
142
|
+
console.log(`Vector Search Performance:`, metrics);
|
|
143
|
+
|
|
144
|
+
// Performance assertions
|
|
145
|
+
assertEquals(metrics.operationsPerSecond > 50, true); // At least 50 ops/sec
|
|
146
|
+
assertEquals(metrics.averageTime < 20, true); // Less than 20ms per operation
|
|
147
|
+
} finally {
|
|
148
|
+
await db.close();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
Deno.test("Performance - Concurrent Operations", 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
|
+
const concurrentCount = 100;
|
|
162
|
+
const operationsPerWorker = 10;
|
|
163
|
+
|
|
164
|
+
const startTime = performance.now();
|
|
165
|
+
|
|
166
|
+
// Run concurrent operations
|
|
167
|
+
const promises = Array.from({ length: concurrentCount }, async (_, i) => {
|
|
168
|
+
for (let j = 0; j < operationsPerWorker; j++) {
|
|
169
|
+
await db.put(`concurrent:${i}:${j}`, {
|
|
170
|
+
worker: i,
|
|
171
|
+
operation: j,
|
|
172
|
+
timestamp: Date.now(),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await Promise.all(promises);
|
|
178
|
+
|
|
179
|
+
const endTime = performance.now();
|
|
180
|
+
const totalTime = endTime - startTime;
|
|
181
|
+
const totalOperations = concurrentCount * operationsPerWorker;
|
|
182
|
+
const operationsPerSecond = (totalOperations / totalTime) * 1000;
|
|
183
|
+
|
|
184
|
+
console.log(`Concurrent Operations Performance:`, {
|
|
185
|
+
totalOperations,
|
|
186
|
+
totalTime,
|
|
187
|
+
operationsPerSecond,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Verify all operations completed
|
|
191
|
+
const allNodes = await db.getAll();
|
|
192
|
+
assertEquals(allNodes.length, totalOperations);
|
|
193
|
+
|
|
194
|
+
// Performance assertions
|
|
195
|
+
assertEquals(operationsPerSecond > 200, true); // At least 200 ops/sec
|
|
196
|
+
} finally {
|
|
197
|
+
await db.close();
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
Deno.test("Performance - Memory Usage", async () => {
|
|
202
|
+
const db = new GunDB();
|
|
203
|
+
try {
|
|
204
|
+
const kvPath = await Deno.makeTempFile({
|
|
205
|
+
prefix: "kv_",
|
|
206
|
+
suffix: ".sqlite",
|
|
207
|
+
});
|
|
208
|
+
await db.ready(kvPath);
|
|
209
|
+
|
|
210
|
+
const initialMemory = (performance as any).memory?.usedJSHeapSize || 0;
|
|
211
|
+
|
|
212
|
+
// Insert large amount of data
|
|
213
|
+
const count = 10000;
|
|
214
|
+
for (let i = 0; i < count; i++) {
|
|
215
|
+
await db.put(`memory:${i}`, {
|
|
216
|
+
id: i,
|
|
217
|
+
largeData: "x".repeat(1000), // 1KB per record
|
|
218
|
+
timestamp: Date.now(),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const afterInsertMemory = (performance as any).memory?.usedJSHeapSize || 0;
|
|
223
|
+
const memoryIncrease = afterInsertMemory - initialMemory;
|
|
224
|
+
|
|
225
|
+
console.log(`Memory Usage:`, {
|
|
226
|
+
initialMemory,
|
|
227
|
+
afterInsertMemory,
|
|
228
|
+
memoryIncrease,
|
|
229
|
+
memoryPerRecord: memoryIncrease / count,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Memory efficiency assertions
|
|
233
|
+
assertEquals(memoryIncrease < 50 * 1024 * 1024, true); // Less than 50MB increase
|
|
234
|
+
assertEquals(memoryIncrease / count < 5000, true); // Less than 5KB per record
|
|
235
|
+
} finally {
|
|
236
|
+
await db.close();
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
Deno.test("Performance - Subscription Performance", async () => {
|
|
241
|
+
const db = new GunDB();
|
|
242
|
+
try {
|
|
243
|
+
const kvPath = await Deno.makeTempFile({
|
|
244
|
+
prefix: "kv_",
|
|
245
|
+
suffix: ".sqlite",
|
|
246
|
+
});
|
|
247
|
+
await db.ready(kvPath);
|
|
248
|
+
|
|
249
|
+
const subscriptionCount = 1000;
|
|
250
|
+
const updateCount = 100;
|
|
251
|
+
|
|
252
|
+
// Set up many subscriptions
|
|
253
|
+
const subscriptions: Array<() => void> = [];
|
|
254
|
+
for (let i = 0; i < subscriptionCount; i++) {
|
|
255
|
+
const unsubscribe = db.on(`sub:${i}`, () => {});
|
|
256
|
+
subscriptions.push(unsubscribe);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const startTime = performance.now();
|
|
260
|
+
|
|
261
|
+
// Trigger updates
|
|
262
|
+
for (let i = 0; i < updateCount; i++) {
|
|
263
|
+
await db.put(`sub:${i % subscriptionCount}`, {
|
|
264
|
+
update: i,
|
|
265
|
+
timestamp: Date.now(),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const endTime = performance.now();
|
|
270
|
+
const totalTime = endTime - startTime;
|
|
271
|
+
const updatesPerSecond = (updateCount / totalTime) * 1000;
|
|
272
|
+
|
|
273
|
+
console.log(`Subscription Performance:`, {
|
|
274
|
+
subscriptionCount,
|
|
275
|
+
updateCount,
|
|
276
|
+
totalTime,
|
|
277
|
+
updatesPerSecond,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Clean up subscriptions
|
|
281
|
+
subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
282
|
+
|
|
283
|
+
// Performance assertions
|
|
284
|
+
assertEquals(updatesPerSecond > 50, true); // At least 50 updates/sec
|
|
285
|
+
} finally {
|
|
286
|
+
await db.close();
|
|
287
|
+
}
|
|
288
|
+
});
|