@plures/pluresdb 1.5.3 → 1.6.10

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.
@@ -0,0 +1,449 @@
1
+ /**
2
+ * Unified Local-First API for PluresDB
3
+ *
4
+ * Automatically selects the best integration method based on runtime environment:
5
+ * - Browser (WASM): Direct in-process WebAssembly
6
+ * - Tauri: Direct Rust crate linking via Tauri commands
7
+ * - Native IPC: Shared memory inter-process communication
8
+ * - Network: HTTP REST API (fallback for backward compatibility)
9
+ *
10
+ * This provides a consistent API across all platforms while maximizing performance
11
+ * and minimizing network overhead.
12
+ */
13
+
14
+ import { debugLog } from "../util/debug";
15
+
16
+ export interface LocalFirstOptions {
17
+ /**
18
+ * Integration mode. If not specified, auto-detects the best mode.
19
+ * - "auto": Auto-detect runtime environment
20
+ * - "wasm": WebAssembly (browser)
21
+ * - "tauri": Tauri native integration
22
+ * - "ipc": Shared memory IPC
23
+ * - "network": HTTP REST API (backward compatibility)
24
+ */
25
+ mode?: "auto" | "wasm" | "tauri" | "ipc" | "network";
26
+
27
+ /**
28
+ * Database name (for WASM mode)
29
+ */
30
+ dbName?: string;
31
+
32
+ /**
33
+ * IPC channel name (for IPC mode)
34
+ */
35
+ channelName?: string;
36
+
37
+ /**
38
+ * Network URL (for network mode)
39
+ */
40
+ networkUrl?: string;
41
+
42
+ /**
43
+ * Network port (for network mode)
44
+ */
45
+ port?: number;
46
+ }
47
+
48
+ export interface LocalFirstBackend {
49
+ put(id: string, data: unknown): Promise<string>;
50
+ get(id: string): Promise<unknown>;
51
+ delete(id: string): Promise<void>;
52
+ list(): Promise<unknown[]>;
53
+ vectorSearch?(query: string, limit: number): Promise<unknown[]>;
54
+ close?(): Promise<void>;
55
+ }
56
+
57
+ /**
58
+ * Runtime environment detection
59
+ */
60
+ class RuntimeDetector {
61
+ static isBrowser(): boolean {
62
+ return typeof globalThis !== "undefined" &&
63
+ typeof (globalThis as any).window !== "undefined" &&
64
+ typeof (globalThis as any).document !== "undefined" &&
65
+ typeof (globalThis as any).WebAssembly !== "undefined";
66
+ }
67
+
68
+ static isTauri(): boolean {
69
+ return typeof globalThis !== "undefined" &&
70
+ typeof (globalThis as any).window !== "undefined" &&
71
+ (globalThis as any).window?.__TAURI__ !== undefined;
72
+ }
73
+
74
+ static isNode(): boolean {
75
+ const globalProcess = (globalThis as any).process;
76
+ return typeof globalProcess !== "undefined" &&
77
+ globalProcess.versions?.node != null;
78
+ }
79
+
80
+ static isDeno(): boolean {
81
+ return typeof globalThis !== "undefined" &&
82
+ (globalThis as any).Deno !== undefined;
83
+ }
84
+
85
+ static hasIPCEnvironment(): boolean {
86
+ if (this.isNode()) {
87
+ return (globalThis as any).process?.env?.PLURESDB_IPC === "true";
88
+ }
89
+ if (this.isDeno()) {
90
+ const Deno = (globalThis as any).Deno;
91
+ return Deno?.env?.get?.("PLURESDB_IPC") === "true";
92
+ }
93
+ return false;
94
+ }
95
+
96
+ static detectBestMode(): "wasm" | "tauri" | "ipc" | "network" {
97
+ if (this.isTauri()) {
98
+ debugLog("Detected Tauri environment - using native integration");
99
+ return "tauri";
100
+ }
101
+
102
+ if (this.isBrowser()) {
103
+ debugLog("Detected browser environment - using WASM");
104
+ return "wasm";
105
+ }
106
+
107
+ if (this.hasIPCEnvironment()) {
108
+ debugLog("Detected IPC environment - using shared memory");
109
+ return "ipc";
110
+ }
111
+
112
+ debugLog("Using network mode (fallback)");
113
+ return "network";
114
+ }
115
+ }
116
+
117
+ /**
118
+ * WASM Backend (Browser)
119
+ *
120
+ * Uses WebAssembly for direct in-process database access.
121
+ * Data is persisted in IndexedDB.
122
+ */
123
+ class WasmBackend implements LocalFirstBackend {
124
+ private db: unknown = null;
125
+ private dbName: string;
126
+
127
+ constructor(dbName: string = "pluresdb") {
128
+ this.dbName = dbName;
129
+ }
130
+
131
+ async initialize(): Promise<void> {
132
+ // Note: This is a placeholder. The actual WASM module will be implemented
133
+ // in Phase 1 of the roadmap (pluresdb-wasm crate).
134
+ throw new Error(
135
+ "WASM backend not yet implemented. Please see docs/LOCAL_FIRST_INTEGRATION.md for implementation status.",
136
+ );
137
+ }
138
+
139
+ async put(id: string, data: unknown): Promise<string> {
140
+ if (!this.db) await this.initialize();
141
+ return (this.db as any).put(id, data);
142
+ }
143
+
144
+ async get(id: string): Promise<unknown> {
145
+ if (!this.db) await this.initialize();
146
+ return (this.db as any).get(id);
147
+ }
148
+
149
+ async delete(id: string): Promise<void> {
150
+ if (!this.db) await this.initialize();
151
+ return (this.db as any).delete(id);
152
+ }
153
+
154
+ async list(): Promise<unknown[]> {
155
+ if (!this.db) await this.initialize();
156
+ return (this.db as any).list();
157
+ }
158
+
159
+ async vectorSearch(query: string, limit: number): Promise<unknown[]> {
160
+ if (!this.db) await this.initialize();
161
+ return (this.db as any).vectorSearch(query, limit);
162
+ }
163
+
164
+ async close(): Promise<void> {
165
+ if (this.db) {
166
+ await (this.db as any).close();
167
+ this.db = null;
168
+ }
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Tauri Backend
174
+ *
175
+ * Uses Tauri commands to invoke Rust functions directly.
176
+ * Provides native performance with no network overhead.
177
+ */
178
+ class TauriBackend implements LocalFirstBackend {
179
+ private invoke: any;
180
+
181
+ constructor() {
182
+ const win = typeof globalThis !== "undefined" ? (globalThis as any).window : undefined;
183
+ if (!win || !win.__TAURI__) {
184
+ throw new Error("Tauri backend requires Tauri environment");
185
+ }
186
+ this.invoke = win.__TAURI__.invoke;
187
+ }
188
+
189
+ async put(id: string, data: unknown): Promise<string> {
190
+ return await this.invoke("pluresdb_put", { id, data });
191
+ }
192
+
193
+ async get(id: string): Promise<unknown> {
194
+ return await this.invoke("pluresdb_get", { id });
195
+ }
196
+
197
+ async delete(id: string): Promise<void> {
198
+ await this.invoke("pluresdb_delete", { id });
199
+ }
200
+
201
+ async list(): Promise<unknown[]> {
202
+ return await this.invoke("pluresdb_list");
203
+ }
204
+
205
+ async vectorSearch(query: string, limit: number): Promise<unknown[]> {
206
+ return await this.invoke("pluresdb_vector_search", { query, limit });
207
+ }
208
+ }
209
+
210
+ /**
211
+ * IPC Backend (Native Apps)
212
+ *
213
+ * Uses shared memory and message passing for inter-process communication.
214
+ * Provides low-latency access without network overhead.
215
+ */
216
+ class IPCBackend implements LocalFirstBackend {
217
+ private channelName: string;
218
+
219
+ constructor(channelName: string = "pluresdb") {
220
+ this.channelName = channelName;
221
+ }
222
+
223
+ async initialize(): Promise<void> {
224
+ // Note: This is a placeholder. The actual IPC implementation will be
225
+ // in Phase 3 of the roadmap (pluresdb-ipc crate).
226
+ throw new Error(
227
+ "IPC backend not yet implemented. Please see docs/LOCAL_FIRST_INTEGRATION.md for implementation status.",
228
+ );
229
+ }
230
+
231
+ async put(id: string, data: unknown): Promise<string> {
232
+ throw new Error("IPC backend not yet implemented");
233
+ }
234
+
235
+ async get(id: string): Promise<unknown> {
236
+ throw new Error("IPC backend not yet implemented");
237
+ }
238
+
239
+ async delete(id: string): Promise<void> {
240
+ throw new Error("IPC backend not yet implemented");
241
+ }
242
+
243
+ async list(): Promise<unknown[]> {
244
+ throw new Error("IPC backend not yet implemented");
245
+ }
246
+
247
+ async vectorSearch(query: string, limit: number): Promise<unknown[]> {
248
+ throw new Error("IPC backend not yet implemented");
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Network Backend (HTTP REST API)
254
+ *
255
+ * Uses HTTP requests to communicate with a PluresDB server.
256
+ * This is the fallback mode for backward compatibility.
257
+ */
258
+ class NetworkBackend implements LocalFirstBackend {
259
+ private baseUrl: string;
260
+
261
+ constructor(url?: string, port?: number) {
262
+ this.baseUrl = url || `http://localhost:${port || 34567}`;
263
+ }
264
+
265
+ async put(id: string, data: unknown): Promise<string> {
266
+ const response = await fetch(`${this.baseUrl}/api/put`, {
267
+ method: "POST",
268
+ headers: { "Content-Type": "application/json" },
269
+ body: JSON.stringify({ id, data }),
270
+ });
271
+
272
+ if (!response.ok) {
273
+ throw new Error(`PUT failed: ${response.statusText}`);
274
+ }
275
+
276
+ // API returns { ok: true }, return the id
277
+ await response.json();
278
+ return id;
279
+ }
280
+
281
+ async get(id: string): Promise<unknown> {
282
+ const response = await fetch(`${this.baseUrl}/api/get?id=${encodeURIComponent(id)}`);
283
+
284
+ if (!response.ok) {
285
+ if (response.status === 404) return null;
286
+ throw new Error(`GET failed: ${response.statusText}`);
287
+ }
288
+
289
+ // API returns the node data directly (can be null)
290
+ return await response.json();
291
+ }
292
+
293
+ async delete(id: string): Promise<void> {
294
+ const response = await fetch(`${this.baseUrl}/api/delete?id=${encodeURIComponent(id)}`, {
295
+ method: "DELETE",
296
+ });
297
+
298
+ if (!response.ok) {
299
+ throw new Error(`DELETE failed: ${response.statusText}`);
300
+ }
301
+ }
302
+
303
+ async list(): Promise<unknown[]> {
304
+ const response = await fetch(`${this.baseUrl}/api/list`);
305
+
306
+ if (!response.ok) {
307
+ throw new Error(`LIST failed: ${response.statusText}`);
308
+ }
309
+
310
+ // API returns array of { id, data }
311
+ const nodes = await response.json() as Array<{ id: string; data: unknown }>;
312
+ return nodes;
313
+ }
314
+
315
+ async vectorSearch(query: string, limit: number): Promise<unknown[]> {
316
+ const response = await fetch(`${this.baseUrl}/api/search`, {
317
+ method: "POST",
318
+ headers: { "Content-Type": "application/json" },
319
+ body: JSON.stringify({ query, limit }),
320
+ });
321
+
322
+ if (!response.ok) {
323
+ throw new Error(`VECTOR_SEARCH failed: ${response.statusText}`);
324
+ }
325
+
326
+ // API returns array of { id, data }
327
+ const nodes = await response.json() as Array<{ id: string; data: unknown }>;
328
+ return nodes;
329
+ }
330
+ }
331
+
332
+ /**
333
+ * PluresDB Local-First API
334
+ *
335
+ * Unified interface that works across all platforms:
336
+ * - Browser (WASM)
337
+ * - Tauri (native)
338
+ * - Native apps (IPC)
339
+ * - Traditional apps (network)
340
+ *
341
+ * Automatically selects the best integration method or allows manual override.
342
+ *
343
+ * @example
344
+ * ```typescript
345
+ * // Auto-detect best mode
346
+ * const db = new PluresDBLocalFirst();
347
+ *
348
+ * // Manual mode selection
349
+ * const db = new PluresDBLocalFirst({ mode: "wasm", dbName: "my-app" });
350
+ *
351
+ * // Use the API (same across all modes)
352
+ * await db.put("user:1", { name: "Alice", email: "alice@example.com" });
353
+ * const user = await db.get("user:1");
354
+ * const results = await db.vectorSearch("Find users named Alice", 10);
355
+ * ```
356
+ */
357
+ export class PluresDBLocalFirst {
358
+ private backend: LocalFirstBackend;
359
+ private mode: string;
360
+
361
+ constructor(options: LocalFirstOptions = {}) {
362
+ const mode = options.mode || "auto";
363
+ const actualMode = mode === "auto" ? RuntimeDetector.detectBestMode() : mode;
364
+
365
+ this.mode = actualMode;
366
+
367
+ switch (actualMode) {
368
+ case "wasm":
369
+ this.backend = new WasmBackend(options.dbName);
370
+ break;
371
+
372
+ case "tauri":
373
+ this.backend = new TauriBackend();
374
+ break;
375
+
376
+ case "ipc":
377
+ this.backend = new IPCBackend(options.channelName);
378
+ break;
379
+
380
+ case "network":
381
+ this.backend = new NetworkBackend(options.networkUrl, options.port);
382
+ break;
383
+
384
+ default:
385
+ throw new Error(`Unknown mode: ${actualMode}`);
386
+ }
387
+
388
+ debugLog(`Initialized in ${this.mode} mode`);
389
+ }
390
+
391
+ /**
392
+ * Get the current integration mode
393
+ */
394
+ getMode(): string {
395
+ return this.mode;
396
+ }
397
+
398
+ /**
399
+ * Insert or update a node
400
+ */
401
+ async put(id: string, data: unknown): Promise<string> {
402
+ return this.backend.put(id, data);
403
+ }
404
+
405
+ /**
406
+ * Retrieve a node by ID
407
+ */
408
+ async get(id: string): Promise<unknown> {
409
+ return this.backend.get(id);
410
+ }
411
+
412
+ /**
413
+ * Delete a node by ID
414
+ */
415
+ async delete(id: string): Promise<void> {
416
+ return this.backend.delete(id);
417
+ }
418
+
419
+ /**
420
+ * List all nodes
421
+ */
422
+ async list(): Promise<unknown[]> {
423
+ return this.backend.list();
424
+ }
425
+
426
+ /**
427
+ * Vector search (semantic similarity)
428
+ */
429
+ async vectorSearch(query: string, limit: number = 10): Promise<unknown[]> {
430
+ if (!this.backend.vectorSearch) {
431
+ throw new Error("Vector search not supported in this mode");
432
+ }
433
+ return this.backend.vectorSearch(query, limit);
434
+ }
435
+
436
+ /**
437
+ * Close the database connection
438
+ */
439
+ async close(): Promise<void> {
440
+ if (this.backend.close) {
441
+ await this.backend.close();
442
+ }
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Legacy export for backward compatibility
448
+ */
449
+ export default PluresDBLocalFirst;
@@ -821,3 +821,7 @@ export {
821
821
  createPluresExtension,
822
822
  PluresVSCodeExtension,
823
823
  } from "./vscode/extension";
824
+
825
+ // Export local-first integration API
826
+ export { PluresDBLocalFirst } from "./local-first/unified-api";
827
+ export type { LocalFirstOptions, LocalFirstBackend } from "./local-first/unified-api";
@@ -0,0 +1,181 @@
1
+ # PluresDB Plugin System
2
+
3
+ The PluresDB plugin system allows you to extend the database with custom functionality.
4
+
5
+ ## Plugin Types
6
+
7
+ ### 1. Embedding Providers
8
+
9
+ Add custom embedding providers for vector search:
10
+
11
+ ```typescript
12
+ // For Deno projects
13
+ import { Plugin, EmbeddingProvider } from "../legacy/plugins/plugin-system.ts";
14
+
15
+ // For Node.js projects (after compilation)
16
+ // import { Plugin, EmbeddingProvider } from "./legacy/plugins/plugin-system";
17
+
18
+ class MyEmbeddingProvider implements EmbeddingProvider {
19
+ name = "my-embeddings";
20
+ dimensions = 384;
21
+
22
+ async embed(text: string): Promise<number[]> {
23
+ // Your embedding logic here
24
+ return new Array(this.dimensions).fill(0);
25
+ }
26
+ }
27
+
28
+ const plugin: Plugin = {
29
+ id: "my-plugin",
30
+ name: "My Plugin",
31
+ version: "1.0.0",
32
+ embeddingProviders: [new MyEmbeddingProvider()],
33
+ };
34
+ ```
35
+
36
+ ### 2. UI Panels
37
+
38
+ Add custom UI panels to the web interface:
39
+
40
+ ```typescript
41
+ import MyPanel from "./MyPanel.svelte";
42
+
43
+ const plugin: Plugin = {
44
+ id: "my-ui-plugin",
45
+ name: "My UI Plugin",
46
+ version: "1.0.0",
47
+ uiPanels: [{
48
+ id: "my-panel",
49
+ name: "My Panel",
50
+ icon: "🎨",
51
+ component: MyPanel,
52
+ order: 100,
53
+ }],
54
+ };
55
+ ```
56
+
57
+ ### 3. Query Transformers
58
+
59
+ Transform queries before execution:
60
+
61
+ ```typescript
62
+ const plugin: Plugin = {
63
+ id: "query-plugin",
64
+ name: "Query Plugin",
65
+ version: "1.0.0",
66
+ queryTransformers: [{
67
+ id: "my-transformer",
68
+ async transform(query: any) {
69
+ // Modify query here
70
+ return { ...query, enhanced: true };
71
+ },
72
+ }],
73
+ };
74
+ ```
75
+
76
+ ### 4. Data Validators
77
+
78
+ Validate data before storage:
79
+
80
+ ```typescript
81
+ const plugin: Plugin = {
82
+ id: "validator-plugin",
83
+ name: "Validator Plugin",
84
+ version: "1.0.0",
85
+ dataValidators: [{
86
+ id: "email-validator",
87
+ async validate(data: Record<string, unknown>) {
88
+ if (data.email && typeof data.email === "string") {
89
+ const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email);
90
+ if (!isValid) {
91
+ return {
92
+ valid: false,
93
+ errors: ["Invalid email format"],
94
+ };
95
+ }
96
+ }
97
+ return { valid: true };
98
+ },
99
+ }],
100
+ };
101
+ ```
102
+
103
+ ## Registering Plugins
104
+
105
+ ```typescript
106
+ // For Deno projects
107
+ import { pluginManager } from "../legacy/plugins/plugin-system.ts";
108
+ import { myPlugin } from "./my-plugin.ts";
109
+
110
+ // For Node.js projects (after compilation)
111
+ // import { pluginManager } from "./legacy/plugins/plugin-system";
112
+ // import { myPlugin } from "./my-plugin";
113
+
114
+ // Register plugin
115
+ await pluginManager.register(myPlugin);
116
+
117
+ // Use custom embedding provider
118
+ const provider = pluginManager.getEmbeddingProvider("my-embeddings");
119
+ if (provider) {
120
+ const embedding = await provider.embed("Hello, world!");
121
+ }
122
+
123
+ // Unregister plugin
124
+ await pluginManager.unregister("my-plugin");
125
+ ```
126
+
127
+ ## Complete Example
128
+
129
+ See `example-embedding-plugin.ts` for a complete example plugin.
130
+
131
+ ## API Reference
132
+
133
+ ### Plugin Interface
134
+
135
+ ```typescript
136
+ interface Plugin {
137
+ id: string;
138
+ name: string;
139
+ version: string;
140
+ description?: string;
141
+ embeddingProviders?: EmbeddingProvider[];
142
+ uiPanels?: UIPanel[];
143
+ queryTransformers?: QueryTransformer[];
144
+ dataValidators?: DataValidator[];
145
+ init?(): Promise<void>;
146
+ destroy?(): Promise<void>;
147
+ }
148
+ ```
149
+
150
+ ### PluginManager Methods
151
+
152
+ - `register(plugin: Plugin): Promise<void>` - Register a plugin
153
+ - `unregister(pluginId: string): Promise<void>` - Unregister a plugin
154
+ - `getPlugins(): Plugin[]` - Get all registered plugins
155
+ - `getEmbeddingProvider(name: string): EmbeddingProvider | undefined`
156
+ - `getEmbeddingProviders(): EmbeddingProvider[]`
157
+ - `getUIPanel(id: string): UIPanel | undefined`
158
+ - `getUIPanels(): UIPanel[]`
159
+ - `getQueryTransformer(id: string): QueryTransformer | undefined`
160
+ - `getQueryTransformers(): QueryTransformer[]`
161
+ - `getDataValidator(id: string): DataValidator | undefined`
162
+ - `getDataValidators(): DataValidator[]`
163
+ - `transformQuery(query: any): Promise<any>` - Apply all transformers
164
+ - `validateData(data: Record<string, unknown>): Promise<{valid: boolean; errors: string[]}>` - Validate with all validators
165
+
166
+ ## Best Practices
167
+
168
+ 1. **Unique IDs**: Use unique, descriptive IDs for plugins and their components
169
+ 2. **Error Handling**: Handle errors gracefully in your plugin code
170
+ 3. **Cleanup**: Implement `destroy()` to clean up resources
171
+ 4. **Documentation**: Document your plugin's functionality and configuration
172
+ 5. **Testing**: Test your plugin thoroughly before deployment
173
+ 6. **Versioning**: Use semantic versioning for your plugins
174
+
175
+ ## Security Considerations
176
+
177
+ - Validate all input data in your plugins
178
+ - Avoid storing sensitive data (API keys, etc.) in plugin code
179
+ - Use environment variables for configuration
180
+ - Review third-party dependencies carefully
181
+ - Test plugins in a safe environment before production use
@@ -0,0 +1,56 @@
1
+ import type { Plugin, EmbeddingProvider } from "./plugin-system";
2
+
3
+ /**
4
+ * Example Custom Embedding Provider Plugin
5
+ *
6
+ * This plugin demonstrates how to add a custom embedding provider to PluresDB.
7
+ * It provides a simple character-based embedding for demonstration purposes.
8
+ */
9
+
10
+ class SimpleEmbeddingProvider implements EmbeddingProvider {
11
+ name = "simple-char-embeddings";
12
+ dimensions = 256;
13
+
14
+ /**
15
+ * Generate simple character-based embeddings
16
+ * Maps characters to vector positions
17
+ */
18
+ async embed(text: string): Promise<number[]> {
19
+ const vector = new Array(this.dimensions).fill(0);
20
+
21
+ // Simple algorithm: increment vector position for each character
22
+ for (let i = 0; i < text.length && i < this.dimensions; i++) {
23
+ const charCode = text.charCodeAt(i);
24
+ vector[charCode % this.dimensions] += 1;
25
+ }
26
+
27
+ // Normalize
28
+ const magnitude = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
29
+ if (magnitude > 0) {
30
+ for (let i = 0; i < vector.length; i++) {
31
+ vector[i] /= magnitude;
32
+ }
33
+ }
34
+
35
+ return vector;
36
+ }
37
+ }
38
+
39
+ export const customEmbeddingsPlugin: Plugin = {
40
+ id: "custom-embeddings",
41
+ name: "Custom Embeddings Provider",
42
+ version: "1.0.0",
43
+ description: "Provides a simple character-based embedding provider for PluresDB",
44
+
45
+ embeddingProviders: [
46
+ new SimpleEmbeddingProvider(),
47
+ ],
48
+
49
+ async init() {
50
+ console.log("Custom embeddings plugin initialized");
51
+ },
52
+
53
+ async destroy() {
54
+ console.log("Custom embeddings plugin destroyed");
55
+ },
56
+ };