waengine 1.7.3 → 1.7.4
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/CHANGELOG.md +29 -0
- package/README.md +34 -3
- package/package.json +3 -2
- package/src/ab-testing.js +698 -0
- package/src/advanced-scheduler.js +577 -0
- package/src/ai-features.js +459 -0
- package/src/ai-integration.js +2 -1
- package/src/analytics-manager.js +458 -0
- package/src/business-manager.js +362 -0
- package/src/client.js +447 -39
- package/src/console-logger.js +256 -0
- package/src/core.js +28 -3
- package/src/cross-platform.js +538 -0
- package/src/database-manager.js +766 -0
- package/src/device-manager.js +1 -1
- package/src/easy-bot-fixed.js +341 -0
- package/src/easy-bot.js +503 -22
- package/src/error-handler.js +230 -0
- package/src/gaming-manager.js +842 -0
- package/src/http-client.js +1 -1
- package/src/index.js +15 -0
- package/src/message.js +197 -94
- package/src/multi-client.js +26 -12
- package/src/plugin-manager.js +59 -10
- package/src/prefix-manager.js +48 -1
- package/src/qr-terminal-fix.js +239 -0
- package/src/qr.js +170 -27
- package/src/quick-bot.js +63 -0
- package/src/reporting-manager.js +867 -0
- package/src/scheduler.js +14 -1
- package/src/security-manager.js +678 -0
- package/src/session-manager-old.js +314 -0
- package/src/session-manager.js +429 -24
- package/src/storage.js +254 -194
- package/src/ui-components.js +560 -0
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
import { getStorage } from "./storage.js";
|
|
2
|
+
import { ErrorHandler } from "./error-handler.js";
|
|
3
|
+
|
|
4
|
+
export class DatabaseManager {
|
|
5
|
+
constructor(client) {
|
|
6
|
+
this.client = client;
|
|
7
|
+
this.storage = getStorage();
|
|
8
|
+
this.errorHandler = new ErrorHandler();
|
|
9
|
+
this.databases = new Map();
|
|
10
|
+
this.schemas = new Map();
|
|
11
|
+
this.indexes = new Map();
|
|
12
|
+
this.queryCache = new Map();
|
|
13
|
+
this.transactions = new Map();
|
|
14
|
+
|
|
15
|
+
this.initializeDatabase();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ===== INITIALIZATION =====
|
|
19
|
+
|
|
20
|
+
initializeDatabase() {
|
|
21
|
+
this.loadDatabases();
|
|
22
|
+
this.setupDefaultSchemas();
|
|
23
|
+
this.startMaintenanceJob();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
loadDatabases() {
|
|
27
|
+
try {
|
|
28
|
+
const dbData = this.storage.read.from("database").get("data") || {};
|
|
29
|
+
this.databases = new Map(Object.entries(dbData.databases || {}));
|
|
30
|
+
this.schemas = new Map(Object.entries(dbData.schemas || {}));
|
|
31
|
+
this.indexes = new Map(Object.entries(dbData.indexes || {}));
|
|
32
|
+
} catch (error) {
|
|
33
|
+
this.errorHandler.handle(error, 'DatabaseManager.loadDatabases');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setupDefaultSchemas() {
|
|
38
|
+
// User schema
|
|
39
|
+
this.createSchema('users', {
|
|
40
|
+
id: { type: 'string', required: true, unique: true },
|
|
41
|
+
name: { type: 'string', required: true },
|
|
42
|
+
phone: { type: 'string', unique: true },
|
|
43
|
+
email: { type: 'string' },
|
|
44
|
+
createdAt: { type: 'date', default: () => new Date() },
|
|
45
|
+
updatedAt: { type: 'date', default: () => new Date() },
|
|
46
|
+
isActive: { type: 'boolean', default: true },
|
|
47
|
+
metadata: { type: 'object', default: {} }
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Messages schema
|
|
51
|
+
this.createSchema('messages', {
|
|
52
|
+
id: { type: 'string', required: true, unique: true },
|
|
53
|
+
chatId: { type: 'string', required: true },
|
|
54
|
+
senderId: { type: 'string', required: true },
|
|
55
|
+
content: { type: 'string', required: true },
|
|
56
|
+
type: { type: 'string', enum: ['text', 'image', 'video', 'audio', 'document'] },
|
|
57
|
+
timestamp: { type: 'date', default: () => new Date() },
|
|
58
|
+
isRead: { type: 'boolean', default: false },
|
|
59
|
+
metadata: { type: 'object', default: {} }
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Groups schema
|
|
63
|
+
this.createSchema('groups', {
|
|
64
|
+
id: { type: 'string', required: true, unique: true },
|
|
65
|
+
name: { type: 'string', required: true },
|
|
66
|
+
description: { type: 'string' },
|
|
67
|
+
members: { type: 'array', default: [] },
|
|
68
|
+
admins: { type: 'array', default: [] },
|
|
69
|
+
createdAt: { type: 'date', default: () => new Date() },
|
|
70
|
+
settings: { type: 'object', default: {} }
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
startMaintenanceJob() {
|
|
75
|
+
setInterval(() => {
|
|
76
|
+
this.cleanupCache();
|
|
77
|
+
this.optimizeIndexes();
|
|
78
|
+
}, 300000); // Every 5 minutes
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ===== SCHEMA MANAGEMENT =====
|
|
82
|
+
|
|
83
|
+
createSchema(tableName, schema) {
|
|
84
|
+
try {
|
|
85
|
+
this.schemas.set(tableName, {
|
|
86
|
+
name: tableName,
|
|
87
|
+
fields: schema,
|
|
88
|
+
createdAt: Date.now(),
|
|
89
|
+
version: 1
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Create database table if it doesn't exist
|
|
93
|
+
if (!this.databases.has(tableName)) {
|
|
94
|
+
this.databases.set(tableName, new Map());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Create indexes for unique fields
|
|
98
|
+
this.createIndexesForSchema(tableName, schema);
|
|
99
|
+
|
|
100
|
+
this.saveDatabaseData();
|
|
101
|
+
console.log(`📊 Schema created: ${tableName}`);
|
|
102
|
+
|
|
103
|
+
return true;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
this.errorHandler.handle(error, 'DatabaseManager.createSchema');
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
createIndexesForSchema(tableName, schema) {
|
|
111
|
+
const tableIndexes = new Map();
|
|
112
|
+
|
|
113
|
+
Object.entries(schema).forEach(([fieldName, fieldConfig]) => {
|
|
114
|
+
if (fieldConfig.unique || fieldConfig.index) {
|
|
115
|
+
tableIndexes.set(fieldName, new Map());
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
this.indexes.set(tableName, tableIndexes);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
updateSchema(tableName, newSchema) {
|
|
123
|
+
try {
|
|
124
|
+
const existingSchema = this.schemas.get(tableName);
|
|
125
|
+
if (!existingSchema) {
|
|
126
|
+
throw new Error(`Schema not found: ${tableName}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
existingSchema.fields = { ...existingSchema.fields, ...newSchema };
|
|
130
|
+
existingSchema.version++;
|
|
131
|
+
existingSchema.updatedAt = Date.now();
|
|
132
|
+
|
|
133
|
+
this.saveDatabaseData();
|
|
134
|
+
return true;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
this.errorHandler.handle(error, 'DatabaseManager.updateSchema');
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ===== CRUD OPERATIONS =====
|
|
142
|
+
|
|
143
|
+
async insert(tableName, data) {
|
|
144
|
+
try {
|
|
145
|
+
const schema = this.schemas.get(tableName);
|
|
146
|
+
if (!schema) {
|
|
147
|
+
throw new Error(`Schema not found: ${tableName}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Validate data
|
|
151
|
+
const validatedData = this.validateData(data, schema.fields);
|
|
152
|
+
|
|
153
|
+
// Check unique constraints
|
|
154
|
+
await this.checkUniqueConstraints(tableName, validatedData);
|
|
155
|
+
|
|
156
|
+
// Generate ID if not provided
|
|
157
|
+
if (!validatedData.id) {
|
|
158
|
+
validatedData.id = this.generateId();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Get table
|
|
162
|
+
const table = this.databases.get(tableName);
|
|
163
|
+
|
|
164
|
+
// Insert data
|
|
165
|
+
table.set(validatedData.id, validatedData);
|
|
166
|
+
|
|
167
|
+
// Update indexes
|
|
168
|
+
this.updateIndexes(tableName, validatedData, 'insert');
|
|
169
|
+
|
|
170
|
+
this.saveDatabaseData();
|
|
171
|
+
|
|
172
|
+
console.log(`✅ Inserted into ${tableName}: ${validatedData.id}`);
|
|
173
|
+
return validatedData;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
this.errorHandler.handle(error, 'DatabaseManager.insert');
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async findById(tableName, id) {
|
|
181
|
+
try {
|
|
182
|
+
const table = this.databases.get(tableName);
|
|
183
|
+
if (!table) {
|
|
184
|
+
throw new Error(`Table not found: ${tableName}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return table.get(id) || null;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
this.errorHandler.handle(error, 'DatabaseManager.findById');
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async find(tableName, query = {}, options = {}) {
|
|
195
|
+
try {
|
|
196
|
+
const table = this.databases.get(tableName);
|
|
197
|
+
if (!table) {
|
|
198
|
+
throw new Error(`Table not found: ${tableName}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check cache first
|
|
202
|
+
const cacheKey = this.generateCacheKey(tableName, query, options);
|
|
203
|
+
if (this.queryCache.has(cacheKey)) {
|
|
204
|
+
return this.queryCache.get(cacheKey);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let results = Array.from(table.values());
|
|
208
|
+
|
|
209
|
+
// Apply filters
|
|
210
|
+
if (Object.keys(query).length > 0) {
|
|
211
|
+
results = this.applyFilters(results, query);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Apply sorting
|
|
215
|
+
if (options.sort) {
|
|
216
|
+
results = this.applySorting(results, options.sort);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Apply pagination
|
|
220
|
+
if (options.limit || options.offset) {
|
|
221
|
+
results = this.applyPagination(results, options);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Cache results
|
|
225
|
+
this.queryCache.set(cacheKey, results);
|
|
226
|
+
|
|
227
|
+
return results;
|
|
228
|
+
} catch (error) {
|
|
229
|
+
this.errorHandler.handle(error, 'DatabaseManager.find');
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async update(tableName, id, updateData) {
|
|
235
|
+
try {
|
|
236
|
+
const table = this.databases.get(tableName);
|
|
237
|
+
if (!table) {
|
|
238
|
+
throw new Error(`Table not found: ${tableName}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const existingData = table.get(id);
|
|
242
|
+
if (!existingData) {
|
|
243
|
+
throw new Error(`Record not found: ${id}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const schema = this.schemas.get(tableName);
|
|
247
|
+
const validatedData = this.validateData(updateData, schema.fields, true);
|
|
248
|
+
|
|
249
|
+
// Merge with existing data
|
|
250
|
+
const updatedData = { ...existingData, ...validatedData, updatedAt: new Date() };
|
|
251
|
+
|
|
252
|
+
// Check unique constraints
|
|
253
|
+
await this.checkUniqueConstraints(tableName, updatedData, id);
|
|
254
|
+
|
|
255
|
+
// Update record
|
|
256
|
+
table.set(id, updatedData);
|
|
257
|
+
|
|
258
|
+
// Update indexes
|
|
259
|
+
this.updateIndexes(tableName, updatedData, 'update', existingData);
|
|
260
|
+
|
|
261
|
+
// Clear cache
|
|
262
|
+
this.clearCacheForTable(tableName);
|
|
263
|
+
|
|
264
|
+
this.saveDatabaseData();
|
|
265
|
+
|
|
266
|
+
console.log(`✅ Updated ${tableName}: ${id}`);
|
|
267
|
+
return updatedData;
|
|
268
|
+
} catch (error) {
|
|
269
|
+
this.errorHandler.handle(error, 'DatabaseManager.update');
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async delete(tableName, id) {
|
|
275
|
+
try {
|
|
276
|
+
const table = this.databases.get(tableName);
|
|
277
|
+
if (!table) {
|
|
278
|
+
throw new Error(`Table not found: ${tableName}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const existingData = table.get(id);
|
|
282
|
+
if (!existingData) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Remove from table
|
|
287
|
+
table.delete(id);
|
|
288
|
+
|
|
289
|
+
// Update indexes
|
|
290
|
+
this.updateIndexes(tableName, existingData, 'delete');
|
|
291
|
+
|
|
292
|
+
// Clear cache
|
|
293
|
+
this.clearCacheForTable(tableName);
|
|
294
|
+
|
|
295
|
+
this.saveDatabaseData();
|
|
296
|
+
|
|
297
|
+
console.log(`🗑️ Deleted from ${tableName}: ${id}`);
|
|
298
|
+
return true;
|
|
299
|
+
} catch (error) {
|
|
300
|
+
this.errorHandler.handle(error, 'DatabaseManager.delete');
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ===== ADVANCED QUERIES =====
|
|
306
|
+
|
|
307
|
+
async aggregate(tableName, pipeline) {
|
|
308
|
+
try {
|
|
309
|
+
const table = this.databases.get(tableName);
|
|
310
|
+
if (!table) {
|
|
311
|
+
throw new Error(`Table not found: ${tableName}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let data = Array.from(table.values());
|
|
315
|
+
|
|
316
|
+
for (const stage of pipeline) {
|
|
317
|
+
data = this.applyAggregationStage(data, stage);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return data;
|
|
321
|
+
} catch (error) {
|
|
322
|
+
this.errorHandler.handle(error, 'DatabaseManager.aggregate');
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
applyAggregationStage(data, stage) {
|
|
328
|
+
const [operation, params] = Object.entries(stage)[0];
|
|
329
|
+
|
|
330
|
+
switch (operation) {
|
|
331
|
+
case '$match':
|
|
332
|
+
return this.applyFilters(data, params);
|
|
333
|
+
|
|
334
|
+
case '$group':
|
|
335
|
+
return this.applyGrouping(data, params);
|
|
336
|
+
|
|
337
|
+
case '$sort':
|
|
338
|
+
return this.applySorting(data, params);
|
|
339
|
+
|
|
340
|
+
case '$limit':
|
|
341
|
+
return data.slice(0, params);
|
|
342
|
+
|
|
343
|
+
case '$skip':
|
|
344
|
+
return data.slice(params);
|
|
345
|
+
|
|
346
|
+
case '$project':
|
|
347
|
+
return data.map(item => this.applyProjection(item, params));
|
|
348
|
+
|
|
349
|
+
default:
|
|
350
|
+
return data;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
applyGrouping(data, groupParams) {
|
|
355
|
+
const { _id, ...aggregations } = groupParams;
|
|
356
|
+
const groups = new Map();
|
|
357
|
+
|
|
358
|
+
data.forEach(item => {
|
|
359
|
+
const groupKey = this.evaluateGroupKey(item, _id);
|
|
360
|
+
|
|
361
|
+
if (!groups.has(groupKey)) {
|
|
362
|
+
groups.set(groupKey, []);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
groups.get(groupKey).push(item);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
return Array.from(groups.entries()).map(([key, items]) => {
|
|
369
|
+
const result = { _id: key };
|
|
370
|
+
|
|
371
|
+
Object.entries(aggregations).forEach(([field, operation]) => {
|
|
372
|
+
result[field] = this.applyAggregationOperation(items, operation);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
return result;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ===== TRANSACTIONS =====
|
|
380
|
+
|
|
381
|
+
async transaction(operations) {
|
|
382
|
+
const transactionId = this.generateId();
|
|
383
|
+
const rollbackData = new Map();
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
this.transactions.set(transactionId, { status: 'active', operations: [] });
|
|
387
|
+
|
|
388
|
+
for (const operation of operations) {
|
|
389
|
+
const { type, tableName, data, id } = operation;
|
|
390
|
+
|
|
391
|
+
// Store rollback data
|
|
392
|
+
if (type === 'update' || type === 'delete') {
|
|
393
|
+
const existing = await this.findById(tableName, id);
|
|
394
|
+
rollbackData.set(`${tableName}:${id}`, existing);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Execute operation
|
|
398
|
+
switch (type) {
|
|
399
|
+
case 'insert':
|
|
400
|
+
await this.insert(tableName, data);
|
|
401
|
+
break;
|
|
402
|
+
case 'update':
|
|
403
|
+
await this.update(tableName, id, data);
|
|
404
|
+
break;
|
|
405
|
+
case 'delete':
|
|
406
|
+
await this.delete(tableName, id);
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
this.transactions.get(transactionId).operations.push(operation);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
this.transactions.get(transactionId).status = 'committed';
|
|
414
|
+
console.log(`✅ Transaction committed: ${transactionId}`);
|
|
415
|
+
|
|
416
|
+
return { success: true, transactionId };
|
|
417
|
+
} catch (error) {
|
|
418
|
+
// Rollback
|
|
419
|
+
await this.rollbackTransaction(transactionId, rollbackData);
|
|
420
|
+
this.errorHandler.handle(error, 'DatabaseManager.transaction');
|
|
421
|
+
|
|
422
|
+
return { success: false, error: error.message };
|
|
423
|
+
} finally {
|
|
424
|
+
this.transactions.delete(transactionId);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async rollbackTransaction(transactionId, rollbackData) {
|
|
429
|
+
try {
|
|
430
|
+
const transaction = this.transactions.get(transactionId);
|
|
431
|
+
if (!transaction) return;
|
|
432
|
+
|
|
433
|
+
// Reverse operations
|
|
434
|
+
const operations = [...transaction.operations].reverse();
|
|
435
|
+
|
|
436
|
+
for (const operation of operations) {
|
|
437
|
+
const { type, tableName, id } = operation;
|
|
438
|
+
const rollbackKey = `${tableName}:${id}`;
|
|
439
|
+
|
|
440
|
+
switch (type) {
|
|
441
|
+
case 'insert':
|
|
442
|
+
await this.delete(tableName, id);
|
|
443
|
+
break;
|
|
444
|
+
case 'update':
|
|
445
|
+
const originalData = rollbackData.get(rollbackKey);
|
|
446
|
+
if (originalData) {
|
|
447
|
+
const table = this.databases.get(tableName);
|
|
448
|
+
table.set(id, originalData);
|
|
449
|
+
}
|
|
450
|
+
break;
|
|
451
|
+
case 'delete':
|
|
452
|
+
const deletedData = rollbackData.get(rollbackKey);
|
|
453
|
+
if (deletedData) {
|
|
454
|
+
await this.insert(tableName, deletedData);
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
console.log(`🔄 Transaction rolled back: ${transactionId}`);
|
|
461
|
+
} catch (error) {
|
|
462
|
+
this.errorHandler.handle(error, 'DatabaseManager.rollbackTransaction');
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ===== VALIDATION =====
|
|
467
|
+
|
|
468
|
+
validateData(data, schema, isUpdate = false) {
|
|
469
|
+
const validatedData = {};
|
|
470
|
+
|
|
471
|
+
Object.entries(schema).forEach(([fieldName, fieldConfig]) => {
|
|
472
|
+
const value = data[fieldName];
|
|
473
|
+
|
|
474
|
+
// Check required fields
|
|
475
|
+
if (fieldConfig.required && !isUpdate && (value === undefined || value === null)) {
|
|
476
|
+
throw new Error(`Required field missing: ${fieldName}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Skip validation if field not provided in update
|
|
480
|
+
if (isUpdate && value === undefined) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Apply default values
|
|
485
|
+
if (value === undefined && fieldConfig.default !== undefined) {
|
|
486
|
+
validatedData[fieldName] = typeof fieldConfig.default === 'function'
|
|
487
|
+
? fieldConfig.default()
|
|
488
|
+
: fieldConfig.default;
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Type validation
|
|
493
|
+
if (value !== undefined && value !== null) {
|
|
494
|
+
validatedData[fieldName] = this.validateFieldType(value, fieldConfig, fieldName);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
return validatedData;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
validateFieldType(value, fieldConfig, fieldName) {
|
|
502
|
+
switch (fieldConfig.type) {
|
|
503
|
+
case 'string':
|
|
504
|
+
if (typeof value !== 'string') {
|
|
505
|
+
throw new Error(`Field ${fieldName} must be a string`);
|
|
506
|
+
}
|
|
507
|
+
if (fieldConfig.enum && !fieldConfig.enum.includes(value)) {
|
|
508
|
+
throw new Error(`Field ${fieldName} must be one of: ${fieldConfig.enum.join(', ')}`);
|
|
509
|
+
}
|
|
510
|
+
return value;
|
|
511
|
+
|
|
512
|
+
case 'number':
|
|
513
|
+
const num = Number(value);
|
|
514
|
+
if (isNaN(num)) {
|
|
515
|
+
throw new Error(`Field ${fieldName} must be a number`);
|
|
516
|
+
}
|
|
517
|
+
return num;
|
|
518
|
+
|
|
519
|
+
case 'boolean':
|
|
520
|
+
return Boolean(value);
|
|
521
|
+
|
|
522
|
+
case 'date':
|
|
523
|
+
return value instanceof Date ? value : new Date(value);
|
|
524
|
+
|
|
525
|
+
case 'array':
|
|
526
|
+
return Array.isArray(value) ? value : [value];
|
|
527
|
+
|
|
528
|
+
case 'object':
|
|
529
|
+
return typeof value === 'object' ? value : {};
|
|
530
|
+
|
|
531
|
+
default:
|
|
532
|
+
return value;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// ===== UTILITY METHODS =====
|
|
537
|
+
|
|
538
|
+
applyFilters(data, query) {
|
|
539
|
+
return data.filter(item => {
|
|
540
|
+
return Object.entries(query).every(([key, value]) => {
|
|
541
|
+
if (typeof value === 'object' && value !== null) {
|
|
542
|
+
return this.applyOperatorFilter(item[key], value);
|
|
543
|
+
}
|
|
544
|
+
return item[key] === value;
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
applyOperatorFilter(fieldValue, operators) {
|
|
550
|
+
return Object.entries(operators).every(([operator, value]) => {
|
|
551
|
+
switch (operator) {
|
|
552
|
+
case '$eq': return fieldValue === value;
|
|
553
|
+
case '$ne': return fieldValue !== value;
|
|
554
|
+
case '$gt': return fieldValue > value;
|
|
555
|
+
case '$gte': return fieldValue >= value;
|
|
556
|
+
case '$lt': return fieldValue < value;
|
|
557
|
+
case '$lte': return fieldValue <= value;
|
|
558
|
+
case '$in': return Array.isArray(value) && value.includes(fieldValue);
|
|
559
|
+
case '$nin': return Array.isArray(value) && !value.includes(fieldValue);
|
|
560
|
+
case '$regex': return new RegExp(value).test(fieldValue);
|
|
561
|
+
case '$exists': return value ? fieldValue !== undefined : fieldValue === undefined;
|
|
562
|
+
default: return true;
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
applySorting(data, sortOptions) {
|
|
568
|
+
return data.sort((a, b) => {
|
|
569
|
+
for (const [field, direction] of Object.entries(sortOptions)) {
|
|
570
|
+
const aVal = a[field];
|
|
571
|
+
const bVal = b[field];
|
|
572
|
+
|
|
573
|
+
if (aVal < bVal) return direction === 1 ? -1 : 1;
|
|
574
|
+
if (aVal > bVal) return direction === 1 ? 1 : -1;
|
|
575
|
+
}
|
|
576
|
+
return 0;
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
applyPagination(data, options) {
|
|
581
|
+
const offset = options.offset || 0;
|
|
582
|
+
const limit = options.limit;
|
|
583
|
+
|
|
584
|
+
if (limit) {
|
|
585
|
+
return data.slice(offset, offset + limit);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return data.slice(offset);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async checkUniqueConstraints(tableName, data, excludeId = null) {
|
|
592
|
+
const schema = this.schemas.get(tableName);
|
|
593
|
+
const table = this.databases.get(tableName);
|
|
594
|
+
|
|
595
|
+
for (const [fieldName, fieldConfig] of Object.entries(schema.fields)) {
|
|
596
|
+
if (fieldConfig.unique && data[fieldName] !== undefined) {
|
|
597
|
+
for (const [id, record] of table) {
|
|
598
|
+
if (id !== excludeId && record[fieldName] === data[fieldName]) {
|
|
599
|
+
throw new Error(`Duplicate value for unique field ${fieldName}: ${data[fieldName]}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
updateIndexes(tableName, data, operation, oldData = null) {
|
|
607
|
+
const tableIndexes = this.indexes.get(tableName);
|
|
608
|
+
if (!tableIndexes) return;
|
|
609
|
+
|
|
610
|
+
for (const [fieldName, index] of tableIndexes) {
|
|
611
|
+
const value = data[fieldName];
|
|
612
|
+
|
|
613
|
+
switch (operation) {
|
|
614
|
+
case 'insert':
|
|
615
|
+
if (value !== undefined) {
|
|
616
|
+
index.set(value, data.id);
|
|
617
|
+
}
|
|
618
|
+
break;
|
|
619
|
+
|
|
620
|
+
case 'update':
|
|
621
|
+
if (oldData && oldData[fieldName] !== undefined) {
|
|
622
|
+
index.delete(oldData[fieldName]);
|
|
623
|
+
}
|
|
624
|
+
if (value !== undefined) {
|
|
625
|
+
index.set(value, data.id);
|
|
626
|
+
}
|
|
627
|
+
break;
|
|
628
|
+
|
|
629
|
+
case 'delete':
|
|
630
|
+
if (value !== undefined) {
|
|
631
|
+
index.delete(value);
|
|
632
|
+
}
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
generateId() {
|
|
639
|
+
return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
generateCacheKey(tableName, query, options) {
|
|
643
|
+
return `${tableName}:${JSON.stringify(query)}:${JSON.stringify(options)}`;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
clearCacheForTable(tableName) {
|
|
647
|
+
for (const key of this.queryCache.keys()) {
|
|
648
|
+
if (key.startsWith(`${tableName}:`)) {
|
|
649
|
+
this.queryCache.delete(key);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
cleanupCache() {
|
|
655
|
+
// Remove old cache entries (older than 10 minutes)
|
|
656
|
+
const maxAge = 10 * 60 * 1000;
|
|
657
|
+
const now = Date.now();
|
|
658
|
+
|
|
659
|
+
for (const [key, entry] of this.queryCache) {
|
|
660
|
+
if (entry.timestamp && (now - entry.timestamp) > maxAge) {
|
|
661
|
+
this.queryCache.delete(key);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
optimizeIndexes() {
|
|
667
|
+
// Rebuild indexes if needed
|
|
668
|
+
for (const [tableName, tableIndexes] of this.indexes) {
|
|
669
|
+
const table = this.databases.get(tableName);
|
|
670
|
+
if (!table) continue;
|
|
671
|
+
|
|
672
|
+
for (const [fieldName, index] of tableIndexes) {
|
|
673
|
+
index.clear();
|
|
674
|
+
|
|
675
|
+
for (const [id, record] of table) {
|
|
676
|
+
const value = record[fieldName];
|
|
677
|
+
if (value !== undefined) {
|
|
678
|
+
index.set(value, id);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
saveDatabaseData() {
|
|
686
|
+
try {
|
|
687
|
+
const dbData = {
|
|
688
|
+
databases: Object.fromEntries(
|
|
689
|
+
Array.from(this.databases.entries()).map(([name, table]) => [
|
|
690
|
+
name,
|
|
691
|
+
Object.fromEntries(table)
|
|
692
|
+
])
|
|
693
|
+
),
|
|
694
|
+
schemas: Object.fromEntries(this.schemas),
|
|
695
|
+
indexes: Object.fromEntries(
|
|
696
|
+
Array.from(this.indexes.entries()).map(([name, tableIndexes]) => [
|
|
697
|
+
name,
|
|
698
|
+
Object.fromEntries(
|
|
699
|
+
Array.from(tableIndexes.entries()).map(([field, index]) => [
|
|
700
|
+
field,
|
|
701
|
+
Object.fromEntries(index)
|
|
702
|
+
])
|
|
703
|
+
)
|
|
704
|
+
])
|
|
705
|
+
)
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
this.storage.write.to("database").set("data", dbData);
|
|
709
|
+
} catch (error) {
|
|
710
|
+
this.errorHandler.handle(error, 'DatabaseManager.saveDatabaseData');
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// ===== PUBLIC API =====
|
|
715
|
+
|
|
716
|
+
getTableStats(tableName) {
|
|
717
|
+
const table = this.databases.get(tableName);
|
|
718
|
+
const schema = this.schemas.get(tableName);
|
|
719
|
+
|
|
720
|
+
if (!table || !schema) return null;
|
|
721
|
+
|
|
722
|
+
return {
|
|
723
|
+
name: tableName,
|
|
724
|
+
recordCount: table.size,
|
|
725
|
+
schema: schema.fields,
|
|
726
|
+
version: schema.version,
|
|
727
|
+
createdAt: schema.createdAt,
|
|
728
|
+
updatedAt: schema.updatedAt
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
getAllTables() {
|
|
733
|
+
return Array.from(this.databases.keys());
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
exportTable(tableName) {
|
|
737
|
+
const table = this.databases.get(tableName);
|
|
738
|
+
if (!table) return null;
|
|
739
|
+
|
|
740
|
+
return {
|
|
741
|
+
tableName,
|
|
742
|
+
schema: this.schemas.get(tableName),
|
|
743
|
+
data: Array.from(table.values()),
|
|
744
|
+
exportedAt: new Date()
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
async importTable(tableData) {
|
|
749
|
+
const { tableName, schema, data } = tableData;
|
|
750
|
+
|
|
751
|
+
// Create schema
|
|
752
|
+
this.createSchema(tableName, schema.fields);
|
|
753
|
+
|
|
754
|
+
// Import data
|
|
755
|
+
const table = this.databases.get(tableName);
|
|
756
|
+
table.clear();
|
|
757
|
+
|
|
758
|
+
for (const record of data) {
|
|
759
|
+
table.set(record.id, record);
|
|
760
|
+
this.updateIndexes(tableName, record, 'insert');
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
this.saveDatabaseData();
|
|
764
|
+
console.log(`📥 Imported ${data.length} records into ${tableName}`);
|
|
765
|
+
}
|
|
766
|
+
}
|