devcode-canavar-pro 3.3.0
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/.env.example +9 -0
- package/README.md +82 -0
- package/bin/devcode.js +43 -0
- package/index.js +47 -0
- package/lib/BackupService.js +184 -0
- package/lib/CLI.js +87 -0
- package/lib/Cache.js +41 -0
- package/lib/Collection.js +295 -0
- package/lib/Core.js +164 -0
- package/lib/Dashboard.js +247 -0
- package/lib/Database.js +60 -0
- package/lib/Index.js +69 -0
- package/lib/Journal.js +79 -0
- package/lib/Middleware.js +50 -0
- package/lib/QueryParser.js +57 -0
- package/lib/RemoteClient.js +275 -0
- package/lib/Schema.js +54 -0
- package/lib/Server.js +224 -0
- package/lib/Storage.js +52 -0
- package/lib/UpdateParser.js +71 -0
- package/package.json +36 -0
- package/test_uri.js +31 -0
- package/vds.js +49 -0
- package/vds_setup.js +36 -0
- package/vds_start.js +27 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const crypto = require('crypto');
|
|
3
|
+
const EventEmitter = require('events');
|
|
4
|
+
const Journal = require('./Journal');
|
|
5
|
+
const QueryParser = require('./QueryParser');
|
|
6
|
+
const UpdateParser = require('./UpdateParser');
|
|
7
|
+
const Index = require('./Index');
|
|
8
|
+
const Middleware = require('./Middleware');
|
|
9
|
+
const Cache = require('./Cache');
|
|
10
|
+
const Storage = require('./Storage');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Collection Class
|
|
14
|
+
* Veri ekleme, silme, güncelleme ve sorgulama işlemlerini yönetir.
|
|
15
|
+
*/
|
|
16
|
+
class Collection extends EventEmitter {
|
|
17
|
+
constructor(name, dbPath) {
|
|
18
|
+
super();
|
|
19
|
+
this.name = name;
|
|
20
|
+
this.dbPath = dbPath;
|
|
21
|
+
this.filePath = path.join(dbPath, `${name}.devcode`);
|
|
22
|
+
this.journal = new Journal(dbPath);
|
|
23
|
+
this.middleware = new Middleware();
|
|
24
|
+
this.cache = new Cache(5000); // 5000 dökümanlık hızlı bellek
|
|
25
|
+
this.schema = null;
|
|
26
|
+
this.indexes = new Map();
|
|
27
|
+
this.queryStats = new Map(); // Auto-indexing için istatistikler
|
|
28
|
+
this.data = [];
|
|
29
|
+
this.transactionData = null;
|
|
30
|
+
this._load();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Koleksiyon için şema belirler.
|
|
35
|
+
*/
|
|
36
|
+
setSchema(schemaInstance) {
|
|
37
|
+
this.schema = schemaInstance;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Middleware kancası ekler.
|
|
42
|
+
*/
|
|
43
|
+
pre(action, fn) { this.middleware.pre(action, fn); }
|
|
44
|
+
post(action, fn) { this.middleware.post(action, fn); }
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Belirli bir alan için indeks oluşturur.
|
|
48
|
+
*/
|
|
49
|
+
createIndex(field) {
|
|
50
|
+
const index = new Index(this.name, field, this.dbPath);
|
|
51
|
+
this.indexes.set(field, index);
|
|
52
|
+
|
|
53
|
+
// Mevcut verileri indeksle
|
|
54
|
+
this.data.forEach(doc => {
|
|
55
|
+
if (doc[field]) index.add(doc[field], doc._id);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Veriyi diskten yükler.
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
_load() {
|
|
64
|
+
try {
|
|
65
|
+
this.data = Storage.load(this.filePath);
|
|
66
|
+
// Cache'e at
|
|
67
|
+
this.data.forEach(doc => this.cache.set(doc._id, doc));
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error(`Koleksiyon yükleme hatası (${this.name}):`, err);
|
|
70
|
+
this.data = [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Veriyi diske kaydeder.
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
_save() {
|
|
79
|
+
Storage.save(this.filePath, this.data);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Döküman ekler.
|
|
84
|
+
* @param {Object} doc
|
|
85
|
+
*/
|
|
86
|
+
async insert(doc) {
|
|
87
|
+
// 1. Şema Doğrulama
|
|
88
|
+
if (this.schema) this.schema.validate(doc);
|
|
89
|
+
|
|
90
|
+
// 2. Pre-Insert Middleware
|
|
91
|
+
await this.middleware.runPre('insert', doc);
|
|
92
|
+
|
|
93
|
+
this.journal.lock();
|
|
94
|
+
try {
|
|
95
|
+
const newDoc = {
|
|
96
|
+
_id: crypto.randomBytes(12).toString('hex'),
|
|
97
|
+
...doc,
|
|
98
|
+
createdAt: new Date()
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Günlüğe kaydet
|
|
102
|
+
await this.journal.record(this.name, 'INSERT', newDoc);
|
|
103
|
+
|
|
104
|
+
// Veriyi ekle (Transaction varsa oraya, yoksa ana veriye)
|
|
105
|
+
const target = this.transactionData || this.data;
|
|
106
|
+
target.push(newDoc);
|
|
107
|
+
|
|
108
|
+
// İndeksleri güncelle (Sadece transaction yoksa veya commit edilince)
|
|
109
|
+
if (!this.transactionData) {
|
|
110
|
+
this._save();
|
|
111
|
+
this.indexes.forEach((idx, field) => {
|
|
112
|
+
if (newDoc[field]) idx.add(newDoc[field], newDoc._id);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 3. Olay Yayınla (Real-time)
|
|
117
|
+
this.emit('insert', newDoc);
|
|
118
|
+
this.emit('change', { type: 'INSERT', data: newDoc });
|
|
119
|
+
|
|
120
|
+
// 4. Post-Insert Middleware
|
|
121
|
+
await this.middleware.runPost('insert', newDoc);
|
|
122
|
+
|
|
123
|
+
return newDoc;
|
|
124
|
+
} finally {
|
|
125
|
+
this.journal.unlock();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Toplu döküman ekler (Ultra Hızlı).
|
|
131
|
+
* @param {Object[]} docs
|
|
132
|
+
*/
|
|
133
|
+
async insertMany(docs) {
|
|
134
|
+
if (!Array.isArray(docs)) throw new Error('Dizi gönderilmelidir.');
|
|
135
|
+
|
|
136
|
+
this.journal.lock();
|
|
137
|
+
try {
|
|
138
|
+
const added = [];
|
|
139
|
+
for (let doc of docs) {
|
|
140
|
+
if (this.schema) this.schema.validate(doc);
|
|
141
|
+
const newDoc = {
|
|
142
|
+
_id: crypto.randomBytes(12).toString('hex'),
|
|
143
|
+
...doc,
|
|
144
|
+
createdAt: new Date()
|
|
145
|
+
};
|
|
146
|
+
added.push(newDoc);
|
|
147
|
+
|
|
148
|
+
// Toplu işlemde cache ve data güncelle
|
|
149
|
+
this.data.push(newDoc);
|
|
150
|
+
this.cache.set(newDoc._id, newDoc);
|
|
151
|
+
|
|
152
|
+
this.indexes.forEach((idx, field) => {
|
|
153
|
+
if (newDoc[field]) idx.add(newDoc[field], newDoc._id);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await this.journal.record(this.name, 'INSERT_MANY', { count: docs.length });
|
|
158
|
+
this._save();
|
|
159
|
+
this.emit('insertMany', added);
|
|
160
|
+
return added;
|
|
161
|
+
} finally {
|
|
162
|
+
this.journal.unlock();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Gelişmiş sorgu motorunu kullanarak arama yapar.
|
|
168
|
+
* (Eğer indeks varsa indeks üzerinden hızlandırır)
|
|
169
|
+
*/
|
|
170
|
+
find(query = {}) {
|
|
171
|
+
// 1. İndeks Kullanımı (Eğer tek bir alan üzerinden basit eşitlik aranıyorsa)
|
|
172
|
+
const queryFields = Object.keys(query);
|
|
173
|
+
|
|
174
|
+
// Eğer tek bir basit sorgu varsa ve indeksi varsa kullan
|
|
175
|
+
if (queryFields.length === 1 && typeof query[queryFields[0]] !== 'object') {
|
|
176
|
+
const field = queryFields[0];
|
|
177
|
+
const val = query[field];
|
|
178
|
+
|
|
179
|
+
if (this.indexes.has(field)) {
|
|
180
|
+
const ids = this.indexes.get(field).get(val);
|
|
181
|
+
return this.data.filter(item => ids.includes(item._id));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// İstatistik tut (Eğer çok sorgulanıyorsa otomatik indeksle)
|
|
185
|
+
this.queryStats.set(field, (this.queryStats.get(field) || 0) + 1);
|
|
186
|
+
if (this.queryStats.get(field) > 100) { // 100 sorguda bir indeks oluştur
|
|
187
|
+
console.log(`🚀 [Auto-Index] '${field}' alanı çok sorgulanıyor, indeksleniyor...`);
|
|
188
|
+
this.createIndex(field);
|
|
189
|
+
this.queryStats.set(field, -999999); // Tekrar tetiklenme
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return this.data.filter(item => QueryParser.matches(item, query));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Tek bir döküman bulur.
|
|
198
|
+
*/
|
|
199
|
+
findOne(query = {}) {
|
|
200
|
+
const results = this.find(query);
|
|
201
|
+
return results.length > 0 ? results[0] : null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Dökümanları günceller ($set, $inc, $push vb. destekler).
|
|
206
|
+
* @param {Object} query Filtreleme sorgusu
|
|
207
|
+
* @param {Object} updateQuery Güncelleme operatörleri
|
|
208
|
+
*/
|
|
209
|
+
async update(query, updateQuery) {
|
|
210
|
+
const targets = this.find(query);
|
|
211
|
+
if (targets.length === 0) return 0;
|
|
212
|
+
|
|
213
|
+
await this.middleware.runPre('update', { query, updateQuery });
|
|
214
|
+
|
|
215
|
+
targets.forEach(doc => {
|
|
216
|
+
UpdateParser.parse(doc, updateQuery);
|
|
217
|
+
doc.updatedAt = new Date();
|
|
218
|
+
this.cache.set(doc._id, doc);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
this._save();
|
|
222
|
+
await this.journal.record(this.name, 'UPDATE', { query, count: targets.length });
|
|
223
|
+
|
|
224
|
+
await this.middleware.runPost('update', targets);
|
|
225
|
+
this.emit('update', targets);
|
|
226
|
+
return targets.length;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Tek bir dökümanı günceller.
|
|
231
|
+
*/
|
|
232
|
+
async updateOne(query, updateQuery) {
|
|
233
|
+
const doc = this.findOne(query);
|
|
234
|
+
if (!doc) return false;
|
|
235
|
+
|
|
236
|
+
UpdateParser.parse(doc, updateQuery);
|
|
237
|
+
doc.updatedAt = new Date();
|
|
238
|
+
this.cache.set(doc._id, doc);
|
|
239
|
+
|
|
240
|
+
this._save();
|
|
241
|
+
await this.journal.record(this.name, 'UPDATE_ONE', { id: doc._id });
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Verileri siler.
|
|
247
|
+
*/
|
|
248
|
+
async remove(query = {}) {
|
|
249
|
+
const initialLength = this.data.length;
|
|
250
|
+
const toDeleteIDs = this.data
|
|
251
|
+
.filter(item => QueryParser.matches(item, query))
|
|
252
|
+
.map(item => item._id);
|
|
253
|
+
|
|
254
|
+
if (toDeleteIDs.length === 0) return 0;
|
|
255
|
+
|
|
256
|
+
await this.middleware.runPre('remove', query);
|
|
257
|
+
|
|
258
|
+
this.data = this.data.filter(item => !toDeleteIDs.includes(item._id));
|
|
259
|
+
toDeleteIDs.forEach(id => this.cache.del(id));
|
|
260
|
+
|
|
261
|
+
this._save();
|
|
262
|
+
await this.journal.record(this.name, 'REMOVE', { query, count: toDeleteIDs.length });
|
|
263
|
+
|
|
264
|
+
await this.middleware.runPost('remove', toDeleteIDs);
|
|
265
|
+
this.emit('remove', toDeleteIDs);
|
|
266
|
+
return toDeleteIDs.length;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// --- TRANSACTION DESTEĞİ ---
|
|
270
|
+
|
|
271
|
+
startTransaction() {
|
|
272
|
+
this.transactionData = JSON.parse(JSON.stringify(this.data));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
commitTransaction() {
|
|
276
|
+
if (this.transactionData) {
|
|
277
|
+
this.data = this.transactionData;
|
|
278
|
+
this.transactionData = null;
|
|
279
|
+
this._save();
|
|
280
|
+
// İndeksleri sıfırla ve yeniden oluştur
|
|
281
|
+
this.indexes.forEach((idx, field) => {
|
|
282
|
+
idx.clear();
|
|
283
|
+
this.data.forEach(doc => {
|
|
284
|
+
if (doc[field]) idx.add(doc[field], doc._id);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
abortTransaction() {
|
|
291
|
+
this.transactionData = null;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
module.exports = Collection;
|
package/lib/Core.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const Database = require('./Database');
|
|
4
|
+
const Dashboard = require('./Dashboard');
|
|
5
|
+
const CLI = require('./CLI');
|
|
6
|
+
const BackupService = require('./BackupService');
|
|
7
|
+
const Server = require('./Server');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* DevCode Core Manager
|
|
11
|
+
* Veritabanı yönetiminden sorumlu ana sınıf.
|
|
12
|
+
*/
|
|
13
|
+
class DevCode {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.dataPath = options.dataPath || path.join(process.cwd(), 'data');
|
|
16
|
+
this.databases = new Map();
|
|
17
|
+
this._backup = null;
|
|
18
|
+
|
|
19
|
+
// Veri klasörünü oluştur
|
|
20
|
+
if (!fs.existsSync(this.dataPath)) {
|
|
21
|
+
fs.mkdirSync(this.dataPath, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Otomatik Güncelleme Kontrolü
|
|
25
|
+
this.checkUpdate();
|
|
26
|
+
|
|
27
|
+
// Otomatik Yedekleme (VDS için önerilir)
|
|
28
|
+
if (options.backup) {
|
|
29
|
+
const backupOpts = typeof options.backup === 'object' ? options.backup : {};
|
|
30
|
+
this._backup = new BackupService(this.dataPath, backupOpts);
|
|
31
|
+
this._backup.start();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Otomatik Dashboard Başlatma
|
|
35
|
+
if (options.dashboard) {
|
|
36
|
+
this.startDashboard(options.port || 3000);
|
|
37
|
+
} else {
|
|
38
|
+
console.log(`\x1b[32m[DevCode]\x1b[0m Monster v2.0 aktif. Görsel panel için: \x1b[4m{ dashboard: true }\x1b[0m`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* NPM üzerinden sürüm kontrolü yapar.
|
|
44
|
+
*/
|
|
45
|
+
async checkUpdate() {
|
|
46
|
+
try {
|
|
47
|
+
const https = require('https');
|
|
48
|
+
const pkg = require('../package.json');
|
|
49
|
+
|
|
50
|
+
https.get(`https://registry.npmjs.org/${pkg.name}/latest`, (res) => {
|
|
51
|
+
let data = '';
|
|
52
|
+
res.on('data', chunk => data += chunk);
|
|
53
|
+
res.on('end', () => {
|
|
54
|
+
try {
|
|
55
|
+
const latest = JSON.parse(data).version;
|
|
56
|
+
if (latest !== pkg.version) {
|
|
57
|
+
console.log(`\n\x1b[33m[!] YENİ GÜNCELLEME SİNYALİ:\x1b[0m v${pkg.version} -> \x1b[32mv${latest}\x1b[0m`);
|
|
58
|
+
console.log(`\x1b[33m[!] Güncellemek için:\x1b[0m npm i ${pkg.name}@latest\n`);
|
|
59
|
+
}
|
|
60
|
+
} catch (e) { }
|
|
61
|
+
});
|
|
62
|
+
}).on('error', () => { });
|
|
63
|
+
} catch (e) { }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Bir veritabanına bağlanır veya yoksa oluşturur.
|
|
68
|
+
* @param {string} dbName Veritabanı adı
|
|
69
|
+
* @param {string} namespace İsteğe bağlı namespace (VDS Multi-tenancy için)
|
|
70
|
+
* @returns {Database} Veritabanı örneği
|
|
71
|
+
*/
|
|
72
|
+
use(dbName, namespace = null) {
|
|
73
|
+
if (!dbName || typeof dbName !== 'string') {
|
|
74
|
+
throw new Error('Geçersiz veritabanı adı.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const fullDbName = namespace ? `${namespace}/${dbName}` : dbName;
|
|
78
|
+
|
|
79
|
+
if (this.databases.has(fullDbName)) {
|
|
80
|
+
return this.databases.get(fullDbName);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const dbPath = path.join(this.dataPath, fullDbName);
|
|
84
|
+
const db = new Database(dbName, dbPath);
|
|
85
|
+
this.databases.set(fullDbName, db);
|
|
86
|
+
|
|
87
|
+
return db;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Mevcut veritabanlarını listeler.
|
|
92
|
+
* @param {string} namespace - Opsiyonel, sadece bu namespace içindeki DB'leri listeler.
|
|
93
|
+
* @returns {string[]} Veritabanı isimleri
|
|
94
|
+
*/
|
|
95
|
+
listDatabases(namespace = null) {
|
|
96
|
+
const searchPath = namespace ? path.join(this.dataPath, namespace) : this.dataPath;
|
|
97
|
+
if (!fs.existsSync(searchPath)) return [];
|
|
98
|
+
return fs.readdirSync(searchPath)
|
|
99
|
+
.filter(file => {
|
|
100
|
+
try {
|
|
101
|
+
return fs.statSync(path.join(searchPath, file)).isDirectory();
|
|
102
|
+
} catch (e) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Bir veritabanını tamamen siler.
|
|
110
|
+
* @param {string} dbName Silinecek veritabanı adı
|
|
111
|
+
*/
|
|
112
|
+
dropDatabase(dbName) {
|
|
113
|
+
const dbPath = path.join(this.dataPath, dbName);
|
|
114
|
+
if (fs.existsSync(dbPath)) {
|
|
115
|
+
fs.rmSync(dbPath, { recursive: true, force: true });
|
|
116
|
+
this.databases.delete(dbName);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* BackupService örneğine erişim sağlar.
|
|
124
|
+
* @returns {BackupService}
|
|
125
|
+
*/
|
|
126
|
+
get backup() {
|
|
127
|
+
if (!this._backup) {
|
|
128
|
+
throw new Error("Yedekleme servisi aktif değil. DevCode()'u { backup: true } ile başlatın.");
|
|
129
|
+
}
|
|
130
|
+
return this._backup;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Uzak bağlantı sunucusunu başlatır (VDS için).
|
|
135
|
+
* Kullanıcılar RemoteClient ile bu sunucuya bağlanabilir.
|
|
136
|
+
* @param {object} options
|
|
137
|
+
* @param {number} options.port - Port numarası (varsayılan: 4242)
|
|
138
|
+
* @param {string} options.secret - Güvenlik anahtarı
|
|
139
|
+
* @returns {Server}
|
|
140
|
+
*/
|
|
141
|
+
startServer(options = {}) {
|
|
142
|
+
const server = new Server(this, options);
|
|
143
|
+
server.start();
|
|
144
|
+
return server;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Görsel Dashboard'u başlatır.
|
|
149
|
+
*/
|
|
150
|
+
startDashboard(port = 3000) {
|
|
151
|
+
const dash = new Dashboard(this, port);
|
|
152
|
+
dash.start();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* İnteraktif Shell'i (CLI) başlatır.
|
|
157
|
+
*/
|
|
158
|
+
startShell() {
|
|
159
|
+
const shell = new CLI(this);
|
|
160
|
+
shell.start();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = DevCode;
|