fewer
Version:
A minimal ORM for Node.js.
264 lines • 9.16 kB
JavaScript
"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