milodb
Version:
Fast encrypted JSON database with AI-friendly API
188 lines (187 loc) • 7.31 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.MiloDB = void 0;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const encrypt_1 = require("./encrypt");
class MiloDB {
constructor(dbPath = path.join(process.cwd(), 'db'), encryptionKey) {
this.indexCache = new Map();
this.dbPath = dbPath;
if (encryptionKey)
this.encryptor = new encrypt_1.Encryptor(encryptionKey);
fs.ensureDirSync(this.dbPath);
}
async createTable(tableName) {
const tablePath = this.getTablePath(tableName);
if (await fs.pathExists(tablePath))
throw new Error(`Table ${tableName} exists`);
await fs.writeJson(tablePath, []);
}
async insert(tableName, record) {
const records = await this.readTable(tableName);
records.push(record);
await this.writeTable(tableName, records);
this.invalidateIndex(tableName);
return record;
}
async insertMany(tableName, records) {
const existing = await this.readTable(tableName);
existing.push(...records);
await this.writeTable(tableName, existing);
this.invalidateIndex(tableName);
return { success: records.length, failed: 0, errors: [] };
}
async findById(tableName, id) {
const index = await this.getIndex(tableName);
const position = index.get(id);
if (position === undefined)
return null;
const records = await this.readTable(tableName);
return records[position] || null;
}
async find(tableName, query = {}, options = {}) {
let records = await this.readTable(tableName);
// Filter
if (Object.keys(query).length > 0) {
records = records.filter(rec => Object.entries(query).every(([k, v]) => rec[k] === v));
}
// Sort
if (options.sort) {
records.sort((a, b) => {
const aVal = a[options.sort];
const bVal = b[options.sort];
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
return options.order === 'desc' ? -cmp : cmp;
});
}
// Paginate
const start = options.offset || 0;
const end = options.limit ? start + options.limit : undefined;
return records.slice(start, end);
}
async update(tableName, id, updates) {
const records = await this.readTable(tableName);
const idx = records.findIndex(r => r.id === id);
if (idx === -1)
return null;
records[idx] = { ...records[idx], ...updates, id };
await this.writeTable(tableName, records);
return records[idx];
}
async updateMany(tableName, query, updates) {
const records = await this.readTable(tableName);
let count = 0;
for (let i = 0; i < records.length; i++) {
if (Object.entries(query).every(([k, v]) => records[i][k] === v)) {
records[i] = { ...records[i], ...updates };
count++;
}
}
if (count > 0)
await this.writeTable(tableName, records);
return count;
}
async delete(tableName, id) {
const records = await this.readTable(tableName);
const filtered = records.filter(r => r.id !== id);
if (filtered.length === records.length)
return false;
await this.writeTable(tableName, filtered);
this.invalidateIndex(tableName);
return true;
}
async deleteMany(tableName, query) {
const records = await this.readTable(tableName);
const filtered = records.filter(rec => !Object.entries(query).every(([k, v]) => rec[k] === v));
const count = records.length - filtered.length;
if (count > 0) {
await this.writeTable(tableName, filtered);
this.invalidateIndex(tableName);
}
return count;
}
async count(tableName, query = {}) {
const records = await this.readTable(tableName);
if (Object.keys(query).length === 0)
return records.length;
return records.filter(rec => Object.entries(query).every(([k, v]) => rec[k] === v)).length;
}
async dropTable(tableName) {
await fs.remove(this.getTablePath(tableName));
this.invalidateIndex(tableName);
}
async listTables() {
const files = await fs.readdir(this.dbPath);
return files.filter(f => f.endsWith('.json')).map(f => f.replace('.json', ''));
}
async readTable(tableName) {
const tablePath = this.getTablePath(tableName);
if (!await fs.pathExists(tablePath))
throw new Error(`Table ${tableName} not found`);
const data = await fs.readFile(tablePath, 'utf8');
if (!data.trim())
return [];
const parsed = JSON.parse(data);
if (!this.encryptor)
return parsed;
return parsed.map((item) => JSON.parse(this.encryptor.decrypt(item)));
}
async writeTable(tableName, records) {
const tablePath = this.getTablePath(tableName);
const data = this.encryptor
? records.map(r => this.encryptor.encrypt(JSON.stringify(r)))
: records;
await fs.writeJson(tablePath, data);
}
async getIndex(tableName) {
if (this.indexCache.has(tableName))
return this.indexCache.get(tableName);
const records = await this.readTable(tableName);
const index = new Map();
records.forEach((rec, idx) => index.set(rec.id, idx));
this.indexCache.set(tableName, index);
return index;
}
invalidateIndex(tableName) {
this.indexCache.delete(tableName);
}
getTablePath(tableName) {
return path.join(this.dbPath, `${tableName}.json`);
}
}
exports.MiloDB = MiloDB;
exports.default = MiloDB;