UNPKG

fewer

Version:

A minimal ORM for Node.js.

264 lines 9.16 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result["default"] = mod; return result; }; Object.defineProperty(exports, "__esModule", { value: true }); const sq_1 = __importDefault(require("@fewer/sq")); const createModel_1 = __importStar(require("./createModel")); var QueryTypes; (function (QueryTypes) { QueryTypes[QueryTypes["SINGLE"] = 0] = "SINGLE"; QueryTypes[QueryTypes["MULTIPLE"] = 1] = "MULTIPLE"; })(QueryTypes = exports.QueryTypes || (exports.QueryTypes = {})); class Repository { constructor(schemaTable, runningQuery, pipes, queryType) { /** * Contains symbols that are used to access metadata about the state of models. */ this.symbols = createModel_1.Symbols; this.schemaTable = schemaTable; this.primaryKey = schemaTable.primaryKey; this.runningQuery = runningQuery; this.pipes = pipes; this.queryType = queryType; } get db() { return this.schemaTable.database; } [("@@SCHEMA_TYPE" /* SCHEMA_TYPE */, "@@RESOLVED_TYPE" /* RESOLVED_TYPE */, "@@INTERNAL_TYPE" /* INTERNAL_TYPE */, "@@TO_SQ_SELECT" /* TO_SQ_SELECT */)]() { return this.selectQuery(); } getTableName() { return this.schemaTable.name; } /** * TODO: Documentation. */ pipe(pipe) { return new Repository(this.schemaTable, this.runningQuery, [...this.pipes, pipe], this.queryType); } /** * Validates an object. */ // TODO: Async validate(model) { // Ensure that we're actually working with a model: if (!model[createModel_1.Symbols.isModel]) { throw new Error('Attempted to validate an object that was not a fewer model.'); } // We clarify the object here to include the internal properties that exist on the model: const obj = model; // If validation has already run, then we can re-use the last result: if (obj[createModel_1.InternalSymbols.hasValidationRun]) { return obj[createModel_1.Symbols.valid]; } // Run through the error pipes and aggregate the validation errors: const errors = []; this.pipes.forEach(pipe => { if (!pipe.validate) return; const validationErrors = pipe.validate(obj); if (validationErrors) { if (Array.isArray(validationErrors)) { errors.push(...validationErrors); } else { errors.push(validationErrors); } } }); // NOTE: This also sets the hasValidationRun flag: obj[createModel_1.InternalSymbols.setErrors](errors); return errors.length === 0; } /** * Converts a plain JavaScript object into a Fewer model. */ from(obj) { return createModel_1.default(obj); } /** * TODO: Documentation. */ async create(obj) { const model = this.from(obj); const valid = this.validate(model); if (!valid) { throw new Error('model was not valid'); } const insertQuery = sq_1.default .insert(this.schemaTable.name, this.primaryKey) .set(model); const primaryKey = await this.db.insert(insertQuery); model[this.primaryKey] = primaryKey; return this.reload(model, true); } /** * Saves a model in the database. */ async save(model) { // Ensure that we're actually working with a model: if (!model[createModel_1.Symbols.isModel]) { throw new Error('Attempted to save an object that was not a fewer model.'); } const valid = this.validate(model); if (!valid) { // TODO: Expose the validation errors here: throw new Error('model was not valid'); } // If the model isn't dirty, then we don't need to do anything: if (!model[createModel_1.Symbols.dirty]) { return model; } // Generate a map of the properties that have changed: const changedProperties = model[createModel_1.Symbols.changed]; const changeSet = {}; for (const property of changedProperties) { changeSet[property] = model[property]; } const updateQuery = sq_1.default // NOTE: We need to cast these the primary key value here because the primary key value type is not statically known. .update(this.schemaTable.name, [ this.primaryKey, model[this.primaryKey], ]) .set(changeSet); await this.db.update(updateQuery); return this.reload(model); } /** * Reloads the model in-place. */ async reload(model, inPlace = false) { // TODO: Stash the repository onto the model so that we don't need to re-create this here? const query = this.selectQuery() .where({ id: model[this.primaryKey] }) .limit(1); const [data] = await this.db.select(query); if (inPlace) { const modelWithInternals = model; modelWithInternals[createModel_1.InternalSymbols.dynAssign](data); return model; } else { return this.from(data); } } /** * TODO: Documentation. */ where(wheres) { return new Repository(this.schemaTable, this.selectQuery().where(wheres), this.pipes, QueryTypes.MULTIPLE); } find(conditions) { let query = this.selectQuery().limit(1); if (typeof conditions !== 'object') { query = query.where({ [this.primaryKey]: conditions }); } else { query = query.where(conditions); } return new Repository(this.schemaTable, query, this.pipes, QueryTypes.SINGLE); } /** * TODO: Documentation. */ pluck(...columns) { return new Repository(this.schemaTable, this.selectQuery().pluck(...columns), this.pipes, this.queryType); } /** * TODO: Documentation */ pluckAs(name, alias) { return new Repository(this.schemaTable, this.selectQuery().pluck([name, alias]), this.pipes, this.queryType); } /** * TODO: Documentation. */ order() { throw new Error('Not implemented'); } /** * TODO: Documentation. */ limit(amount) { return new Repository(this.schemaTable, this.selectQuery().limit(amount), this.pipes, this.queryType); } /** * TODO: Documentation. */ offset(amount) { return new Repository(this.schemaTable, this.selectQuery().offset(amount), this.pipes, this.queryType); } /** * Loads an association. */ load(name, association) { let keys; if (association.type === 'belongsTo') { keys = [association.foreignKey, this.primaryKey]; } else { keys = [this.primaryKey, association.foreignKey]; } return new Repository(this.schemaTable, this.selectQuery().load(name, keys, association["@@TO_SQ_SELECT" /* TO_SQ_SELECT */]()), this.pipes, this.queryType); } /** * Resolves the association, but does not load the records. */ join(name, association) { let keys; if (association.type === 'belongsTo') { keys = [association.foreignKey, this.primaryKey]; } else { keys = [this.primaryKey, association.foreignKey]; } return new Repository(this.schemaTable, this.selectQuery().join(name, keys, association.getTableName(), association["@@TO_SQ_SELECT" /* TO_SQ_SELECT */]()), this.pipes, this.queryType); } /** * TODO: Documentation. */ async then(onFulfilled, onRejected) { try { const query = this.selectQuery(); const data = await this.db.select(query); if (this.queryType === QueryTypes.SINGLE) { return onFulfilled(data[0]); } else { return onFulfilled(data); } } catch (error) { if (onRejected) { return Promise.resolve(onRejected(error)); } else { return Promise.reject(error); } } } selectQuery() { if (!this.runningQuery) { this.runningQuery = sq_1.default.select(this.schemaTable.name); } return this.runningQuery; } } exports.Repository = Repository; /** * TODO: Documentation. */ function createRepository(schemaTable) { return new Repository(schemaTable, undefined, [], QueryTypes.MULTIPLE); } exports.createRepository = createRepository; //# sourceMappingURL=index.js.map