1 | # enju [![circle-ci](https://circleci.com/gh/kelp404/enju.png?circle-token=55c92edca67c45f7e79d9d9bc6ffce340a462878)](https://circleci.com/gh/kelp404/enju)
|
2 | [![npm version](https://badge.fury.io/js/enju.svg)](https://www.npmjs.com/package/enju)
|
3 |
|
4 | An elasticsearch client on node.js written in CoffeeScript.
|
5 | [tina](https://github.com/kelp404/tina) is the Python version.
|
6 |
|
7 | ![tina](_enju.gif)
|
8 |
|
9 |
|
10 |
|
11 | ## Installation
|
12 | ```bash
|
13 | $ npm install enju --save
|
14 | ```
|
15 |
|
16 |
|
17 |
|
18 | ## Config
|
19 | enju use [node-config](https://github.com/lorenwest/node-config).
|
20 | `/your_project/config/default.cson`
|
21 | ```cson
|
22 | enjuElasticsearchHost: 'http://localhost:9200'
|
23 | enjuIndexPrefix: ''
|
24 | ```
|
25 |
|
26 |
|
27 |
|
28 | ## Quick start
|
29 | ### 1. Define models
|
30 | ```coffee
|
31 | enju = require 'enju'
|
32 | class UserModel extends enju.Document
|
33 | @_index = 'users' # your index name
|
34 | @_settings =
|
35 | analysis:
|
36 | analyzer:
|
37 | email_url:
|
38 | type: 'custom'
|
39 | tokenizer: 'uax_url_email'
|
40 | @define
|
41 | name: new enju.StringProperty
|
42 | required: yes
|
43 | email: new enju.StringProperty
|
44 | required: yes
|
45 | analyzer: 'email_url'
|
46 | createTime: new enju.DateProperty
|
47 | autoNow: yes
|
48 | dbField: 'create_time'
|
49 | class ProductModel extends enju.Document
|
50 | @_index = 'products'
|
51 | @define
|
52 | user: new enju.ReferenceProperty
|
53 | referenceClass: UserModel
|
54 | required: yes
|
55 | title: new enju.StringProperty
|
56 | required: yes
|
57 | ```
|
58 | ### 2. Update elasticsearch mapping
|
59 | ```coffee
|
60 | UserModel.updateMapping()
|
61 | ProductModel.updateMapping()
|
62 | ```
|
63 | ### 3. Insert documents
|
64 | ```coffee
|
65 | user = new UserModel
|
66 | name: 'Kelp'
|
67 | email: 'kelp@phate.org'
|
68 | user.save().then (user) ->
|
69 | product = new ProductModel
|
70 | user: user
|
71 | title: 'enju'
|
72 | product.save()
|
73 | ```
|
74 | ### 4. Fetch documents
|
75 | ```coffee
|
76 | ProductModel.where('title', '==': 'enju').fetch().then (result) ->
|
77 | console.log JSON.stringify(result.items, null, 4)
|
78 | # [{
|
79 | # "id": "AU-mMiIwtrhIjlPeQBbT",
|
80 | # "version": 1,
|
81 | # "user": {
|
82 | # "id": "AU-mMiIOtrhIjlPeQBbS",
|
83 | # "version": 1,
|
84 | # "name": "Kelp",
|
85 | # "email": "kelp@phate.org",
|
86 | # "createTime": "2015-09-07T05:05:47.500Z"
|
87 | # },
|
88 | # "title": "enju"
|
89 | # }]
|
90 | ```
|
91 |
|
92 |
|
93 |
|
94 | ## Develop
|
95 | ```bash
|
96 | # install dependencies
|
97 | npm install -g nodeunit
|
98 | npm install -g grunt-cli
|
99 | npm install
|
100 | ```
|
101 |
|
102 | ```bash
|
103 | # compile and watch
|
104 | grunt dev
|
105 | ```
|
106 |
|
107 | ```bash
|
108 | # build coffee-script
|
109 | grunt build
|
110 | ```
|
111 |
|
112 | ```bash
|
113 | # run test
|
114 | grunt build
|
115 | npm test
|
116 | ```
|
117 |
|
118 |
|
119 |
|
120 | ## Document
|
121 | ```coffee
|
122 | # CoffeeScript
|
123 | enju = require 'enju'
|
124 | class UserModel extends enju.Document
|
125 | @_index = 'users' # your index name
|
126 | @_settings =
|
127 | analysis:
|
128 | analyzer:
|
129 | email_url:
|
130 | type: 'custom'
|
131 | tokenizer: 'uax_url_email'
|
132 | @define
|
133 | name: new enju.StringProperty
|
134 | required: yes
|
135 | email: new enju.StringProperty
|
136 | required: yes
|
137 | analyzer: 'email_url'
|
138 | createTime: new enju.DateProperty
|
139 | autoNow: yes
|
140 | dbField: 'create_time'
|
141 | ```
|
142 | ```js
|
143 | // JavaScript
|
144 | var enju = require('enju');
|
145 | var UserModel = enju.Document.define('UserModel', {
|
146 | _index: 'users',
|
147 | _settings: {
|
148 | analysis: {
|
149 | analyzer: {
|
150 | email_url: {
|
151 | type: 'custom',
|
152 | tokenizer: 'uax_url_email'
|
153 | }
|
154 | }
|
155 | }
|
156 | },
|
157 | name: new enju.StringProperty({
|
158 | required: true
|
159 | }),
|
160 | email: new enju.StringProperty({
|
161 | required: true,
|
162 | analyzer: 'email_url'
|
163 | }),
|
164 | createTime: new enju.DateProperty({
|
165 | autoNow: true,
|
166 | dbField: 'create_time'
|
167 | })
|
168 | });
|
169 | ```
|
170 |
|
171 | **Properties**
|
172 | ```coffee
|
173 | class Document
|
174 | ###
|
175 | _index {string} You can set index name by this attribute. **constructor property**
|
176 | _type {string} You can set type of the document. The default is class name. **constructor property**
|
177 | _settings {object} You can set index settings by this attribute. **constructor property**
|
178 | id {string}
|
179 | version {number}
|
180 | ###
|
181 | ```
|
182 |
|
183 | **Class method**
|
184 | ```coffee
|
185 | @get = (ids, fetchReference=yes) ->
|
186 | ###
|
187 | Fetch the document with id or ids.
|
188 | If the document is not exist, it will return null.
|
189 | @param ids {string|list}
|
190 | @param fetchReference {bool} Fetch reference data of this document.
|
191 | @returns {promise<Document>}
|
192 | ###
|
193 | # ex: Document.get('MQ-ULRSJ291RG_eEwSfQ').then (result) ->
|
194 | # ex: Document.get(['MQ-ULRSJ291RG_eEwSfQ']).then (result) ->
|
195 | ```
|
196 | ```coffee
|
197 | @exists = (id) ->
|
198 | ###
|
199 | Is the document exists?
|
200 | @param id {string} The documents' id.
|
201 | @returns {promise<bool>}
|
202 | ###
|
203 | ```
|
204 | ```coffee
|
205 | @all = ->
|
206 | ###
|
207 | Generate a query for this document.
|
208 | @returns {Query}
|
209 | ###
|
210 | # ex: query = Document.all()
|
211 | ```
|
212 | ```coffee
|
213 | @where = (field, operation) ->
|
214 | ###
|
215 | Generate the query for this document.
|
216 | @param field {Property|string|function}
|
217 | Property: The property of the document.
|
218 | string: The property name of the document.
|
219 | function: The sub query.
|
220 | @param operation {object}
|
221 | key: [
|
222 | '!=', 'unequal'
|
223 | '==', 'equal'
|
224 | '<', 'less'
|
225 | '<=', 'lessEqual'
|
226 | '>', 'greater',
|
227 | '>=', 'greaterEqual'
|
228 | 'like'
|
229 | 'unlike'
|
230 | 'contains'
|
231 | 'exclude'
|
232 | ]
|
233 | @returns {Query}
|
234 | ###
|
235 | # ex: query = Document.where('field', '==': 'value')
|
236 | ```
|
237 | ```coffee
|
238 | @updateMapping = ->
|
239 | ###
|
240 | Update the index mapping.
|
241 | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html
|
242 | https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html
|
243 | @returns {promise}
|
244 | ###
|
245 | ```
|
246 |
|
247 | **Method**
|
248 | ```coffee
|
249 | save: (refresh=no) ->
|
250 | ###
|
251 | Save this document.
|
252 | @param refresh {bool} Refresh the index after performing the operation.
|
253 | @returns {promise<Document>}
|
254 | ###
|
255 | ```
|
256 | ```coffee
|
257 | delete: (refresh=no) ->
|
258 | ###
|
259 | Delete this document.
|
260 | @returns {promise<Document>}
|
261 | ###
|
262 | ```
|
263 |
|
264 |
|
265 |
|
266 | ## Property
|
267 | ```coffee
|
268 | class Property
|
269 | ###
|
270 | @property default {bool}
|
271 | @property required {bool}
|
272 | @property dbField {string}
|
273 | @property type {string} For elasticsearch mapping
|
274 | @property index {string} For elasticsearch mapping
|
275 | @property analyzer {string} For elasticsearch mapping
|
276 | @property mapping {object} For elasticsearch mapping
|
277 | @property propertyName {string} The property name in the document. It will be set at Document.define()
|
278 | ###
|
279 | ```
|
280 | ```coffee
|
281 | class StringProperty extends Property
|
282 | ```
|
283 | ```coffee
|
284 | class IntegerProperty extends Property
|
285 | ```
|
286 | ```coffee
|
287 | class FloatProperty extends Property
|
288 | ```
|
289 | ```coffee
|
290 | class BooleanProperty extends Property
|
291 | ```
|
292 | ```coffee
|
293 | class DateProperty extends Property
|
294 | ###
|
295 | @property autoNow {bool}
|
296 | ###
|
297 | ```
|
298 | ```coffee
|
299 | class ListProperty extends Property
|
300 | ###
|
301 | @property itemClass {constructor}
|
302 | ###
|
303 | ```
|
304 | ```coffee
|
305 | class ObjectProperty extends Property
|
306 | ```
|
307 | ```coffee
|
308 | class ReferenceProperty extends Property
|
309 | ###
|
310 | @property referenceClass {Property}
|
311 | ###
|
312 | ```
|
313 |
|
314 |
|
315 |
|
316 | ## Query
|
317 | The enju query.
|
318 |
|
319 | **Methods**
|
320 | ```coffee
|
321 | where: (field, operation) ->
|
322 | ###
|
323 | Append a query as intersect.
|
324 | @param field {Property|string|function}
|
325 | Property: The property of the document.
|
326 | string: The property name of the document.
|
327 | function: The sub query.
|
328 | @param operation {object}
|
329 | key: [
|
330 | '!=', 'unequal'
|
331 | '==', 'equal'
|
332 | '<', 'less'
|
333 | '<=', 'lessEqual'
|
334 | '>', 'greater',
|
335 | '>=', 'greaterEqual'
|
336 | 'like'
|
337 | 'unlike'
|
338 | 'contains'
|
339 | 'exclude'
|
340 | ]
|
341 | @returns {Query}
|
342 | ###
|
343 | ```
|
344 | ```coffee
|
345 | union: (field, operation) ->
|
346 | ###
|
347 | Append a query as intersect.
|
348 | @param field {Property|string}
|
349 | Property: The property of the document.
|
350 | string: The property name of the document.
|
351 | @param operation {object}
|
352 | key: [
|
353 | '!=', 'unequal'
|
354 | '==', 'equal'
|
355 | '<', 'less'
|
356 | '<=', 'lessEqual'
|
357 | '>', 'greater',
|
358 | '>=', 'greaterEqual'
|
359 | 'like'
|
360 | 'unlike'
|
361 | 'contains'
|
362 | 'exclude'
|
363 | ]
|
364 | @returns {Query}
|
365 | ###
|
366 | ```
|
367 | ```coffee
|
368 | orderBy: (field, descending=no) ->
|
369 | ###
|
370 | Append the order query.
|
371 | @param field {Property|string} The property name of the document.
|
372 | @param descending {bool} Is sorted by descending?
|
373 | @returns {Query}
|
374 | ###
|
375 | ```
|
376 | ```coffee
|
377 | fetch: (args={}) ->
|
378 | ###
|
379 | Fetch documents by this query.
|
380 | @param args {object}
|
381 | limit: {number} The size of the pagination. (The limit of the result items.) default is 1000
|
382 | skip: {number} The offset of the pagination. (Skip x items.) default is 0
|
383 | fetchReference: {bool} Fetch documents of reference properties. default is true.
|
384 | @returns {promise<object>} ({items: {Document}, total: {number}})
|
385 | ###
|
386 | ```
|
387 | ```coffee
|
388 | first: (fetchReference=yes) ->
|
389 | ###
|
390 | Fetch the first document by this query.
|
391 | @param fetchReference {bool}
|
392 | @returns {promise<Document|null>}
|
393 | ###
|
394 | ```
|
395 | ```
|
396 | hasAny: ->
|
397 | ###
|
398 | Are there any documents match with the query?
|
399 | @returns {promise<bool>}
|
400 | ###
|
401 | ```
|
402 | ```coffee
|
403 | count: ->
|
404 | ###
|
405 | Count documents by the query.
|
406 | @returns {promise<number>}
|
407 | ###
|
408 | ```
|
409 | ```coffee
|
410 | sum: (field) ->
|
411 | ###
|
412 | Sum the field of documents by the query.
|
413 | https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-sum-aggregation.html
|
414 | @param field {Property|string} The property name of the document.
|
415 | @returns {promise<number>}
|
416 | ###
|
417 | ```
|
418 |
|
419 |
|
420 |
|
421 | ## Example
|
422 | ```sql
|
423 | select * from "ExampleModel" where "name" = "tina"
|
424 | ```
|
425 | ```coffee
|
426 | ExampleModel.where('name', equal: 'tina').fetch().then (result) ->
|
427 | ```
|
428 |
|
429 | ---
|
430 | ```sql
|
431 | select * from "ExampleModel" where "name" = "tina" and "email" = "kelp@phate.org"
|
432 | ```
|
433 | ```coffee
|
434 | ExampleModel.where('name', equal: 'enju')
|
435 | .where('email', equal: 'kelp@phate.org')
|
436 | .fetch().then (result) ->
|
437 | ```
|
438 |
|
439 | ---
|
440 | ```sql
|
441 | select * from "ExampleModel" where "name" like "%tina%" or "email" like "%tina%"
|
442 | ```
|
443 | ```coffee
|
444 | ExampleModel.where (query) ->
|
445 | query.where('name', like: 'tina').union('email', like: 'tina')
|
446 | .fetch().then (result) ->
|
447 | ```
|
448 |
|
449 | ---
|
450 | ```sql
|
451 | select * from "ExampleModel" where "category" = 1 or "category" = 3
|
452 | order by "created_at" limit 20 offset 20
|
453 | ```
|
454 | ```coffee
|
455 | ExampleModel.where('category', contains: [1, 3])
|
456 | .orderBy('created_at')
|
457 | .fetch(20, 20).then (result) ->
|
458 | ```
|
459 |
|
460 | ---
|
461 | Fetch the first item.
|
462 | ```sql
|
463 | select * from "ExampleModel" where "age" >= 10
|
464 | order by "created_at" desc limit 1
|
465 | ```
|
466 | ```coffee
|
467 | ExampleModel.where('age', '>=': 10)
|
468 | .orderBy('created_at', yes).first().then (model) ->
|
469 | ```
|
470 |
|
471 | ---
|
472 | Count items.
|
473 | ```sql
|
474 | select count(*) from "ExampleModel" where "age" < 10
|
475 | ```
|
476 | ```python
|
477 | ExampleModel.where('age', less: 10).count().then (result) ->
|
478 | ```
|
479 |
|