zumito-db
Version:
Multi-driver database abstraction layer with decorator-based models
160 lines (159 loc) • 5.86 kB
JavaScript
export class MemoryDriver {
collections = new Map();
sequences = new Map();
raw = this;
async connect(_config) { }
async disconnect() {
this.collections.clear();
this.sequences.clear();
}
async find(collection, query) {
const docs = this.collections.get(collection) || [];
let results = this.applyWhere(docs, query.where);
if (query.sort && query.sort.length > 0) {
results = this.applySort(results, query.sort);
}
if (query.offset !== undefined && query.offset > 0) {
results = results.slice(query.offset);
}
if (query.limit !== undefined && query.limit > 0) {
results = results.slice(0, query.limit);
}
if (query.select && query.select.length > 0) {
results = results.map(doc => this.applySelect(doc, query.select));
}
return results;
}
async findOne(collection, query) {
const results = await this.find(collection, { ...query, limit: 1 });
return results.length > 0 ? results[0] : null;
}
async insert(collection, data) {
if (!this.collections.has(collection)) {
this.collections.set(collection, []);
}
const seq = this.sequences.get(collection) || 0;
const nextId = seq + 1;
this.sequences.set(collection, nextId);
const doc = { _id: nextId, ...data };
this.collections.get(collection).push(doc);
return { ...doc };
}
async update(collection, query, data) {
const docs = this.collections.get(collection) || [];
const matches = this.findMatchingIndices(docs, query.where);
for (const idx of matches) {
Object.assign(docs[idx], data);
}
return matches.length;
}
async delete(collection, query) {
const docs = this.collections.get(collection) || [];
const matches = this.findMatchingIndices(docs, query.where);
// Remove in reverse order to keep indices valid
for (let i = matches.length - 1; i >= 0; i--) {
docs.splice(matches[i], 1);
}
return matches.length;
}
async count(collection, query) {
const results = await this.find(collection, query);
return results.length;
}
async ensureSchema(_metadata) {
if (!this.collections.has(_metadata.collection)) {
this.collections.set(_metadata.collection, []);
}
// Get next sequence from existing docs
const docs = this.collections.get(_metadata.collection);
if (docs.length > 0) {
const maxId = Math.max(...docs.map(d => d._id));
this.sequences.set(_metadata.collection, maxId);
}
}
async dropCollection(name) {
this.collections.delete(name);
this.sequences.delete(name);
}
async listCollections() {
return Array.from(this.collections.keys());
}
applyWhere(docs, where) {
if (!where || where.length === 0)
return [...docs];
return docs.filter(doc => {
if (where.length === 0)
return true;
// Build result starting with first clause
let result = this.evaluateClause(doc, where[0]);
// Apply subsequent clauses: logic[i] tells how clause[i] connects to accumulated result
for (let i = 1; i < where.length; i++) {
const matches = this.evaluateClause(doc, where[i]);
if (where[i].logic === 'or') {
result = result || matches;
}
else {
result = result && matches;
}
}
return result;
});
}
evaluateClause(doc, clause) {
const value = doc[clause.field];
switch (clause.operator) {
case 'eq': return value === clause.value;
case 'neq': return value !== clause.value;
case 'gt': return value > clause.value;
case 'gte': return value >= clause.value;
case 'lt': return value < clause.value;
case 'lte': return value <= clause.value;
case 'in': return Array.isArray(clause.value) && clause.value.includes(value);
case 'nin': return Array.isArray(clause.value) && !clause.value.includes(value);
case 'like': return typeof value === 'string' && value.includes(String(clause.value));
case 'between':
return Array.isArray(clause.value) &&
clause.value.length === 2 &&
value >= clause.value[0] &&
value <= clause.value[1];
default: return false;
}
}
findMatchingIndices(docs, where) {
const indices = [];
for (let i = 0; i < docs.length; i++) {
let matches = true;
for (const clause of where) {
if (!this.evaluateClause(docs[i], clause)) {
matches = false;
break;
}
}
if (matches)
indices.push(i);
}
return indices;
}
applySort(docs, sort) {
return [...docs].sort((a, b) => {
for (const s of sort) {
const aVal = a[s.field];
const bVal = b[s.field];
if (aVal < bVal)
return s.dir === 'asc' ? -1 : 1;
if (aVal > bVal)
return s.dir === 'asc' ? 1 : -1;
}
return 0;
});
}
applySelect(doc, fields) {
const result = { _id: doc._id };
for (const field of fields) {
if (field in doc) {
result[field] = doc[field];
}
}
return result;
}
}