enju
Version:
An elasticsearch on node.js written in CoffeeScript.
398 lines (358 loc) • 13.6 kB
text/coffeescript
config = require 'config'
q = require 'q'
utils = require './utils'
Query = require './query'
properties = require './properties'
exceptions = require './exceptions'
module.exports = 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={}) ->
for propertyName, property of @constructor._properties
@[propertyName] = property.toJs args[propertyName]
# -----------------------------------------------------
# private methods
# -----------------------------------------------------
@getIndexName = ->
###
Get the index name with prefix.
@returns: {string}
###
"#{utils.getIndexPrefix()}#{@_index}"
@getDocumentType = ->
###
Get the document type.
@returns {string}
###
@_type ? @name
# -----------------------------------------------------
# public methods
# -----------------------------------------------------
@define = ->
###
1. define properties with class name.
@param className: {string}
@param properties: {object}
@returns {constructor}
2. define properties for this document.
@param properties: {object}
###
@_properties =
id: new properties.StringProperty(dbField: '_id')
version: new properties.IntegerProperty(dbField: '_version')
@_es = utils.getElasticsearch()
if arguments.length is 2 and typeof(arguments[0]) is 'string' and arguments[1]? and typeof(arguments[1]) is 'object'
# 1. define properties with class name.
defined = arguments[1]
defined._type ?= arguments[0]
class DynamicClass extends @
@define defined
return DynamicClass
else if arguments.length is 1 and arguments[0]? and typeof(arguments[0]) is 'object'
# 2. define properties for this document.
defined = arguments[0]
if '_index' of defined
@_index = defined._index
delete defined._index
if '_settings' of defined
@_settings = defined._settings
delete defined._settings
if '_type' of defined
@_type = defined._type
delete defined._type
for propertyName, property of defined
property.propertyName = propertyName
@_properties[propertyName] = property
return
throw exceptions.ArgumentError('Argument error for enju.Document.define()')
@get = (ids, fetchReference=yes) ->
###
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)
###
deferred = q.defer()
# the empty document
if not ids? or ids is ''
deferred.resolve null
return deferred.promise
# empty documents
if ids.constructor is Array
ids = (x for x in ids when x)
if ids.length is 0
deferred.resolve []
return deferred.promise
# fetch documents
if ids.constructor is Array
@_es.mget
index: @getIndexName()
type: @getDocumentType()
body:
ids: ids
, (error, response) =>
if error
deferred.reject error
return
result = []
for doc in response.docs when doc.found
item =
id: doc._id
version: doc._version
for propertyName, property of @_properties
dbField = property.dbField ? propertyName
if dbField of doc._source
item[propertyName] = doc._source[dbField]
result.push new @(item)
# call resolve()
if fetchReference
Query.updateReferenceProperties(result).then ->
deferred.resolve result
else
deferred.resolve result
return deferred.promise
# fetch the document
@_es.get
index: @getIndexName()
type: @getDocumentType()
id: ids
, (error, response) =>
if error
if error.status is 404
deferred.resolve null
return
deferred.reject error
return
args =
id: response._id
version: response._version
for propertyName, property of @_properties
dbField = property.dbField ? propertyName
if dbField of response._source
args[propertyName] = response._source[dbField]
# call resolve()
document = new @(args)
if fetchReference
Query.updateReferenceProperties([document]).then ->
deferred.resolve document
else
deferred.resolve document
deferred.promise
@exists = (id) ->
###
Is the document exists?
@param id {string} The documents' id.
@returns {promise<bool>}
###
deferred = q.defer()
@_es.exists
index: @getIndexName()
type: @getDocumentType()
id: id
, (error, response) ->
if error
deferred.reject error
return
deferred.resolve response
deferred.promise
@all = ->
###
Generate a query for this document.
@returns {Query}
###
new Query(@)
@where = (field, operation) ->
###
Generate the query for this document.
Please via Query.intersect().
###
query = new Query(@)
query.intersect field, operation
@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}
###
createIndex = =>
deferred = q.defer()
@_es.indices.create
index: @getIndexName()
, (error, response) ->
if error and error.status isnt 400
deferred.reject error
return
setTimeout ->
deferred.resolve response
, 1000
deferred.promise
closeIndex = =>
deferred = q.defer()
@_es.indices.close
index: @getIndexName()
, (error, response) ->
if error
deferred.reject error
return
deferred.resolve response
deferred.promise
putSettings = =>
deferred = q.defer()
if not @_settings
deferred.resolve()
return deferred.promise
@_es.indices.putSettings
index: @getIndexName()
body:
settings:
index: @_settings
, (error, response) ->
if error
deferred.reject error
return
deferred.resolve response
deferred.promise
putMapping = =>
deferred = q.defer()
mapping = {}
for propertyName, property of @_properties
if property.dbField in ['_id', '_version']
# don't set the mapping to _id and _version
continue
if property.mapping
# there is an object in this field
mapping[property.dbField ? propertyName] =
properties: property.mapping
continue
field = {}
switch property.constructor
when properties.StringProperty
field.type = 'string'
when properties.BooleanProperty
field.type = 'boolean'
when properties.IntegerProperty
field.type = 'long'
when properties.FloatProperty
field.type = 'double'
when properties.DateProperty
field.type = 'date'
field.format = 'strict_date_optional_time||epoch_millis'
when properties.ReferenceProperty
field.type = 'string'
field.analyzer = 'keyword'
when properties.ListProperty
switch property.itemClass
when properties.StringProperty
field.type = 'string'
when properties.BooleanProperty
field.type = 'boolean'
when properties.IntegerProperty
field.type = 'long'
when properties.FloatProperty
field.type = 'double'
when properties.DateProperty
field.type = 'date'
field.format = 'strict_date_optional_time||epoch_millis'
when properties.ReferenceProperty
field.type = 'string'
field.analyzer = 'keyword'
if property.type
field.type = property.type
if property.analyzer
field.analyzer = property.analyzer
if property.index
field.index = property.index
if Object.keys(field).length
mapping[property.dbField ? propertyName] = field
@_es.indices.putMapping
index: @getIndexName()
type: @getDocumentType()
body:
properties: mapping
, (error, response) ->
if error
deferred.reject error
return
deferred.resolve response
deferred.promise
openIndex = =>
deferred = q.defer()
@_es.indices.open
index: @getIndexName()
, (error, response) ->
if error
deferred.reject error
return
deferred.resolve response
deferred.promise
deferred = q.defer()
createIndex()
.then closeIndex
.then putSettings
.then putMapping
.then openIndex
.then =>
console.log "updated mapping [#{@getIndexName()}]"
deferred.resolve()
, (error) ->
console.error error
deferred.reject error
deferred.promise
save: (refresh=no) ->
###
Save this document.
@param refresh {bool} Refresh the index after performing the operation.
@returns {promise<Document>}
###
deferred = q.defer()
document = {} # it will be written to database
for propertyName, property of @constructor._properties when property.dbField not in ['_id', '_version']
dbFieldName = property.dbField ? propertyName
try
document[dbFieldName] = property.toDb @
catch error
deferred.reject error
return deferred.promise
@constructor._es.index
index: @constructor.getIndexName()
type: @constructor.getDocumentType()
refresh: refresh
id: @id
version: if @version? then @version + 1 else 0
versionType: 'external'
body: document
, (error, response) =>
if error
deferred.reject error
return
@id = response._id
@version = response._version
deferred.resolve @
deferred.promise
delete: (refresh=no) ->
###
Delete this document.
@returns {promise<Document>}
###
deferred = q.defer()
@constructor._es.delete
index: @constructor.getIndexName()
type: @constructor.getDocumentType()
refresh: refresh
id: @id
, (error) =>
if error
deferred.reject error
return
deferred.resolve @
deferred.promise