sqlmongoose
Version:
Mongoose-like schemas and models for SQLite3
199 lines (198 loc) • 7.44 kB
JavaScript
export class Model {
constructor(db, tableName, schema) {
this.db = db;
this.tableName = tableName;
this.schema = schema;
this.initialize();
}
initialize() {
const createTableQuery = this.schema.createTable(this.tableName);
this.db.run(createTableQuery.join(';'));
}
async find(query = {}, options = {}) {
let conditions = [];
let values = [];
if (this.isSimpleQuery(query)) {
Object.entries(query).forEach(([key, value]) => {
conditions.push(`${key} = ?`);
values.push(value);
});
}
else {
Object.entries(query).forEach(([field, operators]) => {
Object.entries(operators).forEach(([op, value]) => {
switch (op) {
case 'eq':
conditions.push(`${field} = ?`);
values.push(value);
break;
case 'ne':
conditions.push(`${field} != ?`);
values.push(value);
break;
case 'gt':
conditions.push(`${field} > ?`);
values.push(value);
break;
case 'gte':
conditions.push(`${field} >= ?`);
values.push(value);
break;
case 'lt':
conditions.push(`${field} < ?`);
values.push(value);
break;
case 'lte':
conditions.push(`${field} <= ?`);
values.push(value);
break;
case 'like':
conditions.push(`${field} LIKE ?`);
values.push(`%${value}%`);
break;
case 'in':
conditions.push(`${field} IN (${value.map(() => '?').join(',')})`);
values.push(...value);
break;
}
});
});
}
let sql = conditions.length
? `SELECT * FROM ${this.tableName} WHERE ${conditions.join(' AND ')}`
: `SELECT * FROM ${this.tableName}`;
if (options.orderBy) {
const orderClauses = Object.entries(options.orderBy)
.map(([field, order]) => `${field} ${order}`)
.join(', ');
sql += ` ORDER BY ${orderClauses}`;
}
if (options.limit) {
sql += ` LIMIT ${options.limit}`;
if (options.offset) {
sql += ` OFFSET ${options.offset}`;
}
}
const results = await this.query(sql, values);
if (options.populate) {
return this.populateRelationships(results, options.populate);
}
return results;
}
async findOne(query) {
const result = await this.find(query);
return result[0] || null;
}
async create(data) {
await this.schema.runValidation(data);
for (const hook of this.schema['hooks'].preSave) {
await hook(data);
}
const keys = Object.keys(data);
const values = Object.values(data);
const placeholders = new Array(keys.length).fill('?').join(',');
const sql = `INSERT INTO ${this.tableName} (${keys.join(',')}) VALUES (${placeholders})`;
return new Promise((resolve, reject) => {
this.db.run(sql, values, function (err) {
if (err) {
reject(err);
}
else {
const createdData = { ...data, id: this.lastID };
// Execute post-save hooks
Promise.all(this.schema['hooks'].postSave.map((hook) => hook(createdData)))
.then(() => resolve(createdData))
.catch((error) => reject(error));
}
});
});
}
async update(query, update) {
for (const hook of this.schema['hooks'].preUpdate) {
await hook(update);
}
let setClause = [];
let values = [];
if (this.isUpdateOperator(update)) {
if (update.$inc) {
Object.entries(update.$inc).forEach(([key, value]) => {
setClause.push(`${key} = ${key} + ?`);
values.push(value);
});
}
if (update.$set) {
Object.entries(update.$set).forEach(([key, value]) => {
setClause.push(`${key} = ?`);
values.push(value);
});
}
if (update.$unset) {
Object.entries(update.$unset).forEach(([key]) => {
setClause.push(`${key} = NULL`);
});
}
}
else {
setClause = Object.keys(update).map(key => `${key} = ?`);
values = Object.values(update);
}
const whereClause = Object.keys(query)
.map(key => `${key} = ?`)
.join(' AND ');
values.push(...Object.values(query));
const sql = `UPDATE ${this.tableName} SET ${setClause.join(', ')} WHERE ${whereClause}`;
return new Promise((resolve, reject) => {
this.db.run(sql, values, function (err) {
if (err)
reject(err);
resolve(this.changes);
});
});
}
isUpdateOperator(update) {
return update.$inc !== undefined ||
update.$set !== undefined ||
update.$unset !== undefined;
}
async delete(query) {
const whereClause = Object.keys(query)
.map(key => `${key} = ?`)
.join(' AND ');
const values = Object.values(query);
const sql = `DELETE FROM ${this.tableName} WHERE ${whereClause}`;
return new Promise((resolve, reject) => {
this.db.run(sql, values, function (err) {
if (err)
reject(err);
resolve(this.changes);
});
});
}
async populateRelationships(results, fields) {
const relationships = this.schema.getRelationships();
for (const result of results) {
for (const field of fields) {
if (relationships[field]) {
const refModel = relationships[field];
const foreignKey = result[field];
if (foreignKey) {
result[field] = await this.db.get(`SELECT * FROM ${refModel} WHERE id = ?`, [foreignKey]);
}
}
}
}
return results;
}
isSimpleQuery(query) {
return !Object.values(query).some(v => typeof v === 'object');
}
query(sql, params) {
return new Promise((resolve, reject) => {
this.db.all(sql, params, (err, rows) => {
if (err)
reject(err);
resolve(rows);
});
});
}
}