@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.
- package/README.md +174 -290
- package/dist/.tsbuildinfo +1 -1
- package/dist/local-first/unified-api.d.ts +110 -0
- package/dist/local-first/unified-api.d.ts.map +1 -0
- package/dist/local-first/unified-api.js +348 -0
- package/dist/local-first/unified-api.js.map +1 -0
- package/dist/node-index.d.ts +2 -0
- package/dist/node-index.d.ts.map +1 -1
- package/dist/node-index.js +4 -1
- package/dist/node-index.js.map +1 -1
- package/dist/util/debug.d.ts +3 -0
- package/dist/util/debug.d.ts.map +1 -0
- package/dist/util/debug.js +34 -0
- package/dist/util/debug.js.map +1 -0
- package/examples/browser-demo/README.md +204 -0
- package/examples/browser-demo/index.html +466 -0
- package/examples/browser-wasm-integration.md +411 -0
- package/examples/ipc-demo/README.md +127 -0
- package/examples/local-first-usage.ts +138 -0
- package/examples/native-ipc-integration.md +526 -0
- package/examples/tauri-demo/README.md +240 -0
- package/examples/tauri-integration.md +260 -0
- package/legacy/http/api-server.ts +131 -0
- package/legacy/index.ts +3 -0
- package/legacy/local-first/unified-api.ts +449 -0
- package/legacy/node-index.ts +4 -0
- package/legacy/plugins/README.md +181 -0
- package/legacy/plugins/example-embedding-plugin.ts +56 -0
- package/legacy/plugins/plugin-system.ts +315 -0
- package/legacy/tests/unit/local-first-api.test.ts +65 -0
- package/legacy/tests/unit/plugin-system.test.ts +388 -0
- package/legacy/util/debug.ts +14 -1
- package/package.json +8 -2
- package/scripts/validate-npm-publish.js +228 -0
|
@@ -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;
|
package/legacy/node-index.ts
CHANGED
|
@@ -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
|
+
};
|