UNPKG

zumito-db

Version:

Multi-driver database abstraction layer with decorator-based models

160 lines (159 loc) 5.86 kB
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; } }