diff --git a/backends/database.js b/backends/database.js index edcb5dd0..37741ef5 100644 --- a/backends/database.js +++ b/backends/database.js @@ -1,18 +1,23 @@ const frappe = require('frappejs'); const Observable = require('frappejs/utils/observable'); +const Knex = require('knex'); module.exports = class Database extends Observable { constructor() { super(); this.initTypeMap(); + this.connectionParams = {}; } - async connect() { - // this.conn + connect() { + this.knex = Knex(this.connectionParams); + this.knex.on('query-error', error => { + error.type = this.getError(error); + }); } close() { - this.conn.close(); + // } async migrate() { @@ -31,30 +36,21 @@ module.exports = class Database extends Observable { await this.commit(); } - async createTable(doctype, newName = null) { - let meta = frappe.getMeta(doctype); - let columns = []; - let indexes = []; + tableExists(table) { + return this.knex.schema.hasTable(table); + } - for (let field of meta.getValidFields({ withChildren: false })) { - if (this.typeMap[field.fieldtype]) { - this.updateColumnDefinition(field, columns, indexes); + async createTable(doctype, tableName = null) { + let fields = this.getValidFields(doctype); + return await this.runCreateTableQuery(tableName || doctype, fields); + } + + runCreateTableQuery(doctype, fields) { + return this.knex.schema.createTable(doctype, table => { + for (let field of fields) { + this.buildColumnForTable(table, field); } - } - - return await this.runCreateTableQuery(newName || doctype, columns, indexes); - } - - async tableExists(table) { - // return true if table exists - } - - async runCreateTableQuery(doctype, columns, indexes) { - // override - } - - updateColumnDefinition(field, columns, indexes) { - // return `${df.fieldname} ${this.typeMap[df.fieldtype]} ${ ? "PRIMARY KEY" : ""} ${df.required && !df.default ? "NOT NULL" : ""} ${df.default ? `DEFAULT ${df.default}` : ""}` + }); } async alterTable(doctype) { @@ -62,26 +58,66 @@ module.exports = class Database extends Observable { let diff = await this.getColumnDiff(doctype); let newForeignKeys = await this.getNewForeignKeys(doctype); - if (diff.added.length) { - await this.addColumns(doctype, diff.added); + return this.knex.schema + .table(doctype, table => { + if (diff.added.length) { + for (let field of diff.added) { + this.buildColumnForTable(table, field); + } + } + + if (diff.removed.length) { + this.removeColumns(doctype, diff.removed); + } + }) + .then(() => { + if (newForeignKeys.length) { + return this.addForeignKeys(doctype, newForeignKeys); + } + }); + } + + buildColumnForTable(table, field) { + let columnType = this.getColumnType(field); + let column = table[columnType](field.fieldname); + + // primary key + if (field.fieldname === 'name') { + column.primary(); } - if (diff.removed.length) { - await this.removeColumns(doctype, diff.removed); + // default value + if (field.default) { + column.defaultTo(field.default); } - if (newForeignKeys.length) { - await this.addForeignKeys(doctype, newForeignKeys); + // required + if (field.required) { + column.notNullable(); + } + + // link + if (field.fieldtype === 'Link' && field.target && column.foreign) { + let meta = frappe.getMeta(field.target); + column + .foreign(field.fieldname) + .references('name') + .inTable(meta.getBaseDocType()) + .onUpdate('CASCADE') + .onDelete('RESTRICT'); } } async getColumnDiff(doctype) { const tableColumns = await this.getTableColumns(doctype); - const validFields = frappe.getMeta(doctype).getValidFields({ withChildren: false }); + const validFields = this.getValidFields(doctype); const diff = { added: [], removed: [] }; for (let field of validFields) { - if (!tableColumns.includes(field.fieldname) && this.typeMap[field.fieldtype]) { + if ( + !tableColumns.includes(field.fieldname) && + this.getColumnType(field) + ) { diff.added.push(field); } } @@ -96,25 +132,21 @@ module.exports = class Database extends Observable { return diff; } - async addColumns(doctype, added) { - for (let field of added) { - await this.runAddColumnQuery(doctype, field); - } - } - async removeColumns(doctype, removed) { for (let column of removed) { await this.runRemoveColumnQuery(doctype, column); } } - async getNewForeignKeys(doctype) { let foreignKeys = await this.getForeignKeys(doctype); let newForeignKeys = []; let meta = frappe.getMeta(doctype); for (let field of meta.getValidFields({ withChildren: false })) { - if (field.fieldtype === 'Link' && !foreignKeys.includes(field.fieldname)) { + if ( + field.fieldtype === 'Link' && + !foreignKeys.includes(field.fieldname) + ) { newForeignKeys.push(field); } } @@ -127,18 +159,14 @@ module.exports = class Database extends Observable { } } - async getForeignKey(doctype, field) { - + async getForeignKeys(doctype, field) { + return [] } async getTableColumns(doctype) { return []; } - async runAddColumnQuery(doctype, field) { - // alter table {doctype} add column ({column_def}); - } - async get(doctype, name = null, fields = '*') { let meta = frappe.getMeta(doctype); let doc; @@ -161,7 +189,7 @@ module.exports = class Database extends Observable { for (let field of tableFields) { doc[field.fieldname] = await this.getAll({ doctype: field.childtype, - fields: ["*"], + fields: ['*'], filters: { parent: doc.name }, orderBy: 'idx', order: 'asc' @@ -184,15 +212,15 @@ module.exports = class Database extends Observable { return doc; } - async getOne(doctype, name, fields = '*') { - // select {fields} form {doctype} where name = ? - } + getOne(doctype, name, fields = '*') { + let meta = frappe.getMeta(doctype); + let baseDoctype = meta.getBaseDocType(); - prepareFields(fields) { - if (fields instanceof Array) { - fields = fields.join(", "); - } - return fields; + return this.knex + .select(fields) + .from(baseDoctype) + .where('name', name) + .first(); } triggerChange(doctype, name) { @@ -225,12 +253,11 @@ module.exports = class Database extends Observable { return doc; } - async insertChildren(meta, doc, doctype) { let tableFields = meta.getTableFields(); for (let field of tableFields) { let idx = 0; - for (let child of (doc[field.fieldname] || [])) { + for (let child of doc[field.fieldname] || []) { this.prepareChild(doctype, doc.name, child, field, idx); await this.insertOne(field.childtype, child); idx++; @@ -238,8 +265,15 @@ module.exports = class Database extends Observable { } } - async insertOne(doctype, doc) { - // insert into {doctype} ({fields}) values ({values}) + insertOne(doctype, doc) { + let fields = this.getValidFields(doctype); + + if (!doc.name) { + doc.name = frappe.getRandomString(); + } + + let formattedDoc = this.getFormattedDoc(fields, doc); + return this.knex(doctype).insert(formattedDoc); } async update(doctype, doc) { @@ -265,28 +299,37 @@ module.exports = class Database extends Observable { async updateChildren(meta, doc, doctype) { let tableFields = meta.getTableFields(); for (let field of tableFields) { - // first key is "parent" - for SQL params - let added = [doc.name]; - for (let child of (doc[field.fieldname] || [])) { - this.prepareChild(doctype, doc.name, child, field, added.length - 1); + let added = []; + for (let child of doc[field.fieldname] || []) { + this.prepareChild(doctype, doc.name, child, field, added.length); if (await this.exists(field.childtype, child.name)) { await this.updateOne(field.childtype, child); - } - else { + } else { await this.insertOne(field.childtype, child); } added.push(child.name); } - await this.runDeleteOtherChildren(field, added); + await this.runDeleteOtherChildren(field, doc.name, added); } } - async updateOne(doctype, doc) { - // update {doctype} set {field=value} where name=? + updateOne(doctype, doc) { + let validFields = this.getValidFields(doctype); + let fieldsToUpdate = Object.keys(doc).filter(f => f !== 'name'); + let fields = validFields.filter(df => fieldsToUpdate.includes(df.fieldname)); + let formattedDoc = this.getFormattedDoc(fields, doc); + + return this.knex(doctype) + .where('name', doc.name) + .update(formattedDoc); } - async runDeleteOtherChildren(field, added) { - // delete from doctype where parent = ? and name not in (?, ?, ?) + runDeleteOtherChildren(field, parent, added) { + // delete other children + return this.knex(field.childtype) + .where('parent', parent) + .andWhere('name', 'not in', added) + .delete(); } async updateSingle(meta, doc, doctype) { @@ -299,18 +342,25 @@ module.exports = class Database extends Observable { parent: doctype, fieldname: field.fieldname, value: value - }) + }); await singleValue.insert(); } } } - async deleteSingleValues(name) { - // await frappe.db.run('delete from SingleValue where parent=?', name) + deleteSingleValues(name) { + return this.knex('SingleValue') + .where('parent', name) + .delete(); } async rename(doctype, oldName, newName) { - // await frappe.db.run('update doctype set name = ? where name = ?', name) + let meta = frappe.getMeta(doctype); + let baseDoctype = meta.getBaseDocType(); + await this.knex(baseDoctype) + .update({ name: newName }) + .where('name', oldName); + await frappe.db.commit(); } prepareChild(parenttype, parent, child, field, idx) { @@ -323,16 +373,17 @@ module.exports = class Database extends Observable { child.idx = idx; } - getKeys(doctype) { + getValidFields(doctype) { return frappe.getMeta(doctype).getValidFields({ withChildren: false }); } - getFormattedValues(fields, doc) { - let values = fields.map(field => { + getFormattedDoc(fields, doc) { + let formattedDoc = {}; + fields.map(field => { let value = doc[field.fieldname]; - return this.getFormattedValue(field, value); + formattedDoc[field.fieldname] = this.getFormattedValue(field, value); }); - return values; + return formattedDoc; } getFormattedValue(field, value) { @@ -387,11 +438,15 @@ module.exports = class Database extends Observable { } async deleteOne(doctype, name) { - // delete from {doctype} where name = ? + return this.knex(doctype) + .where('name', name) + .delete(); } - async deleteChildren(parenttype, parent) { - // delete from {parenttype} where parent = ? + deleteChildren(parenttype, parent) { + return this.knex(parenttype) + .where('parent', parent) + .delete(); } async exists(doctype, name) { @@ -427,14 +482,57 @@ module.exports = class Database extends Observable { } async setValues(doctype, name, fieldValuePair) { - // + let doc = Object.assign({}, fieldValuePair, { name }); + return this.updateOne(doctype, doc); } - getAll({ doctype, fields, filters, start, limit, orderBy = 'modified', order = 'desc' } = {}) { - // select {fields} from {doctype} where {filters} order by {orderBy} {order} limit {start} {limit} + getAll({ + doctype, + fields, + filters, + start, + limit, + groupBy, + orderBy = 'creation', + order = 'desc' + } = {}) { + let meta = frappe.getMeta(doctype); + let baseDoctype = meta.getBaseDocType(); + if (!fields) { + fields = meta.getKeywordFields(); + fields.push('name'); + } + if (typeof fields === 'string') { + fields = [fields]; + } + if (meta.filters) { + filters = Object.assign({}, filters, meta.filters); + } + + let builder = this.knex.select(fields).from(baseDoctype); + + this.applyFiltersToBuilder(builder, filters); + + if (orderBy) { + builder.orderBy(orderBy, order); + } + + if (groupBy) { + builder.groupBy(groupBy); + } + + if (start) { + builder.offset(start); + } + + if (limit) { + builder.limit(limit); + } + + return builder; } - getFilterConditions(filters) { + applyFiltersToBuilder(builder, filters) { // {"status": "Open"} => `status = "Open"` // {"status": "Open", "name": ["like", "apple%"]} @@ -445,9 +543,8 @@ module.exports = class Database extends Observable { let filtersArray = []; - for (let key in filters) { - let value = filters[key]; - let field = key; + for (let field in filters) { + let value = filters[field]; let operator = '='; let comparisonValue = value; @@ -460,7 +557,7 @@ module.exports = class Database extends Observable { operator = 'like'; } - if (['like', 'includes'].includes(operator) && !comparisonValue.includes('%')) { + if (operator === 'like' && !comparisonValue.includes('%')) { comparisonValue = `%${comparisonValue}%`; } } @@ -475,74 +572,41 @@ module.exports = class Database extends Observable { } } - let conditions = filtersArray.map(filter => { + filtersArray.map(filter => { const [field, operator, comparisonValue] = filter; - - let placeholder = Array.isArray(comparisonValue) ? - comparisonValue.map(v => '?').join(', ') : - '?'; - - return `ifnull(${field}, '') ${operator} (${placeholder})`; + builder.where(field, operator, comparisonValue); }); - - let values = filtersArray.reduce((acc, filter) => { - const comparisonValue = filter[2]; - if (Array.isArray(comparisonValue)) { - acc = acc.concat(comparisonValue); - } else { - acc.push(comparisonValue); - } - return acc; - }, []); - - return { - conditions: conditions.length ? conditions.join(" and ") : "", - values - }; } - async run(query, params) { + run(query, params) { // run query + return this.sql(query, params); } - async sql(query, params) { + sql(query, params) { // run sql + return this.knex.raw(query, params); } async commit() { - // commit - } - - initTypeMap() { - this.typeMap = { - 'AutoComplete': 'text' - , 'Currency': 'real' - , 'Int': 'integer' - , 'Float': 'real' - , 'Percent': 'real' - , 'Check': 'integer' - , 'Small Text': 'text' - , 'Long Text': 'text' - , 'Code': 'text' - , 'Text Editor': 'text' - , 'Date': 'text' - , 'Datetime': 'text' - , 'Time': 'text' - , 'Text': 'text' - , 'Data': 'text' - , 'Link': 'text' - , 'DynamicLink': 'text' - , 'Password': 'text' - , 'Select': 'text' - , 'Read Only': 'text' - , 'File': 'text' - , 'Attach': 'text' - , 'AttachImage': 'text' - , 'Signature': 'text' - , 'Color': 'text' - , 'Barcode': 'text' - , 'Geolocation': 'text' + try { + await this.sql('commit'); + } catch (e) { + if (e.type !== frappe.errors.CannotCommitError) { + throw e; + } } } -} + getColumnType(field) { + return this.typeMap[field.fieldtype]; + } + + getError(err) { + return frappe.errors.DatabaseError; + } + + initTypeMap() { + this.typeMap = {}; + } +}; diff --git a/backends/sqlite.js b/backends/sqlite.js index 95b03e1f..a5363312 100644 --- a/backends/sqlite.js +++ b/backends/sqlite.js @@ -1,294 +1,92 @@ const frappe = require('frappejs'); -const sqlite3 = require('sqlite3').verbose(); const Database = require('./database'); -const errors = require('frappejs/common/errors'); -const debug = false; class SqliteDatabase extends Database { constructor({ dbPath }) { super(); this.dbPath = dbPath; - } - - connect(dbPath) { - if (dbPath) { - this.dbPath = dbPath; - } - return new Promise((resolve, reject) => { - this.conn = new sqlite3.Database(this.dbPath, (err) => { - if (err) { - console.log(err); - reject(err); - return; + this.connectionParams = { + client: 'sqlite3', + connection: { + filename: this.dbPath + }, + pool: { + afterCreate(conn, done) { + conn.run('PRAGMA foreign_keys=ON'); + done(); } - if (debug) { - this.conn.on('trace', (trace) => console.log(trace)); - } - this.run('PRAGMA foreign_keys=ON').then(resolve); - }); - }); - } - - async tableExists(table) { - const name = await this.sql(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`); - return (name && name.length) ? true : false; + }, + useNullAsDefault: true + }; } async addForeignKeys(doctype, newForeignKeys) { - await this.run('PRAGMA foreign_keys=OFF'); - await this.run('BEGIN TRANSACTION'); + await this.sql('PRAGMA foreign_keys=OFF'); + await this.sql('BEGIN TRANSACTION'); - const tempName = 'TEMP' + doctype + const tempName = 'TEMP' + doctype; // create temp table await this.createTable(doctype, tempName); - const columns = (await this.getTableColumns(tempName)).join(', '); - // copy from old to new table - await this.run(`INSERT INTO ${tempName} (${columns}) SELECT ${columns} from ${doctype}`); + await this.knex(tempName).insert(this.knex.select().from(doctype)); // drop old table - await this.run(`DROP TABLE ${doctype}`); + await this.knex.schema.dropTable(doctype); // rename new table - await this.run(`ALTER TABLE ${tempName} RENAME TO ${doctype}`); + await this.knex.schema.renameTable(tempName, doctype); - await this.run('COMMIT'); - await this.run('PRAGMA foreign_keys=ON'); + await this.sql('COMMIT'); + await this.sql('PRAGMA foreign_keys=ON'); } removeColumns() { // pass } - async runCreateTableQuery(doctype, columns, indexes) { - const query = `CREATE TABLE IF NOT EXISTS ${doctype} ( - ${columns.join(", ")} ${indexes.length ? (", " + indexes.join(", ")) : ''})`; - - return await this.run(query); - } - - updateColumnDefinition(field, columns, indexes) { - let def = this.getColumnDefinition(field); - - columns.push(def); - - if (field.fieldtype === 'Link' && field.target) { - let meta = frappe.getMeta(field.target); - indexes.push(`FOREIGN KEY (${field.fieldname}) REFERENCES ${meta.getBaseDocType()} ON UPDATE CASCADE ON DELETE RESTRICT`); - } - } - - getColumnDefinition(field) { - let defaultValue = field.default; - if (typeof defaultValue === 'string') { - defaultValue = `'${defaultValue}'` - } - let def = [ - field.fieldname, - this.typeMap[field.fieldtype], - field.fieldname === 'name' ? 'PRIMARY KEY NOT NULL' : '', - field.required ? 'NOT NULL' : '', - field.default ? `DEFAULT ${defaultValue}` : '' - ].join(' '); - - return def; - } - async getTableColumns(doctype) { return (await this.sql(`PRAGMA table_info(${doctype})`)).map(d => d.name); } async getForeignKeys(doctype) { - return (await this.sql(`PRAGMA foreign_key_list(${doctype})`)).map(d => d.from); - } - - async runAddColumnQuery(doctype, field, values) { - await this.run(`ALTER TABLE ${doctype} ADD COLUMN ${this.getColumnDefinition(field)}`, values); - } - - getOne(doctype, name, fields = '*') { - let meta = frappe.getMeta(doctype); - let baseDoctype = meta.getBaseDocType(); - fields = this.prepareFields(fields); - return new Promise((resolve, reject) => { - this.conn.get(`select ${fields} from ${baseDoctype} - where name = ?`, name, - (err, row) => { - resolve(row || {}); - }); - }); - } - - async insertOne(doctype, doc) { - let fields = this.getKeys(doctype); - let placeholders = fields.map(d => '?').join(', '); - - if (!doc.name) { - doc.name = frappe.getRandomString(); - } - - return await this.run(`insert into ${doctype} - (${fields.map(field => field.fieldname).join(", ")}) - values (${placeholders})`, this.getFormattedValues(fields, doc)); - } - - async updateOne(doctype, doc) { - let fields = this.getKeys(doctype); - let assigns = fields.map(field => `${field.fieldname} = ?`); - let values = this.getFormattedValues(fields, doc); - - // additional name for where clause - values.push(doc.name); - - return await this.run(`update ${doctype} - set ${assigns.join(", ")} where name=?`, values); - } - - async runDeleteOtherChildren(field, added) { - // delete other children - // `delete from doctype where parent = ? and name not in (?, ?, ?)}` - await this.run(`delete from ${field.childtype} - where - parent = ? and - name not in (${added.slice(1).map(d => '?').join(', ')})`, added); - } - - async deleteOne(doctype, name) { - return await this.run(`delete from ${doctype} where name=?`, name); - } - - async deleteChildren(parenttype, parent) { - await this.run(`delete from ${parenttype} where parent=?`, parent); - } - - async deleteSingleValues(name) { - await frappe.db.run('delete from SingleValue where parent=?', name) - } - - async rename(doctype, oldName, newName) { - let meta = frappe.getMeta(doctype); - let baseDoctype = meta.getBaseDocType(); - await frappe.db.run(`update ${baseDoctype} set name = ? where name = ?`, [newName, oldName]); - await frappe.db.commit(); - } - - async setValues(doctype, name, fieldValuePair) { - const meta = frappe.getMeta(doctype); - const baseDoctype = meta.getBaseDocType(); - const validFields = this.getKeys(doctype); - const validFieldnames = validFields.map(df => df.fieldname); - const fieldsToUpdate = Object.keys(fieldValuePair) - .filter(fieldname => validFieldnames.includes(fieldname)) - - // assignment part of query - const assigns = fieldsToUpdate.map(fieldname => `${fieldname} = ?`); - - // values - const values = fieldsToUpdate.map(fieldname => { - const field = meta.getField(fieldname); - const value = fieldValuePair[fieldname]; - return this.getFormattedValue(field, value); - }); - - // additional name for where clause - values.push(name); - - return await this.run(`update ${baseDoctype} - set ${assigns.join(', ')} where name=?`, values); - } - - getAll({ doctype, fields, filters, start, limit, orderBy = 'modified', groupBy, order = 'desc' } = {}) { - let meta = frappe.getMeta(doctype); - let baseDoctype = meta.getBaseDocType(); - if (!fields) { - fields = meta.getKeywordFields(); - } - if (typeof fields === 'string') { - fields = [fields]; - } - if (meta.filters) { - filters = Object.assign({}, filters, meta.filters); - } - - let conditions = this.getFilterConditions(filters); - let query = `select ${fields.join(", ")} - from ${baseDoctype} - ${conditions.conditions ? "where" : ""} ${conditions.conditions} - ${groupBy ? ("group by " + groupBy.join(', ')) : ""} - ${orderBy ? ("order by " + orderBy) : ""} ${orderBy ? (order || "asc") : ""} - ${limit ? ("limit " + limit) : ""} ${start ? ("offset " + start) : ""}`; - - return this.sql(query, conditions.values); - } - - run(query, params) { - return new Promise((resolve, reject) => { - this.conn.run(query, params, (err) => { - if (err) { - console.error('Error in sql:', query); - let Error = this.getError(err); - reject(new Error()); - } else { - resolve(); - } - }); - }); - } - - sql(query, params) { - return new Promise((resolve, reject) => { - this.conn.all(query, params, (err, rows) => { - if (err) { - console.error('Error in sql:', query); - reject(err) - } - resolve(rows); - }); - }); - } - - async commit() { - try { - await this.run('commit'); - } catch (e) { - if (e.name !== 'CannotCommitError') { - throw e; - } - } + return (await this.sql(`PRAGMA foreign_key_list(${doctype})`)).map( + d => d.from + ); } initTypeMap() { + // prettier-ignore this.typeMap = { - 'AutoComplete': 'text' - , 'Currency': 'real' - , 'Int': 'integer' - , 'Float': 'real' - , 'Percent': 'real' - , 'Check': 'integer' - , 'Small Text': 'text' - , 'Long Text': 'text' - , 'Code': 'text' - , 'Text Editor': 'text' - , 'Date': 'text' - , 'Datetime': 'text' - , 'Time': 'text' - , 'Text': 'text' - , 'Data': 'text' - , 'Link': 'text' - , 'DynamicLink': 'text' - , 'Password': 'text' - , 'Select': 'text' - , 'Read Only': 'text' - , 'File': 'text' - , 'Attach': 'text' - , 'AttachImage': 'text' - , 'Signature': 'text' - , 'Color': 'text' - , 'Barcode': 'text' - , 'Geolocation': 'text' - } + 'AutoComplete': 'text', + 'Currency': 'float', + 'Int': 'integer', + 'Float': 'float', + 'Percent': 'float', + 'Check': 'integer', + 'Small Text': 'text', + 'Long Text': 'text', + 'Code': 'text', + 'Text Editor': 'text', + 'Date': 'text', + 'Datetime': 'text', + 'Time': 'text', + 'Text': 'text', + 'Data': 'text', + 'Link': 'text', + 'DynamicLink': 'text', + 'Password': 'text', + 'Select': 'text', + 'Read Only': 'text', + 'File': 'text', + 'Attach': 'text', + 'AttachImage': 'text', + 'Signature': 'text', + 'Color': 'text', + 'Barcode': 'text', + 'Geolocation': 'text' + }; } getError(err) { @@ -296,18 +94,13 @@ class SqliteDatabase extends Database { return frappe.errors.LinkValidationError; } if (err.message.includes('SQLITE_ERROR: cannot commit')) { - return SqliteDatabase.CannotCommitError; + return frappe.errors.CannotCommitError; } - return { - 19: frappe.errors.DuplicateEntryError - }[err.errno] || Error; - } -} - -SqliteDatabase.CannotCommitError = class CannotCommitError extends errors.DatabaseError { - constructor(message) { - super(message); - this.name = 'CannotCommitError'; + return ( + { + 19: frappe.errors.DuplicateEntryError + }[err.errno] || Error + ); } } diff --git a/common/errors.js b/common/errors.js index d2e52fc0..713427e5 100644 --- a/common/errors.js +++ b/common/errors.js @@ -58,6 +58,13 @@ class DatabaseError extends BaseError { } } +class CannotCommitError extends DatabaseError { + constructor(message) { + super(message); + this.name = 'CannotCommitError'; + } +} + class ValueError extends ValidationError {} class Conflict extends ValidationError {} @@ -86,6 +93,7 @@ module.exports = { DuplicateEntryError, LinkValidationError, DatabaseError, + CannotCommitError, MandatoryError, throw: throwError }; diff --git a/model/document.js b/model/document.js index 8e4b3a44..d68d479f 100644 --- a/model/document.js +++ b/model/document.js @@ -231,7 +231,7 @@ module.exports = class BaseDocument extends Observable { async load() { let data = await frappe.db.get(this.doctype, this.name); - if (data.name) { + if (data && data.name) { this.syncValues(data); if (this.meta.isSingle) { this.setDefaults(); diff --git a/model/meta.js b/model/meta.js index 67cb7ae4..65edf0d8 100644 --- a/model/meta.js +++ b/model/meta.js @@ -42,8 +42,13 @@ module.exports = class BaseMeta extends BaseDocument { ].concat(this.fields); } - // attach default precision to Float and Currency this.fields = this.fields.map(df => { + // name field is always required + if (df.fieldname === 'name') { + df.required = 1; + } + + // attach default precision to Float and Currency if (['Float', 'Currency'].includes(df.fieldtype)) { let defaultPrecision = frappe.SystemSettings ? frappe.SystemSettings.floatPrecision : 2; df.precision = df.precision || defaultPrecision; diff --git a/package.json b/package.json index 0845525a..1f88b23f 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "file-loader": "^1.1.11", "friendly-errors-webpack-plugin": "^1.7.0", "html-webpack-plugin": "^3.2.0", + "knex": "^0.20.3", "luxon": "^1.0.0", "mkdirp": "^0.5.1", "morgan": "^1.9.0", @@ -44,7 +45,7 @@ "sass-loader": "^7.0.3", "showdown": "^1.8.6", "socket.io": "^2.0.4", - "sqlite3": "^4.0.9", + "sqlite3": "^4.1.1", "tailwindcss": "1.1.1", "vue": "^2.6.10", "vue-flatpickr-component": "^8.1.2", diff --git a/tests/helpers.js b/tests/helpers.js index a45d26cf..6cff1a11 100644 --- a/tests/helpers.js +++ b/tests/helpers.js @@ -1,15 +1,16 @@ -const server = require('frappejs/server'); const frappe = require('frappejs'); +const server = require('frappejs/server'); +const SQLite = require('frappejs/backends/sqlite'); module.exports = { - async initSqlite({dbPath = '_test.db', models} = {}) { - server.init(); - if (models) { - frappe.registerModels(models, 'server'); - } - await server.initDb({ - backend: 'sqlite', - connectionParams: {dbPath: dbPath}, - }); + async initSqlite({ dbPath = '_test.db', models } = {}) { + server.init(); + if (models) { + frappe.registerModels(models, 'server'); } -} \ No newline at end of file + + frappe.db = new SQLite({ dbPath }); + await frappe.db.connect(); + await frappe.db.migrate(); + } +}; diff --git a/tests/test_database.js b/tests/test_database.js index 251e7c18..8bd469aa 100644 --- a/tests/test_database.js +++ b/tests/test_database.js @@ -3,47 +3,90 @@ const frappe = require('frappejs'); const helpers = require('./helpers'); describe('Database', () => { - before(async function() { - await helpers.initSqlite(); + before(async function() { + await helpers.initSqlite(); + }); + + beforeEach(async () => { + await frappe.db.sql('delete from todo'); + + await frappe.insert({ + doctype: 'ToDo', + subject: 'testing 1', + status: 'Open' + }); + await frappe.insert({ + doctype: 'ToDo', + subject: 'testing 3', + status: 'Open' + }); + await frappe.insert({ + doctype: 'ToDo', + subject: 'testing 2', + status: 'Closed' + }); + }); + + it('should insert and get values', async () => { + let subjects = await frappe.db.getAll({ + doctype: 'ToDo', + fields: ['name', 'subject'] + }); + subjects = subjects.map(d => d.subject); + + assert.ok(subjects.includes('testing 1')); + assert.ok(subjects.includes('testing 2')); + assert.ok(subjects.includes('testing 3')); + }); + + it('should filter correct values', async () => { + let todos = await frappe.db.getAll({ + doctype: 'ToDo', + fields: ['name', 'subject'], + filters: { status: 'Open' } + }); + let subjects = todos.map(d => d.subject); + + assert.ok(subjects.includes('testing 1')); + assert.ok(subjects.includes('testing 3')); + assert.equal(subjects.includes('testing 2'), false); + + todos = await frappe.db.getAll({ + doctype: 'ToDo', + fields: ['name', 'subject'], + filters: { status: 'Closed' } + }); + subjects = todos.map(d => d.subject); + + assert.equal(subjects.includes('testing 1'), false); + assert.equal(subjects.includes('testing 3'), false); + assert.ok(subjects.includes('testing 2')); + }); + + it('should delete records', async () => { + let todos = await frappe.db.getAll({ doctype: 'ToDo' }); + frappe.db.delete('ToDo', todos[0].name); + + todos = await frappe.db.getAll({ doctype: 'ToDo' }); + assert.equal(todos.length, 2); + }); + + it('should update records', async () => { + let todo = (await frappe.db.getAll({ doctype: 'ToDo', limit: 1 }))[0]; + + frappe.db.update('ToDo', { + name: todo.name, + subject: 'updated subject' }); - it('should insert and get values', async () => { - await frappe.db.sql('delete from todo'); - await frappe.insert({doctype:'ToDo', subject: 'testing 1'}); - await frappe.insert({doctype:'ToDo', subject: 'testing 3'}); - await frappe.insert({doctype:'ToDo', subject: 'testing 2'}); + todo = ( + await frappe.db.getAll({ + doctype: 'ToDo', + fields: ['subject'], + filters: { name: todo.name } + }) + )[0]; - let subjects = await frappe.db.getAll({doctype:'ToDo', fields:['name', 'subject']}) - subjects = subjects.map(d => d.subject); - - assert.ok(subjects.includes('testing 1')); - assert.ok(subjects.includes('testing 2')); - assert.ok(subjects.includes('testing 3')); - }); - - it('should filter correct values', async () => { - let subjects = null; - - await frappe.db.sql('delete from todo'); - await frappe.insert({doctype:'ToDo', subject: 'testing 1', status: 'Open'}); - await frappe.insert({doctype:'ToDo', subject: 'testing 3', status: 'Open'}); - await frappe.insert({doctype:'ToDo', subject: 'testing 2', status: 'Closed'}); - - subjects = await frappe.db.getAll({doctype:'ToDo', fields:['name', 'subject'], - filters:{status: 'Open'}}); - subjects = subjects.map(d => d.subject); - - assert.ok(subjects.includes('testing 1')); - assert.ok(subjects.includes('testing 3')); - assert.equal(subjects.includes('testing 2'), false); - - subjects = await frappe.db.getAll({doctype:'ToDo', fields:['name', 'subject'], - filters:{status: 'Closed'}}); - subjects = subjects.map(d => d.subject); - - assert.equal(subjects.includes('testing 1'), false); - assert.equal(subjects.includes('testing 3'), false); - assert.ok(subjects.includes('testing 2')); - - }); -}); \ No newline at end of file + assert.equal(todo.subject, 'updated subject'); + }); +}); diff --git a/yarn.lock b/yarn.lock index 034b2c56..4c407619 100644 --- a/yarn.lock +++ b/yarn.lock @@ -442,6 +442,11 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= +array-each@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" + integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= + array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" @@ -457,6 +462,11 @@ array-flatten@^2.1.0: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== +array-slice@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" + integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== + array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -814,6 +824,11 @@ bluebird@^3.1.1, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== +bluebird@^3.7.1: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -1419,6 +1434,11 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +colorette@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.1.0.tgz#1f943e5a357fac10b4e0f5aaef3b14cdc1af6ec7" + integrity sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg== + combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -1441,6 +1461,11 @@ commander@^2.13.0, commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== +commander@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.0.1.tgz#b67622721785993182e807f4883633e6401ba53c" + integrity sha512-IPF4ouhCP+qdlcmCedhxX4xiGBPyigb8v5NeUp+0LyhwLgxMqyp3S0vl7TAPfS/hiP7FC3caI/PB9lTmP8r1NA== + commander@~2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" @@ -1865,6 +1890,13 @@ debug@3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" +debug@4.1.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + debug@^3.0.0, debug@^3.1.0, debug@^3.2.5, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -1872,13 +1904,6 @@ debug@^3.0.0, debug@^3.1.0, debug@^3.2.5, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1994,6 +2019,11 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +detect-file@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" + integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= + detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" @@ -2548,6 +2578,13 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" +expand-tilde@^2.0.0, expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= + dependencies: + homedir-polyfill "^1.0.1" + express@^4.16.2, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -2599,7 +2636,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -2758,6 +2795,32 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" +findup-sync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" + integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + +fined@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" + integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== + dependencies: + expand-tilde "^2.0.2" + is-plain-object "^2.0.3" + object.defaults "^1.1.0" + object.pick "^1.2.0" + parse-filepath "^1.0.1" + +flagged-respawn@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41" + integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== + flatpickr@^4.5.1: version "4.6.2" resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.2.tgz#50e1b4fc84fbf67c5b0919ba3ddc330221f126da" @@ -2949,6 +3012,11 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +getopts@2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.5.tgz#67a0fe471cacb9c687d817cab6450b96dde8313b" + integrity sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA== + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -2995,6 +3063,26 @@ global-dirs@^0.1.0: dependencies: ini "^1.3.4" +global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== + dependencies: + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" + +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= + dependencies: + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" + ini "^1.3.4" + is-windows "^1.0.1" + which "^1.2.14" + globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -3210,6 +3298,13 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" +homedir-polyfill@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" @@ -3466,7 +3561,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3494,6 +3589,11 @@ internal-ip@^4.3.0: default-gateway "^4.2.0" ipaddr.js "^1.9.0" +interpret@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.0.0.tgz#b783ffac0b8371503e9ab39561df223286aa5433" + integrity sha512-e0/LknJ8wpMMhTiWcjivB+ESwIuvHnBSlBbmP/pSb8CQJldoj1p2qv7xGZ/+BtbTziYRFSz8OsvdbiX45LtYQA== + invariant@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -3531,6 +3631,14 @@ ipaddr.js@^1.9.0: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -3748,6 +3856,13 @@ is-regex@^1.0.4: dependencies: has "^1.0.1" +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + is-retry-allowed@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" @@ -3770,12 +3885,19 @@ is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= -is-windows@^1.0.0, is-windows@^1.0.2: +is-windows@^1.0.0, is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== @@ -3984,6 +4106,27 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== +knex@^0.20.3: + version "0.20.3" + resolved "https://registry.yarnpkg.com/knex/-/knex-0.20.3.tgz#85178cd6873f75827be86d054c4e117bb4d9657b" + integrity sha512-zzYO34pSCCYVqRTbCp8xL+Z7fvHQl5anif3Oacu6JaHFDubB7mFGWRRJBNSO3N8Ql4g4CxUgBctaPiliwoOsNA== + dependencies: + bluebird "^3.7.1" + colorette "1.1.0" + commander "^4.0.1" + debug "4.1.1" + getopts "2.2.5" + inherits "~2.0.4" + interpret "^2.0.0" + liftoff "3.1.0" + lodash "^4.17.15" + mkdirp "^0.5.1" + pg-connection-string "2.1.0" + tarn "^2.0.0" + tildify "2.0.0" + uuid "^3.3.3" + v8flags "^3.1.3" + latest-version@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" @@ -4017,6 +4160,20 @@ lcid@^2.0.0: dependencies: invert-kv "^2.0.0" +liftoff@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-3.1.0.tgz#c9ba6081f908670607ee79062d700df062c52ed3" + integrity sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog== + dependencies: + extend "^3.0.0" + findup-sync "^3.0.0" + fined "^1.0.1" + flagged-respawn "^1.0.0" + is-plain-object "^2.0.4" + object.map "^1.0.0" + rechoir "^0.6.2" + resolve "^1.1.7" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -4083,6 +4240,11 @@ lodash@^4.17.11, lodash@^4.17.3, lodash@^4.17.4: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw== +lodash@^4.17.15: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + loglevel@^1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.3.tgz#77f2eb64be55a404c9fd04ad16d57c1d6d6b1280" @@ -4153,6 +4315,13 @@ make-dir@^2.0.0: pify "^4.0.1" semver "^5.6.0" +make-iterator@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" + integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== + dependencies: + kind-of "^6.0.2" + mamacro@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" @@ -4165,7 +4334,7 @@ map-age-cleaner@^0.1.1: dependencies: p-defer "^1.0.0" -map-cache@^0.2.2: +map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= @@ -4253,7 +4422,7 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -4817,6 +4986,16 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" +object.defaults@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" + integrity sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8= + dependencies: + array-each "^1.0.1" + array-slice "^1.0.0" + for-own "^1.0.0" + isobject "^3.0.0" + object.getownpropertydescriptors@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" @@ -4825,7 +5004,15 @@ object.getownpropertydescriptors@^2.0.3: define-properties "^1.1.2" es-abstract "^1.5.1" -object.pick@^1.3.0: +object.map@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" + integrity sha1-z4Plncj8wK1fQlDh94s7gb2AHTc= + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + +object.pick@^1.2.0, object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= @@ -5048,6 +5235,15 @@ parse-color@^1.0.0: dependencies: color-convert "~0.5.0" +parse-filepath@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" + integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= + dependencies: + is-absolute "^1.0.0" + map-cache "^0.2.0" + path-root "^0.1.1" + parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -5063,6 +5259,11 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + parseqs@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" @@ -5129,6 +5330,18 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-root-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" + integrity sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0= + +path-root@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" + integrity sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc= + dependencies: + path-root-regex "^0.1.0" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -5171,6 +5384,11 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +pg-connection-string@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.1.0.tgz#e07258f280476540b24818ebb5dca29e101ca502" + integrity sha512-bhlV7Eq09JrRIvo1eKngpwuqKtJnNhZdpdOlvrPrA4dxqXPjxSrbNrfnIDmTpwMyRszrcV4kU5ZA4mMsQUrjdg== + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -5651,6 +5869,13 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= + dependencies: + resolve "^1.1.6" + redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -5819,6 +6044,14 @@ resolve-cwd@^2.0.0: dependencies: resolve-from "^3.0.0" +resolve-dir@^1.0.0, resolve-dir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= + dependencies: + expand-tilde "^2.0.0" + global-modules "^1.0.0" + resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -5829,6 +6062,13 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve@^1.1.6, resolve@^1.1.7: + version "1.13.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16" + integrity sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w== + dependencies: + path-parse "^1.0.6" + resolve@^1.10.0: version "1.11.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" @@ -6310,10 +6550,10 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sqlite3@^4.0.9: - version "4.0.9" - resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.0.9.tgz#cff74550fa5a1159956815400bdef69245557640" - integrity sha512-IkvzjmsWQl9BuBiM4xKpl5X8WCR4w0AeJHRdobCdXZ8dT/lNc1XS6WqvY35N6+YzIIgzSBeY5prdFObID9F9tA== +sqlite3@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.1.1.tgz#539a42e476640796578e22d589b3283c28055242" + integrity sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg== dependencies: nan "^2.12.1" node-pre-gyp "^0.11.0" @@ -6569,6 +6809,11 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.3" +tarn@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tarn/-/tarn-2.0.0.tgz#c68499f69881f99ae955b4317ca7d212d942fdee" + integrity sha512-7rNMCZd3s9bhQh47ksAQd92ADFcJUjjbyOvyFjNLwTPpGieFHMC84S+LOzw0fx1uh6hnDz/19r8CPMnIjJlMMA== + temp-file@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/temp-file/-/temp-file-3.3.4.tgz#73af868cd7cb7400a44e4bb03e653b2280ce2878" @@ -6635,6 +6880,11 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826" integrity sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow== +tildify@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" + integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw== + timed-out@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" @@ -6782,6 +7032,11 @@ uglify-js@3.4.x: commander "~2.19.0" source-map "~0.6.1" +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= + undefsafe@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" @@ -6986,6 +7241,18 @@ uuid@^3.0.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== +uuid@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" + integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== + +v8flags@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" + integrity sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== + dependencies: + homedir-polyfill "^1.0.1" + validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -7204,7 +7471,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@^1.2.9: +which@^1.2.14, which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==