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,115 @@
1
+ import { debugLog } from "../util/debug.ts";
2
+ export interface MeshServer {
3
+ url: string;
4
+ broadcast: (obj: unknown, exclude?: WebSocket) => void;
5
+ close: () => void;
6
+ }
7
+
8
+ export function startMeshServer(args: {
9
+ port: number;
10
+ onMessage: (payload: {
11
+ msg: unknown;
12
+ source: WebSocket;
13
+ send: (obj: unknown) => void;
14
+ broadcast: (obj: unknown, exclude?: WebSocket) => void;
15
+ }) => void;
16
+ }): MeshServer {
17
+ const sockets = new Set<WebSocket>();
18
+ // Debug logging is gated by env var in util/debug.ts
19
+
20
+ const broadcast = (obj: unknown, exclude?: WebSocket) => {
21
+ const data = JSON.stringify(obj);
22
+ for (const s of sockets) {
23
+ if (s === exclude) continue;
24
+ try {
25
+ s.send(data);
26
+ } catch {
27
+ // ignore
28
+ }
29
+ }
30
+ };
31
+
32
+ const server = Deno.serve({ port: args.port, onListen: () => {} }, (req) => {
33
+ debugLog("ws:incoming request");
34
+ const upgrade = Deno.upgradeWebSocket(req);
35
+ const socket = upgrade.socket;
36
+
37
+ socket.onopen = () => {
38
+ debugLog("ws:open");
39
+ sockets.add(socket);
40
+ };
41
+
42
+ socket.onmessage = (event: MessageEvent<string>) => {
43
+ try {
44
+ const msg = JSON.parse(event.data);
45
+ debugLog("ws:message", { type: (msg as { type?: string }).type });
46
+ args.onMessage({
47
+ msg,
48
+ source: socket,
49
+ send: (obj: unknown) => {
50
+ try {
51
+ socket.send(JSON.stringify(obj));
52
+ } catch {
53
+ /* ignore */
54
+ }
55
+ },
56
+ broadcast,
57
+ });
58
+ } catch {
59
+ // ignore malformed message
60
+ }
61
+ };
62
+
63
+ const cleanup = () => sockets.delete(socket);
64
+ socket.onclose = cleanup;
65
+ socket.onerror = cleanup;
66
+
67
+ return upgrade.response;
68
+ });
69
+
70
+ return {
71
+ url: `ws://localhost:${args.port}`,
72
+ broadcast,
73
+ close: () => {
74
+ try {
75
+ server.shutdown();
76
+ } catch {
77
+ /* ignore */
78
+ }
79
+ for (const s of sockets) {
80
+ try {
81
+ s.close();
82
+ } catch {
83
+ /* ignore */
84
+ }
85
+ }
86
+ sockets.clear();
87
+ },
88
+ };
89
+ }
90
+
91
+ export function connectToPeer(
92
+ url: string,
93
+ handlers: {
94
+ onOpen?: (socket: WebSocket) => void;
95
+ onMessage?: (msg: unknown, socket: WebSocket) => void;
96
+ onClose?: (socket: WebSocket) => void;
97
+ },
98
+ ): WebSocket {
99
+ const socket = new WebSocket(url);
100
+ if (handlers.onOpen) socket.onopen = () => handlers.onOpen?.(socket);
101
+ if (handlers.onMessage) {
102
+ socket.onmessage = (e) => {
103
+ try {
104
+ handlers.onMessage?.(JSON.parse(e.data), socket);
105
+ } catch {
106
+ /* ignore */
107
+ }
108
+ };
109
+ }
110
+ if (handlers.onClose) socket.onclose = () => handlers.onClose?.(socket);
111
+ socket.onerror = () => {
112
+ /* ignore */
113
+ };
114
+ return socket;
115
+ }
@@ -0,0 +1,385 @@
1
+ /**
2
+ * Node.js Entry Point for PluresDB
3
+ * This provides a clean API for VSCode extensions and other Node.js applications
4
+ */
5
+
6
+ import { EventEmitter } from "node:events";
7
+ import { spawn, ChildProcess } from "node:child_process";
8
+ import * as path from "node:path";
9
+ import * as fs from "node:fs";
10
+ import * as os from "node:os";
11
+ import process from "node:process";
12
+ import { PluresDBConfig, PluresDBOptions } from "./types/node-types";
13
+
14
+ const packageRoot =
15
+ typeof __dirname !== "undefined" ? path.resolve(__dirname, "..") : process.cwd();
16
+
17
+ export class PluresNode extends EventEmitter {
18
+ private process: ChildProcess | null = null;
19
+ private config: PluresDBConfig;
20
+ private denoPath: string;
21
+ private isRunning = false;
22
+ private apiUrl: string = "";
23
+
24
+ constructor(options: PluresDBOptions = {}) {
25
+ super();
26
+
27
+ this.config = {
28
+ port: 34567,
29
+ host: "localhost",
30
+ dataDir: path.join(os.homedir(), ".pluresdb"),
31
+ webPort: 34568,
32
+ logLevel: "info",
33
+ ...options.config,
34
+ };
35
+
36
+ this.denoPath = options.denoPath || this.findDenoPath();
37
+
38
+ if (options.autoStart !== false) {
39
+ this.start();
40
+ }
41
+ }
42
+
43
+ private findDenoPath(): string {
44
+ // Try to find Deno in common locations
45
+ const possiblePaths = [
46
+ "deno", // In PATH
47
+ path.join(os.homedir(), ".deno", "bin", "deno"),
48
+ path.join(os.homedir(), ".local", "bin", "deno"),
49
+ "/usr/local/bin/deno",
50
+ "/opt/homebrew/bin/deno",
51
+ "C:\\Users\\" + os.userInfo().username + "\\.deno\\bin\\deno.exe",
52
+ "C:\\Program Files\\deno\\deno.exe",
53
+ ];
54
+
55
+ for (const denoPath of possiblePaths) {
56
+ try {
57
+ if (fs.existsSync(denoPath) || this.isCommandAvailable(denoPath)) {
58
+ return denoPath;
59
+ }
60
+ } catch (error) {
61
+ // Continue to next path
62
+ }
63
+ }
64
+
65
+ throw new Error("Deno not found. Please install Deno from https://deno.land/");
66
+ }
67
+
68
+ private isCommandAvailable(command: string): boolean {
69
+ try {
70
+ require("child_process").execSync(`"${command}" --version`, { stdio: "ignore" });
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ async start(): Promise<void> {
78
+ if (this.isRunning) {
79
+ return;
80
+ }
81
+
82
+ return new Promise((resolve, reject) => {
83
+ try {
84
+ // Ensure data directory exists
85
+ if (!fs.existsSync(this.config.dataDir!)) {
86
+ fs.mkdirSync(this.config.dataDir!, { recursive: true });
87
+ }
88
+
89
+ const kvPath = path.join(this.config.dataDir!, "pluresdb.kv");
90
+
91
+ // Find the main.ts file
92
+ const mainTsPath = path.join(packageRoot, "src", "main.ts");
93
+ if (!fs.existsSync(mainTsPath)) {
94
+ throw new Error(
95
+ "PluresDB main.ts not found. Please ensure the package is properly installed.",
96
+ );
97
+ }
98
+
99
+ // Start the Deno process
100
+ const args = [
101
+ "run",
102
+ "-A",
103
+ "--unstable-kv",
104
+ "--no-lock",
105
+ mainTsPath,
106
+ "serve",
107
+ "--port",
108
+ this.config.port!.toString(),
109
+ "--host",
110
+ this.config.host!,
111
+ "--kv",
112
+ kvPath,
113
+ ];
114
+
115
+ this.process = spawn(this.denoPath, args, {
116
+ stdio: ["pipe", "pipe", "pipe"],
117
+ cwd: packageRoot,
118
+ });
119
+
120
+ this.apiUrl = `http://${this.config.host}:${this.config.port}`;
121
+
122
+ // Handle process events
123
+ this.process.on("error", (error) => {
124
+ this.emit("error", error);
125
+ reject(error);
126
+ });
127
+
128
+ this.process.on("exit", (code) => {
129
+ this.isRunning = false;
130
+ this.emit("exit", code);
131
+ });
132
+
133
+ // Wait for server to start
134
+ this.waitForServer()
135
+ .then(() => {
136
+ this.isRunning = true;
137
+ this.emit("started");
138
+ resolve();
139
+ })
140
+ .catch(reject);
141
+
142
+ // Handle stdout/stderr
143
+ this.process.stdout?.on("data", (data) => {
144
+ const output = data.toString();
145
+ this.emit("stdout", output);
146
+ });
147
+
148
+ this.process.stderr?.on("data", (data) => {
149
+ const output = data.toString();
150
+ this.emit("stderr", output);
151
+ });
152
+ } catch (error) {
153
+ reject(error);
154
+ }
155
+ });
156
+ }
157
+
158
+ private async waitForServer(timeout = 20000): Promise<void> {
159
+ const startTime = Date.now();
160
+
161
+ while (Date.now() - startTime < timeout) {
162
+ try {
163
+ const response = await fetch(`${this.apiUrl}/api/config`);
164
+ if (response.ok) {
165
+ return;
166
+ }
167
+ } catch (error) {
168
+ // Server not ready yet
169
+ }
170
+
171
+ await new Promise((resolve) => setTimeout(resolve, 100));
172
+ }
173
+
174
+ throw new Error("Server failed to start within timeout");
175
+ }
176
+
177
+ async stop(): Promise<void> {
178
+ if (!this.isRunning || !this.process) {
179
+ return;
180
+ }
181
+
182
+ return new Promise((resolve) => {
183
+ this.process!.kill("SIGTERM");
184
+
185
+ this.process!.on("exit", () => {
186
+ this.isRunning = false;
187
+ this.emit("stopped");
188
+ resolve();
189
+ });
190
+
191
+ // Force kill after 5 seconds
192
+ setTimeout(() => {
193
+ if (this.process && this.isRunning) {
194
+ this.process.kill("SIGKILL");
195
+ }
196
+ resolve();
197
+ }, 5000);
198
+ });
199
+ }
200
+
201
+ getApiUrl(): string {
202
+ return this.apiUrl;
203
+ }
204
+
205
+ getWebUrl(): string {
206
+ return `http://${this.config.host}:${this.config.webPort}`;
207
+ }
208
+
209
+ isServerRunning(): boolean {
210
+ return this.isRunning;
211
+ }
212
+
213
+ // SQLite-compatible API methods
214
+ async query(sql: string, params: any[] = []): Promise<any> {
215
+ const response = await fetch(`${this.apiUrl}/api/query`, {
216
+ method: "POST",
217
+ headers: { "Content-Type": "application/json" },
218
+ body: JSON.stringify({ sql, params }),
219
+ });
220
+
221
+ if (!response.ok) {
222
+ throw new Error(`Query failed: ${response.statusText}`);
223
+ }
224
+
225
+ return response.json();
226
+ }
227
+
228
+ async put(key: string, value: any): Promise<void> {
229
+ const response = await fetch(`${this.apiUrl}/api/data`, {
230
+ method: "PUT",
231
+ headers: { "Content-Type": "application/json" },
232
+ body: JSON.stringify({ key, value }),
233
+ });
234
+
235
+ if (!response.ok) {
236
+ throw new Error(`Put failed: ${response.statusText}`);
237
+ }
238
+ }
239
+
240
+ async get(key: string): Promise<any> {
241
+ const response = await fetch(`${this.apiUrl}/api/data/${encodeURIComponent(key)}`);
242
+
243
+ if (!response.ok) {
244
+ if (response.status === 404) {
245
+ return null;
246
+ }
247
+ throw new Error(`Get failed: ${response.statusText}`);
248
+ }
249
+
250
+ return response.json();
251
+ }
252
+
253
+ async delete(key: string): Promise<void> {
254
+ const response = await fetch(`${this.apiUrl}/api/data/${encodeURIComponent(key)}`, {
255
+ method: "DELETE",
256
+ });
257
+
258
+ if (!response.ok) {
259
+ throw new Error(`Delete failed: ${response.statusText}`);
260
+ }
261
+ }
262
+
263
+ async vectorSearch(query: string, limit = 10): Promise<any[]> {
264
+ const response = await fetch(`${this.apiUrl}/api/vsearch`, {
265
+ method: "POST",
266
+ headers: { "Content-Type": "application/json" },
267
+ body: JSON.stringify({ query, limit }),
268
+ });
269
+
270
+ if (!response.ok) {
271
+ throw new Error(`Vector search failed: ${response.statusText}`);
272
+ }
273
+
274
+ return response.json() as Promise<any[]>;
275
+ }
276
+
277
+ async list(prefix?: string): Promise<string[]> {
278
+ const url = prefix
279
+ ? `${this.apiUrl}/api/list?prefix=${encodeURIComponent(prefix)}`
280
+ : `${this.apiUrl}/api/list`;
281
+ const response = await fetch(url);
282
+
283
+ if (!response.ok) {
284
+ throw new Error(`List failed: ${response.statusText}`);
285
+ }
286
+
287
+ return response.json() as Promise<string[]>;
288
+ }
289
+
290
+ async getConfig(): Promise<any> {
291
+ const response = await fetch(`${this.apiUrl}/api/config`);
292
+
293
+ if (!response.ok) {
294
+ throw new Error(`Get config failed: ${response.statusText}`);
295
+ }
296
+
297
+ return response.json();
298
+ }
299
+
300
+ async setConfig(config: any): Promise<void> {
301
+ const response = await fetch(`${this.apiUrl}/api/config`, {
302
+ method: "POST",
303
+ headers: { "Content-Type": "application/json" },
304
+ body: JSON.stringify(config),
305
+ });
306
+
307
+ if (!response.ok) {
308
+ throw new Error(`Set config failed: ${response.statusText}`);
309
+ }
310
+ }
311
+ }
312
+
313
+ // SQLite-compatible API for easy migration
314
+ export class SQLiteCompatibleAPI {
315
+ private plures: PluresNode;
316
+
317
+ constructor(options?: PluresDBOptions) {
318
+ this.plures = new PluresNode(options);
319
+ }
320
+
321
+ async start() {
322
+ await this.plures.start();
323
+ }
324
+
325
+ async stop() {
326
+ await this.plures.stop();
327
+ }
328
+
329
+ // SQLite-compatible methods
330
+ async run(sql: string, params: any[] = []) {
331
+ return this.plures.query(sql, params);
332
+ }
333
+
334
+ async get(sql: string, params: any[] = []) {
335
+ const result = await this.plures.query(sql, params);
336
+ return result.rows?.[0] || null;
337
+ }
338
+
339
+ async all(sql: string, params: any[] = []) {
340
+ const result = await this.plures.query(sql, params);
341
+ return result.rows || [];
342
+ }
343
+
344
+ async exec(sql: string) {
345
+ return this.plures.query(sql);
346
+ }
347
+
348
+ // Additional PluresDB specific methods
349
+ async put(key: string, value: any) {
350
+ return this.plures.put(key, value);
351
+ }
352
+
353
+ async getValue(key: string) {
354
+ return this.plures.get(key);
355
+ }
356
+
357
+ async delete(key: string) {
358
+ return this.plures.delete(key);
359
+ }
360
+
361
+ async vectorSearch(query: string, limit = 10) {
362
+ return this.plures.vectorSearch(query, limit);
363
+ }
364
+
365
+ async list(prefix?: string) {
366
+ return this.plures.list(prefix);
367
+ }
368
+
369
+ getApiUrl() {
370
+ return this.plures.getApiUrl();
371
+ }
372
+
373
+ getWebUrl() {
374
+ return this.plures.getWebUrl();
375
+ }
376
+
377
+ isRunning() {
378
+ return this.plures.isServerRunning();
379
+ }
380
+ }
381
+
382
+ // Export the main class and types
383
+ export { PluresNode as default };
384
+ export * from "./types/node-types";
385
+ export { PluresVSCodeExtension, createPluresExtension } from "./vscode/extension";