@motiontr/motiondb
v1.0.1
Published
Discord botları için hızlı, atomik ve kullanımı kolay JSON veritabanı (SQLite yok, tek dosya)
Maintainers
Readme
motiondb
Discord botları için tek dosyalık, atomik ve hızlı JSON veritabanı.
discord.js ile birlikte kullanılmak üzere tasarlandı, ama framework'ten bağımsızdır.
{
"guilds": {
"123456789012345678": { "modLog": "918273645091827364", "welcome": { "channelId": "987654321098765432" } }
},
"users": {
"555555555555555555": { "coins": 250, "inventory": ["ticket", "vip"] }
},
"cooldowns": { "555555555555555555:daily": 1730000000000 }
}Kurulum
npm install
npm run build # TypeScript derleme -> dist/
npm test # 49 test (23 çekirdek + 26 API)
npm run example # examples/basic.js
npm run example:api # examples/api.js (MotionDB API turu)Kendi projenizde:
npm install @motiontr/motiondbimport { MotionDB, JSONDatabase } from '@motiontr/motiondb';
const db = new JSONDatabase({ path: './data/db.json' });MotionDB API (asenkron, tablo tabanlı)
İkinci katman: tek JSON dosyasında tablolar, await'li ve dot-notation
destekli arayüz. Discord botlarında kullanıcı/sunucu verisi için ideal:
import { MotionDB } from '@motiontr/motiondb';
const db = new MotionDB({ filePath: './data.json' });
await db.init(); // dosyayı yükler/oluşturur
// ---- okuma / yazma ----
await db.set('myKey', 'myValue'); // → 'myValue'
await db.get('myKey'); // → 'myValue'
await db.get('olmayan'); // → null
await db.has('myKey'); // → true
await db.delete('myKey'); // → 1 (yoksa 0)
await db.ensure('ayarlar', { modLog: '918273645091827364' }); // yoksa oluşturur
// ---- dot notation: iç içe nesneler ----
await db.set('myUser.balance', 700);
await db.get('myUser.balance'); // → 700
await db.get('myUser'); // → { balance: 700 }
// ---- sayılar ----
await db.add('myUser.balance', 100); // → 800
await db.sub('myUser.balance', 50); // → 750
await db.increment('sayac', 1); // → add kısayolu
await db.decrement('sayac', 1); // → sub kısayolu
// ---- diziler ----
await db.set('myUser.items', ['Sword', 'Shield']);
await db.push('myUser.items', 'Armor'); // → [..., 'Armor']
await db.unshift('myUser.items', 'Helm'); // → başa ekler
await db.pull('myUser.items', 'Shield'); // → siler
await db.pull('myUser.items', (i) => i.length < 5, true); // once: ilk eşleşen
await db.pop('myUser.items'); // → son eleman
await db.shift('myUser.items'); // → ilk eleman
// ---- sorgular ----
await db.all(); // → [{ id, value }, ...]
await db.startsWith('userInfo_'); // → [{ id, value }, ...]
await db.startsWith('x', 'bazıSatırListesi'); // → ikinci parametre anahtar
await db.count(); // → satır sayısı
// ---- tablolar (aynı dosyada ayrı bölüm) ----
const guilds = db.table('guilds');
await guilds.set('1552239063984246784.modLog', '918273645091827364');
await db.deleteAll(); // → silinen satır sayısı
db.useNormalKeys(true); // nokta literal anahtar yapsınSeçenekler
new MotionDB({
filePath: './data.json', // dosya yolu (varsayılan './data.json')
table: 'json', // kök bölüm adı (varsayılan 'json')
normalKeys: false, // true ise nokta ayracı değil, literal anahtar
deferWrites: false, // true ise diske yazmayı erteler → flush() gerekir
spaces: 2, // JSON girintisi
onCorrupt: 'throw', // 'throw' | 'reset'
});Metot özeti
| Kategori | Metot | Dönüş |
| --- | --- | --- |
| Okuma | get(key) | değer veya null |
| | has(key) | boolean |
| | all() | { id, value }[] |
| | startsWith(query, key?) | { id, value }[] |
| | count() | number |
| Yazma | set(key, value) | yazılan değer |
| | ensure(key, varsayılan) | değer |
| | delete(key) | 1 / 0 |
| | deleteAll() | silinen satır sayısı |
| Sayı | add, sub, increment, decrement | yeni sayı |
| Dizi | push, unshift, pop, shift, pull | dizi / eleman |
| Tablo | table(name) | yeni MotionDB |
| | createSingleton(options) | tek instance |
| | useNormalKeys(bool) | void |
| Kalıcılık | init(), flush(), flushSync() | — |
| Olay | on('save' \| 'load' \| 'change' \| 'error', fn) | this |
Hata mesajları (geçersiz kullanımda):
First argument (key) needs to be a stringMissing second argument (value)(set(key, null)gibi)Current value with key: (x) is not an array(pushile dizi olmayana)Current value with key: (x) is not a number...(addile sayı olmayana)
Dosya biçimi
{
"json": { "myKey": "myValue", "myUser": { "balance": 750 } },
"guilds": { "1552239063984246784": { "modLog": "918273645091827364" } }
}Aynı dosyayı paylaşan tüm
MotionDBinstance'ları (table()dahil) tek motor kullanır; birbirinin yazdığını silmezler.deferWrites: trueile her işlemde diske yazmayı erteleyip toplu işlerdeawait db.flush()çağırabilirsiniz.
Hızlı başlangıç
const db = new JSONDatabase({ path: './data/db.json' });
db.registerShutdownHooks(); // kapanışta bekleyen yazışları diske atar
// oku / yaz (dot-path)
db.set('guilds.123456789012345678.modLog', '918273645091827364');
db.get('guilds.123456789012345678.modLog'); // '918273645091827364'
db.get('guilds.olmayan.modLog', 'yok'); // 'yok' (fallback)
// yoksa oluştur
db.ensure('users.555555555555555555', { coins: 0, inventory: [] });
// sayaç
db.increment('users.555555555555555555.coins', 250);
// dizi
db.push('guilds.123.roles.moderator', '111111111111111111');
db.pull('guilds.123.roles.moderator', '111111111111111111');
// sil / kontrol
db.has('users.555555555555555555'); // true
db.delete('users.555555555555555555.oldKey');Seçenekler
new JSONDatabase({
path: './data/bot.json', // dosya yolu (varsayılan './db.json')
spaces: 2, // girinti sayısı (varsayılan 2)
debounce: 50, // değişiklikten sonra yazma gecikmesi ms (varsayılan 50)
autosave: true, // false ise yalnızca save()/flushSync() yazar
createIfMissing: true, // dosya yoksa oluştur
onCorrupt: 'throw', // 'throw' | 'reset' (bozuk dosyayı yedekleyip sıfırlar)
backupOnLoad: false, // ilk yüklemde otomatik yedek al
backupDir: './data/backups',
});
debounce: 0yaparsanız her değişiklikte anında yazar. Varsayılan50ms, bir komut içindeki 50 yazımı tek dosya işlemine indirger.
API
Okuma
| Metod | Açıklama |
| --- | --- |
| get(path, fallback?) | Değeri okur, yoksa fallback döner |
| has(path) | Yol var mı? |
| all() | Kök nesnenin canlı referansı |
| toJSON() | Kök nesnenin kopyası |
| keys() | Kök kategoriler (['guilds', 'users', ...]) |
| category(name, fallback?) | Bir kategorinin tamamı (db.category('guilds')) |
Yazma
| Metod | Açıklama |
| --- | --- |
| set(path, value) | Değer yazar |
| ensure(path, defaultValue) | Yoksa oluşturur, varsa mevcut değeri döner |
| delete(path) | Anahtarı siler, silindiyse true |
| push(path, ...values) | Diziye ekler (dizi yoksa oluşturur) |
| pull(path, value \| fn) | Diziden eşleşeni çıkarır (derin karşılaştırma) |
| increment(path, amount?) | Sayısal değeri artırır, yeni değeri döner |
| decrement(path, amount?) | Sayısal değeri azaltır |
| edit(fn, path?) | Ham düzenleme — verinin tamamına erişip değiştirin |
| clear() | Tüm veriyi siler |
Path sözdizimi
db.get('guilds.123.modLog'); // nokta ile
db.get('items[0].name'); // dizi indeksi
db.get('users["a.b"].coins'); // nokta içeren anahtar
db.get(['users', 'a.b', 'coins']); // segment dizisi (en hızlısı)Not: Discord snowflake ID'leri
Number'a çevrilmez (yok olurlardı), tüm segmentler string olarak tutulur.
Kalıcılık
| Metod | Açıklama |
| --- | --- |
| save() / flush() | Değişiklikleri atomik olarak yazar (Promise) |
| flushSync() | Anında senkron yazar — çıkış öncesi çağırın |
| load(force?) | Dosyayı okur (ilk çağrıda otomatik) |
| reload() | Bellek değişimlerini atıp dosyayı yeniden okur |
| registerShutdownHooks() | SIGINT/SIGTERM/exit → flushSync() |
Yedekleme
const file = db.backup('before-migration'); // path döner
db.listBackups(); // yeniden eskiye tüm yedeklerOlaylar
db.on('change', (c) => console.log(c.type, c.path.join('.'))); // set | delete | clear | edit
db.on('save', (file) => console.log('yazıldı:', file));
db.on('load', (file) => console.log('yüklendi:', file));
db.on('corrupt', (backupPath) => console.log('bozuk dosya yedeklendi:', backupPath));
db.on('error', (err) => console.error(err)); // yazma hatalarıdiscord.js örneği
import { Client, GatewayIntentBits, Events } from 'discord.js';
import { JSONDatabase } from '@motiontr/motiondb';
const db = new JSONDatabase({ path: './data/db.json', backupOnLoad: true });
db.registerShutdownHooks();
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent],
});
const prefix = '!'; // ön ek config.js'te durur, veritabanında değil
client.on(Events.MessageCreate, (message) => {
if (message.author.bot || !message.guild) return;
if (!message.content.startsWith(prefix)) return;
const [cmd, ...args] = message.content.slice(prefix.length).trim().split(/\s+/);
// !modlog #kanal — değişen sunucu ayarları veritabanında tutulur
if (cmd === 'modlog') {
const channel = message.mentions.channels.first();
if (!channel) return void message.reply('Bir kanal etiketle: !modlog #kanal');
db.set(`guilds.${message.guild.id}.modLog`, channel.id);
message.reply(`Mod log kanalı ayarlandı: ${channel}`);
}
});Sınırlar ve iyi pratikler
- Tek süreç içindir. Aynı dosyayı 2+ process (ör. shard cluster) aynı anda
yazarsa son yazan kazanır. Sharding yapıyorsanız process başına ayrı dosya
(
data/shard-0.json) veya harici bir DB (SQLite/Redis) kullanın. - Orta ölçek için uygundur: birkaç MB'a kadar veri rahatça çalışır;
- tüm veri bellekte tutulur, okuma O(1)'dir, yazma ise atomiktir
(tmp dosya +
rename, yarım kayıt olmaz). - Büyük veritabanlarda (on binlerce sunucu + yüksek write I/O) JSON yerine SQLite geçmeyi düşünün.
- Process
SIGKILLile öldürülürse sondebouncepenceresindeki değişiklikler kaybolabilir; kritik işlemlerden sonraawait db.flush()çağırın.
Proje yapısı
src/
index.ts dışa aktarımlar (MotionDB, JSONDatabase)
MotionDB.ts asenkron tablo API (get/set/push/pull/table/all/...)
Database.ts JSONDatabase çekirdeği (atomik yazma, yedek, olaylar)
path.ts dot-path çözümleyici ve okuma/yazma yardımcıları
examples/
basic.js çekirdek database özellikleri turu
api.js MotionDB API turu
tests/
db.test.js `npm test` — 23 test (çekirdek)
motiondb.test.js `npm test` — 26 test (API)