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,586 @@
|
|
|
1
|
+
// SQLite Compatible API for PluresDB
|
|
2
|
+
// Provides drop-in replacement for sqlite3 and sqlite packages
|
|
3
|
+
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { PluresNode } from "./node-wrapper.ts";
|
|
7
|
+
|
|
8
|
+
export interface SQLiteConfig {
|
|
9
|
+
filename: string;
|
|
10
|
+
driver: any; // SQLite driver (ignored, but kept for compatibility)
|
|
11
|
+
mode?: number;
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DatabaseOptions {
|
|
16
|
+
filename: string;
|
|
17
|
+
driver?: any;
|
|
18
|
+
mode?: number;
|
|
19
|
+
verbose?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type RowRecord = Record<string, unknown>;
|
|
23
|
+
|
|
24
|
+
export class Database {
|
|
25
|
+
private plures: PluresNode;
|
|
26
|
+
private filename: string;
|
|
27
|
+
private isOpen: boolean = false;
|
|
28
|
+
private verbose: boolean = false;
|
|
29
|
+
|
|
30
|
+
constructor(options: DatabaseOptions) {
|
|
31
|
+
this.filename = options.filename;
|
|
32
|
+
this.verbose = options.verbose || false;
|
|
33
|
+
|
|
34
|
+
// Initialize PluresDB with the SQLite file path
|
|
35
|
+
const dataDir = resolve(dirname(options.filename), "pluresdb");
|
|
36
|
+
this.plures = new PluresNode({
|
|
37
|
+
config: {
|
|
38
|
+
dataDir,
|
|
39
|
+
port: 34567,
|
|
40
|
+
host: "localhost",
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async open(): Promise<Database> {
|
|
46
|
+
if (this.isOpen) {
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
await this.plures.start();
|
|
52
|
+
this.isOpen = true;
|
|
53
|
+
|
|
54
|
+
if (this.verbose) {
|
|
55
|
+
console.log(`Database opened: ${this.filename}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return this;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new Error(`Failed to open database: ${formatError(error)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async close(): Promise<void> {
|
|
65
|
+
if (!this.isOpen) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
await this.plures.stop();
|
|
71
|
+
this.isOpen = false;
|
|
72
|
+
|
|
73
|
+
if (this.verbose) {
|
|
74
|
+
console.log(`Database closed: ${this.filename}`);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new Error(`Failed to close database: ${formatError(error)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// SQLite-compatible methods
|
|
82
|
+
async exec(sql: string): Promise<void> {
|
|
83
|
+
if (!this.isOpen) {
|
|
84
|
+
throw new Error("Database is not open");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
// Parse SQL and execute
|
|
89
|
+
const statements = this.parseSQL(sql);
|
|
90
|
+
|
|
91
|
+
for (const statement of statements) {
|
|
92
|
+
await this.executeStatement(statement);
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
throw new Error(`SQL execution failed: ${formatError(error)}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async run(sql: string, params: any[] = []): Promise<{ lastID: number; changes: number }> {
|
|
100
|
+
if (!this.isOpen) {
|
|
101
|
+
throw new Error("Database is not open");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const result = await this.executeStatement({ sql, params });
|
|
106
|
+
return {
|
|
107
|
+
lastID: result.lastID || 0,
|
|
108
|
+
changes: result.changes || 0,
|
|
109
|
+
};
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new Error(`SQL run failed: ${formatError(error)}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async get(sql: string, params: any[] = []): Promise<any> {
|
|
116
|
+
if (!this.isOpen) {
|
|
117
|
+
throw new Error("Database is not open");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const results = await this.executeQuery(sql, params);
|
|
122
|
+
return results.length > 0 ? results[0] : undefined;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
throw new Error(`SQL get failed: ${formatError(error)}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async all(sql: string, params: any[] = []): Promise<any[]> {
|
|
129
|
+
if (!this.isOpen) {
|
|
130
|
+
throw new Error("Database is not open");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
return await this.executeQuery(sql, params);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
throw new Error(`SQL all failed: ${formatError(error)}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async each(sql: string, params: any[] = [], callback: (row: any) => void): Promise<number> {
|
|
141
|
+
if (!this.isOpen) {
|
|
142
|
+
throw new Error("Database is not open");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const results = await this.executeQuery(sql, params);
|
|
147
|
+
let count = 0;
|
|
148
|
+
|
|
149
|
+
for (const row of results) {
|
|
150
|
+
callback(row);
|
|
151
|
+
count++;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return count;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
throw new Error(`SQL each failed: ${formatError(error)}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Transaction support
|
|
161
|
+
async transaction<T>(fn: (db: Database) => Promise<T>): Promise<T> {
|
|
162
|
+
if (!this.isOpen) {
|
|
163
|
+
throw new Error("Database is not open");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
await this.exec("BEGIN TRANSACTION");
|
|
168
|
+
const result = await fn(this);
|
|
169
|
+
await this.exec("COMMIT");
|
|
170
|
+
return result;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
await this.exec("ROLLBACK");
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Prepare statements (simplified implementation)
|
|
178
|
+
prepare(sql: string): PreparedStatement {
|
|
179
|
+
return new PreparedStatement(this, sql);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Private helper methods
|
|
183
|
+
private parseSQL(sql: string): Array<{ sql: string; params?: any[] }> {
|
|
184
|
+
// Simple SQL parser - split by semicolon and trim
|
|
185
|
+
const statements = sql
|
|
186
|
+
.split(";")
|
|
187
|
+
.map((s) => s.trim())
|
|
188
|
+
.filter((s) => s.length > 0);
|
|
189
|
+
|
|
190
|
+
return statements.map((statement) => ({ sql: statement }));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private async executeStatement(statement: { sql: string; params?: any[] }): Promise<any> {
|
|
194
|
+
const sql = statement.sql.toLowerCase().trim();
|
|
195
|
+
|
|
196
|
+
if (sql.startsWith("create table")) {
|
|
197
|
+
return await this.createTable(statement.sql);
|
|
198
|
+
} else if (sql.startsWith("drop table")) {
|
|
199
|
+
return await this.dropTable(statement.sql);
|
|
200
|
+
} else if (sql.startsWith("insert")) {
|
|
201
|
+
return await this.insert(statement.sql, statement.params || []);
|
|
202
|
+
} else if (sql.startsWith("update")) {
|
|
203
|
+
return await this.update(statement.sql, statement.params || []);
|
|
204
|
+
} else if (sql.startsWith("delete")) {
|
|
205
|
+
return await this.delete(statement.sql, statement.params || []);
|
|
206
|
+
} else if (sql.startsWith("begin") || sql.startsWith("commit") || sql.startsWith("rollback")) {
|
|
207
|
+
// Transaction commands - handled by transaction method
|
|
208
|
+
return { changes: 0 };
|
|
209
|
+
} else {
|
|
210
|
+
throw new Error(`Unsupported SQL statement: ${statement.sql}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private async executeQuery(sql: string, params: any[]): Promise<RowRecord[]> {
|
|
215
|
+
const sqlLower = sql.toLowerCase().trim();
|
|
216
|
+
|
|
217
|
+
if (sqlLower.startsWith("select")) {
|
|
218
|
+
return await this.select(sql, params);
|
|
219
|
+
} else {
|
|
220
|
+
throw new Error(`Unsupported query: ${sql}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private async createTable(sql: string): Promise<void> {
|
|
225
|
+
// Extract table name and columns from CREATE TABLE statement
|
|
226
|
+
const tableMatch = sql.match(/CREATE TABLE\s+(?:IF NOT EXISTS\s+)?(\w+)\s*\(([^)]+)\)/i);
|
|
227
|
+
if (!tableMatch) {
|
|
228
|
+
throw new Error(`Invalid CREATE TABLE statement: ${sql}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const tableName = tableMatch[1];
|
|
232
|
+
const columns = tableMatch[2].split(",").map((col) => col.trim());
|
|
233
|
+
|
|
234
|
+
// Store table schema in PluresDB
|
|
235
|
+
await this.plures.put(`schema:${tableName}`, {
|
|
236
|
+
name: tableName,
|
|
237
|
+
columns: columns,
|
|
238
|
+
created_at: new Date().toISOString(),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (this.verbose) {
|
|
242
|
+
console.log(`Table created: ${tableName}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private async dropTable(sql: string): Promise<void> {
|
|
247
|
+
const tableMatch = sql.match(/DROP TABLE\s+(?:IF EXISTS\s+)?(\w+)/i);
|
|
248
|
+
if (!tableMatch) {
|
|
249
|
+
throw new Error(`Invalid DROP TABLE statement: ${sql}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const tableName = tableMatch[1];
|
|
253
|
+
|
|
254
|
+
// Remove table schema and all data
|
|
255
|
+
await this.plures.delete(`schema:${tableName}`);
|
|
256
|
+
|
|
257
|
+
// Delete all rows for this table
|
|
258
|
+
const rows: RowRecord[] = await this.plures.query(`table:${tableName}:*`);
|
|
259
|
+
for (const row of rows) {
|
|
260
|
+
await this.plures.delete(getRowId(row));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (this.verbose) {
|
|
264
|
+
console.log(`Table dropped: ${tableName}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private async insert(sql: string, params: any[]): Promise<{ lastID: number; changes: number }> {
|
|
269
|
+
const insertMatch = sql.match(
|
|
270
|
+
/INSERT\s+(?:INTO\s+)?(\w+)\s*\(([^)]+)\)\s*VALUES\s*\(([^)]+)\)/i,
|
|
271
|
+
);
|
|
272
|
+
if (!insertMatch) {
|
|
273
|
+
throw new Error(`Invalid INSERT statement: ${sql}`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const tableName = insertMatch[1];
|
|
277
|
+
const columns = insertMatch[2].split(",").map((col) => col.trim());
|
|
278
|
+
const values = insertMatch[3].split(",").map((val) => val.trim());
|
|
279
|
+
|
|
280
|
+
// Replace ? placeholders with actual values
|
|
281
|
+
const actualValues = values.map((val, index) => {
|
|
282
|
+
if (val === "?") {
|
|
283
|
+
return params[index] || null;
|
|
284
|
+
} else if (val.startsWith("'") && val.endsWith("'")) {
|
|
285
|
+
return val.slice(1, -1); // Remove quotes
|
|
286
|
+
} else {
|
|
287
|
+
return val;
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Create row object
|
|
292
|
+
const row: any = {};
|
|
293
|
+
columns.forEach((col, index) => {
|
|
294
|
+
row[col] = actualValues[index];
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Generate unique ID
|
|
298
|
+
const id = `${tableName}:${Date.now()}:${Math.random().toString(36).substr(2, 9)}`;
|
|
299
|
+
row.id = id;
|
|
300
|
+
row.created_at = new Date().toISOString();
|
|
301
|
+
|
|
302
|
+
// Store in PluresDB
|
|
303
|
+
await this.plures.put(`table:${tableName}:${id}`, row);
|
|
304
|
+
|
|
305
|
+
if (this.verbose) {
|
|
306
|
+
console.log(`Row inserted into ${tableName}:`, row);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return { lastID: 0, changes: 1 };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private async update(sql: string, params: any[]): Promise<{ changes: number }> {
|
|
313
|
+
const updateMatch = sql.match(/UPDATE\s+(\w+)\s+SET\s+([^WHERE]+)(?:\s+WHERE\s+(.+))?/i);
|
|
314
|
+
if (!updateMatch) {
|
|
315
|
+
throw new Error(`Invalid UPDATE statement: ${sql}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const tableName = updateMatch[1];
|
|
319
|
+
const setClause = updateMatch[2];
|
|
320
|
+
const whereClause = updateMatch[3];
|
|
321
|
+
|
|
322
|
+
// Parse SET clause
|
|
323
|
+
const setPairs = setClause.split(",").map((pair) => pair.trim());
|
|
324
|
+
const updates: any = {};
|
|
325
|
+
|
|
326
|
+
setPairs.forEach((pair, index) => {
|
|
327
|
+
const [column, value] = pair.split("=").map((s) => s.trim());
|
|
328
|
+
if (value === "?") {
|
|
329
|
+
updates[column] = params[index] || null;
|
|
330
|
+
} else if (value.startsWith("'") && value.endsWith("'")) {
|
|
331
|
+
updates[column] = value.slice(1, -1);
|
|
332
|
+
} else {
|
|
333
|
+
updates[column] = value;
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Find rows to update
|
|
338
|
+
const rows: RowRecord[] = await this.plures.query(`table:${tableName}:*`);
|
|
339
|
+
let changes = 0;
|
|
340
|
+
|
|
341
|
+
for (const row of rows) {
|
|
342
|
+
if (whereClause) {
|
|
343
|
+
// Simple WHERE clause evaluation (basic implementation)
|
|
344
|
+
if (this.evaluateWhereClause(row, whereClause, params)) {
|
|
345
|
+
const updatedRow = { ...row, ...updates, updated_at: new Date().toISOString() };
|
|
346
|
+
await this.plures.put(getRowId(row), updatedRow);
|
|
347
|
+
changes++;
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
// Update all rows
|
|
351
|
+
const updatedRow = { ...row, ...updates, updated_at: new Date().toISOString() };
|
|
352
|
+
await this.plures.put(getRowId(row), updatedRow);
|
|
353
|
+
changes++;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (this.verbose) {
|
|
358
|
+
console.log(`Updated ${changes} rows in ${tableName}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return { changes };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private async delete(sql: string, params: any[]): Promise<{ changes: number }> {
|
|
365
|
+
const deleteMatch = sql.match(/DELETE\s+FROM\s+(\w+)(?:\s+WHERE\s+(.+))?/i);
|
|
366
|
+
if (!deleteMatch) {
|
|
367
|
+
throw new Error(`Invalid DELETE statement: ${sql}`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const tableName = deleteMatch[1];
|
|
371
|
+
const whereClause = deleteMatch[2];
|
|
372
|
+
|
|
373
|
+
// Find rows to delete
|
|
374
|
+
const rows: RowRecord[] = await this.plures.query(`table:${tableName}:*`);
|
|
375
|
+
let changes = 0;
|
|
376
|
+
|
|
377
|
+
for (const row of rows) {
|
|
378
|
+
if (whereClause) {
|
|
379
|
+
// Simple WHERE clause evaluation
|
|
380
|
+
if (this.evaluateWhereClause(row, whereClause, params)) {
|
|
381
|
+
await this.plures.delete(getRowId(row));
|
|
382
|
+
changes++;
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
// Delete all rows
|
|
386
|
+
await this.plures.delete(getRowId(row));
|
|
387
|
+
changes++;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (this.verbose) {
|
|
392
|
+
console.log(`Deleted ${changes} rows from ${tableName}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return { changes };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private async select(sql: string, params: any[]): Promise<RowRecord[]> {
|
|
399
|
+
const selectMatch = sql.match(
|
|
400
|
+
/SELECT\s+(.+?)\s+FROM\s+(\w+)(?:\s+WHERE\s+(.+))?(?:\s+ORDER\s+BY\s+(.+))?(?:\s+LIMIT\s+(\d+))?/i,
|
|
401
|
+
);
|
|
402
|
+
if (!selectMatch) {
|
|
403
|
+
throw new Error(`Invalid SELECT statement: ${sql}`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const columns = selectMatch[1];
|
|
407
|
+
const tableName = selectMatch[2];
|
|
408
|
+
const whereClause = selectMatch[3];
|
|
409
|
+
const orderBy = selectMatch[4];
|
|
410
|
+
const limit = selectMatch[5] ? parseInt(selectMatch[5]) : undefined;
|
|
411
|
+
|
|
412
|
+
// Get all rows for the table
|
|
413
|
+
const rows: RowRecord[] = await this.plures.query(`table:${tableName}:*`);
|
|
414
|
+
let results: RowRecord[] = rows;
|
|
415
|
+
|
|
416
|
+
// Apply WHERE clause
|
|
417
|
+
if (whereClause) {
|
|
418
|
+
results = results.filter((row) => this.evaluateWhereClause(row, whereClause, params));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Apply ORDER BY
|
|
422
|
+
if (orderBy) {
|
|
423
|
+
const [column, direction] = orderBy.split(/\s+/);
|
|
424
|
+
const isDesc = direction?.toLowerCase() === "desc";
|
|
425
|
+
results.sort((a, b) => compareValues(a[column], b[column], isDesc));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Apply LIMIT
|
|
429
|
+
if (limit) {
|
|
430
|
+
results = results.slice(0, limit);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Select specific columns
|
|
434
|
+
if (columns !== "*") {
|
|
435
|
+
const columnList = columns.split(",").map((col) => col.trim());
|
|
436
|
+
results = results.map((row) => {
|
|
437
|
+
const selectedRow: RowRecord = {};
|
|
438
|
+
columnList.forEach((col) => {
|
|
439
|
+
selectedRow[col] = row[col];
|
|
440
|
+
});
|
|
441
|
+
return selectedRow;
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (this.verbose) {
|
|
446
|
+
console.log(`Selected ${results.length} rows from ${tableName}`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return results;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private evaluateWhereClause(row: RowRecord, whereClause: string, params: any[]): boolean {
|
|
453
|
+
// Simple WHERE clause evaluation
|
|
454
|
+
// This is a basic implementation - in production, you'd want a proper SQL parser
|
|
455
|
+
|
|
456
|
+
// Handle simple equality comparisons
|
|
457
|
+
const equalityMatch = whereClause.match(/(\w+)\s*=\s*\?/);
|
|
458
|
+
if (equalityMatch) {
|
|
459
|
+
const column = equalityMatch[1];
|
|
460
|
+
const value = params[0];
|
|
461
|
+
return row[column] === value;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Handle string equality
|
|
465
|
+
const stringMatch = whereClause.match(/(\w+)\s*=\s*'([^']+)'/);
|
|
466
|
+
if (stringMatch) {
|
|
467
|
+
const column = stringMatch[1];
|
|
468
|
+
const value = stringMatch[2];
|
|
469
|
+
return row[column] === value;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Default to true for unsupported WHERE clauses
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function formatError(error: unknown): string {
|
|
478
|
+
if (error instanceof Error) {
|
|
479
|
+
return error.message;
|
|
480
|
+
}
|
|
481
|
+
if (typeof error === "string") {
|
|
482
|
+
return error;
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
return JSON.stringify(error);
|
|
486
|
+
} catch {
|
|
487
|
+
return "Unknown error";
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function getRowId(row: RowRecord): string {
|
|
492
|
+
const id = row["id"];
|
|
493
|
+
if (typeof id === "string" && id.length > 0) {
|
|
494
|
+
return id;
|
|
495
|
+
}
|
|
496
|
+
throw new Error("Row is missing a valid string id");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function compareValues(a: unknown, b: unknown, desc = false): number {
|
|
500
|
+
const normalize = (value: unknown): string => {
|
|
501
|
+
if (value === null || value === undefined) {
|
|
502
|
+
return "";
|
|
503
|
+
}
|
|
504
|
+
if (typeof value === "string") {
|
|
505
|
+
return value;
|
|
506
|
+
}
|
|
507
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
508
|
+
return value.toString();
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
return JSON.stringify(value);
|
|
512
|
+
} catch {
|
|
513
|
+
return String(value);
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const normalizedA = normalize(a);
|
|
518
|
+
const normalizedB = normalize(b);
|
|
519
|
+
const result = normalizedA.localeCompare(normalizedB, undefined, {
|
|
520
|
+
numeric: true,
|
|
521
|
+
sensitivity: "base",
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
return desc ? -result : result;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export class PreparedStatement {
|
|
528
|
+
private db: Database;
|
|
529
|
+
private sql: string;
|
|
530
|
+
|
|
531
|
+
constructor(db: Database, sql: string) {
|
|
532
|
+
this.db = db;
|
|
533
|
+
this.sql = sql;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async run(params: any[] = []): Promise<{ lastID: number; changes: number }> {
|
|
537
|
+
return await this.db.run(this.sql, params);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async get(params: any[] = []): Promise<any> {
|
|
541
|
+
return await this.db.get(this.sql, params);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async all(params: any[] = []): Promise<any[]> {
|
|
545
|
+
return await this.db.all(this.sql, params);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async each(params: any[] = [], callback: (row: any) => void): Promise<number> {
|
|
549
|
+
return await this.db.each(this.sql, params, callback);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
finalize(): void {
|
|
553
|
+
// No-op for compatibility
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// SQLite3 driver compatibility
|
|
558
|
+
export class SQLite3Database extends Database {
|
|
559
|
+
constructor(filename: string, callback?: (err: Error | null) => void) {
|
|
560
|
+
super({ filename });
|
|
561
|
+
|
|
562
|
+
if (callback) {
|
|
563
|
+
this.open()
|
|
564
|
+
.then(() => callback(null))
|
|
565
|
+
.catch((err) => callback(err));
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
static Database = Database;
|
|
570
|
+
static OPEN_READONLY = 1;
|
|
571
|
+
static OPEN_READWRITE = 2;
|
|
572
|
+
static OPEN_CREATE = 4;
|
|
573
|
+
static OPEN_FULLMUTEX = 0x00010000;
|
|
574
|
+
static OPEN_SHAREDCACHE = 0x00020000;
|
|
575
|
+
static OPEN_PRIVATECACHE = 0x00040000;
|
|
576
|
+
static OPEN_URI = 0x00000040;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Main export function - SQLite compatible API
|
|
580
|
+
export async function open(options: SQLiteConfig): Promise<Database> {
|
|
581
|
+
const db = new Database(options);
|
|
582
|
+
return await db.open();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Export for compatibility with sqlite package
|
|
586
|
+
export { Database as default };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// SQLite3 Compatible API for PluresDB
|
|
2
|
+
// Provides drop-in replacement for sqlite3 package
|
|
3
|
+
|
|
4
|
+
import { Database as PluresDBDatabase } from "./sqlite-compat.ts";
|
|
5
|
+
|
|
6
|
+
export class Database extends PluresDBDatabase {
|
|
7
|
+
constructor(filename: string, callback?: (err: Error | null) => void) {
|
|
8
|
+
super({ filename });
|
|
9
|
+
|
|
10
|
+
if (callback) {
|
|
11
|
+
this.open()
|
|
12
|
+
.then(() => callback(null))
|
|
13
|
+
.catch((err) => callback(err));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// SQLite3 specific methods
|
|
18
|
+
serialize(callback?: (err: Error | null, sql: string) => void): void {
|
|
19
|
+
if (callback) {
|
|
20
|
+
callback(null, "-- Serialization not supported in PluresDB");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
parallelize(callback?: (err: Error | null) => void): void {
|
|
25
|
+
if (callback) {
|
|
26
|
+
callback(null);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
configure(option: string, value: any): void {
|
|
31
|
+
// No-op for compatibility
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interrupt(): void {
|
|
35
|
+
// No-op for compatibility
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
loadExtension(path: string, callback?: (err: Error | null) => void): void {
|
|
39
|
+
if (callback) {
|
|
40
|
+
callback(new Error("Extensions not supported in PluresDB"), null);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Export constants for compatibility
|
|
46
|
+
export const OPEN_READONLY = 1;
|
|
47
|
+
export const OPEN_READWRITE = 2;
|
|
48
|
+
export const OPEN_CREATE = 4;
|
|
49
|
+
export const OPEN_FULLMUTEX = 0x00010000;
|
|
50
|
+
export const OPEN_SHAREDCACHE = 0x00020000;
|
|
51
|
+
export const OPEN_PRIVATECACHE = 0x00040000;
|
|
52
|
+
export const OPEN_URI = 0x00000040;
|
|
53
|
+
|
|
54
|
+
// Export Database as default
|
|
55
|
+
export default Database;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { NodeRecord } from "../types/index.ts";
|
|
2
|
+
|
|
3
|
+
export class KvStorage {
|
|
4
|
+
private kv: Deno.Kv | null = null;
|
|
5
|
+
|
|
6
|
+
async open(path?: string): Promise<void> {
|
|
7
|
+
this.kv = await Deno.openKv(path);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async close(): Promise<void> {
|
|
11
|
+
if (this.kv) {
|
|
12
|
+
try {
|
|
13
|
+
this.kv.close();
|
|
14
|
+
} catch {
|
|
15
|
+
/* ignore */
|
|
16
|
+
}
|
|
17
|
+
this.kv = null;
|
|
18
|
+
}
|
|
19
|
+
// Allow microtasks to flush for callers awaiting close()
|
|
20
|
+
await Promise.resolve();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private ensureKv(): Deno.Kv {
|
|
24
|
+
if (!this.kv) {
|
|
25
|
+
throw new Error("KvStorage is not opened. Call open() first.");
|
|
26
|
+
}
|
|
27
|
+
return this.kv;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getNode(id: string): Promise<NodeRecord | null> {
|
|
31
|
+
const kv = this.ensureKv();
|
|
32
|
+
const res = await kv.get<NodeRecord>(["node", id]);
|
|
33
|
+
return res.value ?? null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async setNode(node: NodeRecord): Promise<void> {
|
|
37
|
+
const kv = this.ensureKv();
|
|
38
|
+
await kv.set(["node", node.id], node);
|
|
39
|
+
|
|
40
|
+
// Store version history
|
|
41
|
+
const historyKey = ["history", node.id, node.timestamp];
|
|
42
|
+
await kv.set(historyKey, node);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async deleteNode(id: string): Promise<void> {
|
|
46
|
+
const kv = this.ensureKv();
|
|
47
|
+
await kv.delete(["node", id]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async *listNodes(): AsyncIterable<NodeRecord> {
|
|
51
|
+
const kv = this.ensureKv();
|
|
52
|
+
for await (const entry of kv.list<NodeRecord>({ prefix: ["node"] })) {
|
|
53
|
+
if (entry.value) yield entry.value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async *listNodeHistory(id: string): AsyncIterable<NodeRecord> {
|
|
58
|
+
const kv = this.ensureKv();
|
|
59
|
+
for await (const entry of kv.list<NodeRecord>({ prefix: ["history", id] })) {
|
|
60
|
+
if (entry.value) yield entry.value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getNodeHistory(id: string): Promise<NodeRecord[]> {
|
|
65
|
+
const history: NodeRecord[] = [];
|
|
66
|
+
for await (const version of this.listNodeHistory(id)) {
|
|
67
|
+
history.push(version);
|
|
68
|
+
}
|
|
69
|
+
return history.sort((a, b) => b.timestamp - a.timestamp); // Most recent first
|
|
70
|
+
}
|
|
71
|
+
}
|