base-domain
Version:
simple module to help build Domain-Driven Design
512 lines (420 loc) • 12.4 kB
JavaScript
'use strict';
var Base, BaseRepository, Entity, GeneralFactory, isPromise,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
Base = require('./base');
Entity = require('./entity');
GeneralFactory = require('./general-factory');
isPromise = require('../util').isPromise;
/**
Base repository class of DDD pattern.
Responsible for perpetuation of models.
BaseRepository has a client, which access to data resource (RDB, NoSQL, memory, etc...)
the parent "Base" class just simply gives `this.facade` property
@class BaseRepository
@extends Base
@module base-domain
*/
BaseRepository = (function(superClass) {
extend(BaseRepository, superClass);
/**
model name to handle
@property modelName
@static
@protected
@type String
*/
BaseRepository.modelName = null;
BaseRepository.prototype.getModelName = function() {
var ref;
return (ref = this.constructor.modelName) != null ? ref : this.constructor.getName().slice(0, -'-repository'.length);
};
/**
client accessing to data resource (RDB, NoSQL, memory, etc...)
mock object is input by default.
Extenders must set this property to achieve perpetuation
@property client
@abstract
@protected
@type ResourceClientInterface
*/
BaseRepository.prototype.client = null;
/**
constructor
@constructor
@params {RootInterface} root
@return
*/
function BaseRepository(root) {
var modelName;
BaseRepository.__super__.constructor.call(this, root);
modelName = this.getModelName();
/**
factory of the entity.
@property {FactoryInterface} factory
*/
this.factory = GeneralFactory.create(modelName, this.root);
if (!((this.factory.getModelClass().prototype) instanceof Entity)) {
this.error('base-domain:repositoryWithNonEntity', "cannot define repository to non-entity: '" + modelName + "'");
}
}
/**
get model class this factory handles
@method getModelClass
@return {Class}
*/
BaseRepository.prototype.getModelClass = function() {
return this.factory.getModelClass();
};
/**
returns Promise or the result of given function
@return {any}
@protected
*/
BaseRepository.prototype.resolve = function(result, fn) {
if (isPromise(result)) {
return result.then((function(_this) {
return function(obj) {
return fn.call(_this, obj);
};
})(this));
} else {
return fn.call(this, result);
}
};
/**
Update or insert a model instance
@method save
@public
@param {Entity|Object} entity
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Entity|Promise(Entity)} entity (the same instance from input, if entity given,)
*/
BaseRepository.prototype.save = function(entity, options) {
var client, data, method;
if (options == null) {
options = {};
}
client = options.client;
delete options.client;
if (!(entity instanceof Entity)) {
entity = this.factory.createFromObject(entity, options);
}
if (client == null) {
client = this.client;
}
data = entity.toPlainObject();
this.appendTimeStamp(data);
method = (function() {
switch (options.method) {
case 'upsert':
case 'create':
return options.method;
default:
return 'upsert';
}
})();
return this.resolve(client[method](data), function(obj) {
var newEntity;
newEntity = this.createFromResult(obj, options);
if (this.getModelClass().isImmutable) {
return newEntity;
} else {
return entity.inherit(newEntity);
}
});
};
/**
get entity by id.
@method get
@public
@param {String|Number} id
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Entity|Promise(Entity)} entity
*/
BaseRepository.prototype.get = function(id, options) {
var client;
if (options == null) {
options = {};
}
client = options.client;
delete options.client;
if (client == null) {
client = this.client;
}
return this.resolve(client.findById(id), function(obj) {
return this.createFromResult(obj, options);
});
};
/**
get diff from perpetuation layer
@method getDiff
@public
@param {Entity} entity
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Object|Promise(Object)} diff
*/
BaseRepository.prototype.getDiff = function(entity, options) {
var client, id;
if (options == null) {
options = {};
}
id = entity.id;
if (id == null) {
throw this.error('EntityMustContainId');
}
client = options.client;
delete options.client;
if (client == null) {
client = this.client;
}
return this.resolve(client.findById(id), function(obj) {
return entity.getDiff(obj);
});
};
/**
alias for get()
@method getById
@public
@param {String|Number} id
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Entity|Promise(Entity)} entity
*/
BaseRepository.prototype.getById = function(id, options) {
return this.get(id, options);
};
/**
get entities by id.
@method getByIds
@public
@param {Array|(String|Number)} ids
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Array(Entity)|Promise(Array(Entity))} entities
*/
BaseRepository.prototype.getByIds = function(ids, options) {
var existence, id, results;
results = (function() {
var i, len, results1;
results1 = [];
for (i = 0, len = ids.length; i < len; i++) {
id = ids[i];
results1.push(this.get(id, options));
}
return results1;
}).call(this);
existence = function(val) {
return val != null;
};
if (isPromise(results[0])) {
return Promise.all(results).then(function(models) {
return models.filter(existence);
});
} else {
return results.filter(existence);
}
};
/**
get all entities
@method getAll
@return {Array(Entity)|Promise(Array(Entity))} array of entities
*/
BaseRepository.prototype.getAll = function() {
return this.query({});
};
/**
Find all model instances that match params
@method query
@public
@param {Object} [params] query parameters
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Array(Entity)|Promise(Array(Entity))} array of entities
*/
BaseRepository.prototype.query = function(params, options) {
var client;
if (options == null) {
options = {};
}
client = options.client;
delete options.client;
if (client == null) {
client = this.client;
}
return this.resolve(client.find(params), function(objs) {
return this.createFromQueryResults(params, objs, options);
});
};
/**
Find one model instance that matches params, Same as query, but limited to one result
@method singleQuery
@public
@param {Object} [params] query parameters
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Entity|Promise(Entity)} entity
*/
BaseRepository.prototype.singleQuery = function(params, options) {
var client;
if (options == null) {
options = {};
}
client = options.client;
delete options.client;
if (client == null) {
client = this.client;
}
return this.resolve(client.findOne(params), function(obj) {
return this.createFromResult(obj, options);
});
};
/**
Destroy the given entity (which must have "id" value)
@method delete
@public
@param {Entity} entity
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Boolean|Promise(Boolean)} isDeleted
*/
BaseRepository.prototype["delete"] = function(entity, options) {
var client;
if (options == null) {
options = {};
}
client = options.client;
delete options.client;
if (client == null) {
client = this.client;
}
return this.resolve(client.destroy(entity), function() {
return true;
});
};
/**
Update set of attributes.
@method update
@public
@param {String|Number} id id of the entity to update
@param {Object} data key-value pair to update (notice: this must not be instance of Entity)
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Entity|Promise(Entity)} updated entity
*/
BaseRepository.prototype.update = function(id, data, options) {
var client, isUpdate;
if (options == null) {
options = {};
}
client = options.client;
delete options.client;
if (data instanceof Entity) {
throw this.error('base-domain:updateWithModelInhihited', "update entity with BaseRepository#update() is not allowed.\nuse BaseRepository#save(entity) instead");
}
if (client == null) {
client = this.client;
}
this.appendTimeStamp(data, isUpdate = true);
return this.resolve(client.updateAttributes(id, data), function(obj) {
return this.createFromResult(obj, options);
});
};
/**
Update set of attributes and returns newly-updated props (other than `props`)
@method updateProps
@public
@param {Entity} entity
@param {Object} data key-value pair to update (notice: this must not be instance of Entity)
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Promise(Object)|Object} updated props
*/
BaseRepository.prototype.updateProps = function(entity, props, options) {
var client, id, isUpdate;
if (props == null) {
props = {};
}
if (options == null) {
options = {};
}
id = entity.id;
if (id == null) {
throw this.error('EntityMustContainId');
}
client = options.client;
delete options.client;
if (client == null) {
client = this.client;
}
this.appendTimeStamp(props, isUpdate = true);
return this.resolve(client.updateAttributes(id, props), function(obj) {
return entity.getDiff(obj, {
ignores: Object.keys(props)
});
});
};
/**
add createdAt, updatedAt to given data
- createdAt will not be overriden if already set.
- updatedAt will be overriden for each time
@method appendTimeStamp
@protected
@param {Object} data
@param {Boolean} isUpdate true when updating
@return {Object} data
*/
BaseRepository.prototype.appendTimeStamp = function(data, isUpdate) {
var modelProps, now, propCreatedAt, propUpdatedAt;
if (isUpdate == null) {
isUpdate = false;
}
modelProps = this.facade.getModelProps(this.getModelName());
propCreatedAt = modelProps.createdAt;
propUpdatedAt = modelProps.updatedAt;
now = new Date().toISOString();
if (propCreatedAt && !isUpdate) {
if (data[propCreatedAt] == null) {
data[propCreatedAt] = now;
}
}
if (propUpdatedAt) {
data[propUpdatedAt] = now;
}
return data;
};
/**
Create model instance from result from client
@method createFromResult
@protected
@param {Object} obj
@param {Object} [options]
@return {BaseModel} model
*/
BaseRepository.prototype.createFromResult = function(obj, options) {
return this.factory.createFromObject(obj, options);
};
/**
Create model instances from query results
@method createFromQueryResults
@protected
@param {Object} params
@param {Array(Object)} objs
@param {Object} [options]
@return {Array(BaseModel)} models
*/
BaseRepository.prototype.createFromQueryResults = function(params, objs, options) {
var i, len, obj, results1;
results1 = [];
for (i = 0, len = objs.length; i < len; i++) {
obj = objs[i];
results1.push(this.createFromResult(obj, options));
}
return results1;
};
return BaseRepository;
})(Base);
module.exports = BaseRepository;