enju
Version:
An elasticsearch client on node.js written in CoffeeScript.
575 lines (553 loc) • 18.9 kB
JavaScript
(function() {
var Document, Query, exceptions, properties, utils;
utils = require('./utils');
Query = require('./query');
properties = require('./properties');
exceptions = require('./exceptions');
module.exports = Document = class Document {
/*
You have to call define() to define your document.
@property _index {string} You can set index name by this attribute.
@property _type {string} You can set type of the document. The default is class name.
@property _settings {object} You can set index settings by this attribute.
@property id {string}
@property version {number}
@property _properties {object} {'propertyName': {Property}}
@property _es {Elasticsearch.Client}
*/
constructor(args = {}) {
var property, propertyName, ref;
ref = this.constructor._properties;
for (propertyName in ref) {
property = ref[propertyName];
this[propertyName] = property.toJs(args[propertyName]);
}
}
// -----------------------------------------------------
// private methods
// -----------------------------------------------------
static getIndexName() {
/*
Get the index name with prefix.
@returns: {string}
*/
return `${utils.getIndexPrefix()}${this._index}`;
}
static getDocumentType() {
var ref;
/*
Get the document type.
@returns {string}
*/
return (ref = this._type) != null ? ref : this.name;
}
// -----------------------------------------------------
// public methods
// -----------------------------------------------------
static define() {
var DynamicClass, defined, property, propertyName;
/*
1. define properties with class name.
@param className: {string}
@param properties: {object}
@returns {constructor}
2. define properties for this document.
@param properties: {object}
*/
this._properties = {
id: new properties.KeywordProperty({
dbField: '_id'
}),
version: new properties.IntegerProperty({
dbField: '_version'
})
};
this._es = utils.getElasticsearch();
if (arguments.length === 2 && typeof arguments[0] === 'string' && typeof arguments[1] === 'object') {
// 1. define properties with class name.
defined = arguments[1];
if (defined._type == null) {
defined._type = arguments[0];
}
DynamicClass = (function() {
class DynamicClass extends this {};
DynamicClass.define(defined);
return DynamicClass;
}).call(this);
return DynamicClass;
} else if (arguments.length === 1 && typeof arguments[0] === 'object') {
// 2. define properties for this document.
defined = arguments[0];
if ('_index' in defined) {
this._index = defined._index;
delete defined._index;
}
if ('_settings' in defined) {
this._settings = defined._settings;
delete defined._settings;
}
if ('_type' in defined) {
this._type = defined._type;
delete defined._type;
}
for (propertyName in defined) {
property = defined[propertyName];
property.propertyName = propertyName;
this._properties[propertyName] = property;
}
return;
}
throw new exceptions.ArgumentError('Argument error for enju.Document.define()');
}
static get(ids, fetchReference = true) {
return new Promise((resolve, reject) => {
var previousError, x;
/*
Fetch the document with id or ids.
If the document is not exist, it will return null.
@param ids {string|list}
@param fetchReference {bool} Fetch reference data of this document.
@returns {promise} (Document|null|list)
*/
// the empty document
if ((ids == null) || ids === '') {
return resolve(null);
}
// empty documents
if (ids.constructor === Array) {
ids = (function() {
var i, len, results;
results = [];
for (i = 0, len = ids.length; i < len; i++) {
x = ids[i];
if (x) {
results.push(x);
}
}
return results;
})();
if (ids.length === 0) {
return resolve([]);
}
}
// fetch documents
if (ids.constructor === Array) {
previousError = new Error('From previous stack');
this._es.mget({
index: this.getIndexName(),
type: this.getDocumentType(),
body: {
ids: ids
}
}, (error, response) => {
var dbField, doc, i, item, len, property, propertyName, ref, ref1, ref2, result;
if (error) {
error.stack += previousError.stack;
return reject(error);
}
result = [];
ref = response.docs;
for (i = 0, len = ref.length; i < len; i++) {
doc = ref[i];
if (!doc.found) {
continue;
}
item = {
id: doc._id,
version: doc._version
};
ref1 = this._properties;
for (propertyName in ref1) {
property = ref1[propertyName];
dbField = (ref2 = property.dbField) != null ? ref2 : propertyName;
if (dbField in doc._source) {
item[propertyName] = doc._source[dbField];
}
}
result.push(new this(item));
}
// call resolve()
if (fetchReference) {
return utils.updateReferenceProperties(result).then(function() {
return resolve(result);
}).catch(function(error) {
return reject(error);
});
} else {
return resolve(result);
}
});
return;
}
// fetch the document
previousError = new Error('From previous stack');
return this._es.get({
index: this.getIndexName(),
type: this.getDocumentType(),
id: ids
}, (error, response) => {
var args, dbField, document, property, propertyName, ref, ref1;
if (error) {
error.stack += previousError.stack;
if (error.status === 404) {
return resolve(null);
}
return reject(error);
}
args = {
id: response._id,
version: response._version
};
ref = this._properties;
for (propertyName in ref) {
property = ref[propertyName];
dbField = (ref1 = property.dbField) != null ? ref1 : propertyName;
if (dbField in response._source) {
args[propertyName] = response._source[dbField];
}
}
// call resolve()
document = new this(args);
if (fetchReference) {
return utils.updateReferenceProperties([document]).then(function() {
return resolve(document);
}).catch(function(error) {
return reject(error);
});
} else {
return resolve(document);
}
});
});
}
static exists(id) {
return new Promise((resolve, reject) => {
/*
Is the document exists?
@param id {string} The documents' id.
@returns {promise<bool>}
*/
var previousError;
previousError = new Error('From previous stack');
return this._es.exists({
index: this.getIndexName(),
type: this.getDocumentType(),
id: id
}, function(error, response) {
if (error) {
error.stack += previousError.stack;
return reject(error);
}
return resolve(response);
});
});
}
static all() {
/*
Generate a query for this document.
@returns {Query}
*/
return new Query(this);
}
static where(field, operation) {
/*
Generate the query for this document.
Please via Query.intersect().
*/
var query;
query = new Query(this);
return query.intersect(field, operation);
}
static refresh(args = {}) {
return new Promise((resolve, reject) => {
var previousError;
/*
Explicitly refresh one or more index, making all operations performed since the last refresh available for search.
https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/api-reference-5-6.html#api-indices-refresh-5-6
@params args {object}
@returns {promise}
*/
args.index = this.getIndexName();
previousError = new Error('From previous stack');
return this._es.indices.refresh(args, function(error) {
if (error) {
error.stack += previousError.stack;
return reject(error);
}
return resolve();
});
});
}
static updateMapping() {
/*
Update the index mapping.
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html
@returns {promise}
*/
var closeIndex, createIndex, openIndex, putMapping, putSettings;
createIndex = () => {
return new Promise((resolve, reject) => {
var previousError;
previousError = new Error('From previous stack');
return this._es.indices.create({
index: this.getIndexName()
}, function(error, response) {
if (error && error.status !== 400) {
error.stack += previousError.stack;
return reject(error);
}
return setTimeout(function() {
return resolve(response);
}, 1000);
});
});
};
closeIndex = () => {
return new Promise((resolve, reject) => {
var previousError;
previousError = new Error('From previous stack');
return this._es.indices.close({
index: this.getIndexName()
}, function(error, response) {
if (error) {
error.stack += previousError.stack;
return reject(error);
}
return resolve(response);
});
});
};
putSettings = () => {
return new Promise((resolve, reject) => {
var previousError;
if (!this._settings) {
return resolve();
}
previousError = new Error('From previous stack');
return this._es.indices.putSettings({
index: this.getIndexName(),
body: {
settings: {
index: this._settings
}
}
}, function(error, response) {
if (error) {
error.stack += previousError.stack;
return reject(error);
}
return resolve(response);
});
});
};
putMapping = () => {
return new Promise((resolve, reject) => {
var field, mapping, previousError, property, propertyName, ref, ref1, ref2, ref3;
mapping = {};
ref = this._properties;
for (propertyName in ref) {
property = ref[propertyName];
if ((ref1 = property.dbField) === '_id' || ref1 === '_version') {
// don't set the mapping to _id and _version
continue;
}
if (property.mapping) {
// there is an object in this field
mapping[(ref2 = property.dbField) != null ? ref2 : propertyName] = {
properties: property.mapping
};
continue;
}
field = {};
switch (property.constructor) {
case properties.StringProperty:
field.type = 'string';
break;
case properties.TextProperty:
field.type = 'text';
break;
case properties.KeywordProperty:
field.type = 'keyword';
break;
case properties.BooleanProperty:
field.type = 'boolean';
break;
case properties.IntegerProperty:
field.type = 'long';
break;
case properties.FloatProperty:
field.type = 'double';
break;
case properties.DateProperty:
field.type = 'date';
field.format = 'strict_date_optional_time||epoch_millis';
break;
case properties.ReferenceProperty:
field.type = 'keyword';
break;
case properties.ListProperty:
switch (property.itemClass) {
case properties.StringProperty:
field.type = 'string';
break;
case properties.TextProperty:
field.type = 'text';
break;
case properties.KeywordProperty:
field.type = 'keyword';
break;
case properties.BooleanProperty:
field.type = 'boolean';
break;
case properties.IntegerProperty:
field.type = 'long';
break;
case properties.FloatProperty:
field.type = 'double';
break;
case properties.DateProperty:
field.type = 'date';
field.format = 'strict_date_optional_time||epoch_millis';
break;
case properties.ReferenceProperty:
field.type = 'keyword';
}
}
if (property.type) {
field.type = property.type;
}
if (property.analyzer) {
field.analyzer = property.analyzer;
}
if (property.normalizer) {
field.normalizer = property.normalizer;
}
if (property.index != null) {
field.index = property.index;
}
if (Object.keys(field).length) {
mapping[(ref3 = property.dbField) != null ? ref3 : propertyName] = field;
}
}
previousError = new Error('From previous stack');
return this._es.indices.putMapping({
index: this.getIndexName(),
type: this.getDocumentType(),
body: {
properties: mapping
}
}, function(error, response) {
if (error) {
error.stack += previousError.stack;
return reject(error);
}
return resolve(response);
});
});
};
openIndex = () => {
return new Promise((resolve, reject) => {
var previousError;
previousError = new Error('From previous stack');
return this._es.indices.open({
index: this.getIndexName()
}, function(error, response) {
if (error) {
error.stack += previousError.stack;
return reject(error);
}
return resolve(response);
});
});
};
return createIndex().then(closeIndex).then(putSettings).then(putMapping).then(openIndex).then(() => {
return console.log(`updated mapping [${this.getIndexName()}]`);
}).catch(function(error) {
console.error(error);
throw error;
});
}
save(refresh = false) {
return new Promise((resolve, reject) => {
var args, convertError, dbFieldName, document, error, previousError, property, propertyName, ref, ref1, ref2;
/*
Save this document.
@param refresh {bool} Refresh the index after performing the operation.
@returns {promise<Document>}
*/
document = {}; // it will be written to database
convertError = null;
ref = this.constructor._properties;
for (propertyName in ref) {
property = ref[propertyName];
if (!((ref1 = property.dbField) !== '_id' && ref1 !== '_version')) {
continue;
}
dbFieldName = (ref2 = property.dbField) != null ? ref2 : propertyName;
try {
document[dbFieldName] = property.toDb(this);
} catch (error1) {
error = error1;
convertError = error;
}
}
if (convertError != null) {
return reject(convertError);
}
if (this.id) {
// modify
args = {
index: this.constructor.getIndexName(),
type: this.constructor.getDocumentType(),
refresh: refresh,
id: this.id,
version: this.version != null ? this.version + 1 : 0,
versionType: 'external',
body: document
};
} else {
// create
args = {
index: this.constructor.getIndexName(),
type: this.constructor.getDocumentType(),
refresh: refresh,
body: document
};
}
previousError = new Error('From previous stack');
return this.constructor._es.index(args, (error, response) => {
if (error) {
error.stack += previousError.stack;
return reject(error);
}
this.id = response._id;
this.version = response._version;
return resolve(this);
});
});
}
delete(refresh = false) {
return new Promise((resolve, reject) => {
/*
Delete this document.
@returns {promise<Document>}
*/
var previousError;
previousError = new Error('From previous stack');
return this.constructor._es.delete({
index: this.constructor.getIndexName(),
type: this.constructor.getDocumentType(),
refresh: refresh,
id: this.id
}, (error) => {
if (error) {
error.stack += previousError.stack;
return reject(error);
}
return resolve(this);
});
});
}
};
}).call(this);