├── .codeclimate.yml
├── .gitignore
├── .travis.yml
├── README.md
├── build
├── npdynamodb.js
└── npdynamodb.min.js
├── docs
├── migration_apis.md
├── orm_apis.md
└── query_builder_apis.md
├── index.js
├── lib
├── bin
│ └── npd
├── dialects
│ └── 2012-08-10
│ │ ├── api.js
│ │ ├── feature.js
│ │ └── schema.js
├── interface.js
├── migrate
│ └── migrator.js
├── npdynamodb.js
├── orm
│ ├── collection.js
│ ├── index.js
│ ├── model.js
│ └── promise_runner.js
├── promisify.js
├── query_builder.js
├── schema
│ ├── builder.js
│ ├── chainable.js
│ └── types.js
├── templates
│ ├── generator.stub
│ ├── index.js
│ ├── npd_aa.stub
│ └── npdfile.stub
└── utils.js
├── package.json
├── scripts
├── build.sh
└── webpack.config.js
└── test
├── data
├── complex_table_seed.js
└── test_tables.js
├── dynamodb_2012_08_10.js
├── migrations
├── 20150404071625_create_test_table1.js
├── 20150404071722_create_test_table2.js
├── 20150819155839_alter_test_table2.js
├── 20150819155840_alter_test_table1.js
└── 20150819155841_alter_test_table1_2.js
├── migrator_spec.js
├── orm_spec.js
├── query_builder_read_spec.js
├── query_builder_write_spec.js
└── schema_spec.js
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | exclude_paths:
2 | - build/*
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | node_modules/
3 | .DS_Store
4 | npdfile.js
5 | examples
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | language: node_js
3 | node_js:
4 | - "4"
5 | - "5"
6 | - "6"
7 | - "7"
8 | - "8"
9 | - "node"
10 | - "lts/*"
11 |
12 | install:
13 | - docker run -d -p 8000:8000 tray/dynamodb-local -inMemory -port 8000
14 | - npm i -g mocha
15 | - npm i
16 |
17 | script: npm test
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # npdynamodb [](http://badge.fury.io/js/npdynamodb) [](https://codeclimate.com/github/noppoMan/npdynamodb) [](https://travis-ci.org/noppoMan/npdynamodb)
2 | A Node.js Simple Query Builder and ORM for AWS DynamoDB.
3 |
4 | ## Motivation
5 | When I visited [here ](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#query-property
6 | ) for the first time, I closed it in a moment.
7 | Because it is too long and hard to see to understand.
8 | So I decided to make client to handle DynamoDB more easier and it doesn't take waste of time to read documentation for it.
9 |
10 | ## Services that are used in Production
11 | [
](https://chatca.st)
12 |
13 |
14 | ## Supported DynamoDB Api Versions
15 | * 2012-08-10
16 |
17 | ## Installation
18 | ```sh
19 | npm install npdynamodb
20 | ```
21 |
22 | ## Why is the Pure AWS-SDK in Node.js NOT good?
23 |
24 | Parameters are like Chant of the magic.
25 | [http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html)
26 |
27 | ## Overview
28 | Npdynamodb has modern interfaces to handle AWS DynamoDB.
29 | We release you redundancy codes and see simple syntax.
30 | Of course, will not see callback hell!
31 |
32 | Npdynamodb has the following functions
33 | * [Simple QueryBuilder](https://github.com/noppoMan/npdynamodb/blob/master/README.md#usage-of-querybuilder)
34 | * [Light ORM(Active Record Model Like)](https://github.com/noppoMan/npdynamodb/blob/master/README.md#usage-of-orm)
35 | * [DynamoDB Migrator](https://github.com/noppoMan/npdynamodb/blob/master/README.md#migration)
36 | * [Command Line Interface](https://github.com/noppoMan/npdynamodb/blob/master/README.md#command-line-interfaces)
37 |
38 | ### List of npdynamodb apis
39 | * [QueryBuilder Apis](https://github.com/noppoMan/npdynamodb/blob/master/docs/query_builder_apis.md)
40 | * [ORM Apis](https://github.com/noppoMan/npdynamodb/blob/master/docs/orm_apis.md)
41 | * [Migration Apis](https://github.com/noppoMan/npdynamodb/blob/master/docs/migration_apis.md)
42 |
43 | ## Usage of QueryBuilder
44 |
45 | Initialization
46 | ```js
47 | var npdynamodb = require('npdynamodb');
48 | var AWS = require('aws-sdk');
49 |
50 | var dynamodb = new AWS.DynamoDB({
51 | apiVersion: '2012-08-10'
52 | });
53 |
54 | var npd = npdynamodb.createClient(dynamodb);
55 |
56 | // Or can take options
57 | var npd = npdynamodb.createClient(dynamodb, {
58 | timeout: 3000,
59 | initialize: function(){
60 | // Some Initialization here.
61 | }
62 | });
63 | ```
64 |
65 | ##### Get by hash key (getItem operation)
66 | ```js
67 | npd().table('users')
68 | .where("id", 1)
69 | .first()
70 | .then(function(data){
71 |
72 | console.log(data)
73 | // => {Item: {id: 1, name: 'Tonny'}, Count: 1, ScannedCount: 1}
74 |
75 | })
76 | .catch(function(err){
77 | console.err(err);
78 | });
79 | ```
80 |
81 | ##### Get rows with where (query operation)
82 | ```js
83 | npd().table('users')
84 | .where('name', 'tonny') //hash key
85 | .then(function(data){
86 |
87 | console.log(data)
88 | // => {Items: [{id: 1, name: 'Tonny'}], Count: 1, ScannedCount: 1}
89 |
90 | })
91 | .catch(function(err){
92 | console.err(err);
93 | });
94 | ```
95 |
96 | ##### Get multiple rows with where, filter and descending order
97 | ```js
98 | npd().table('chats')
99 | .where('room_id', 'room1') // hash key
100 | .where('timestamp', '>', 1429454911) // range key
101 | .filter('user_name', 'tonny') // non index key
102 | .desc()
103 | .then(function(data){
104 | console.log(data);
105 | })
106 | .catch(function(err){
107 | console.err(err);
108 | });
109 | ```
110 |
111 | ##### whereIn
112 | whereIn call batchGetItem instead of query operation.
113 |
114 | ###### Single Key Usage
115 | ```js
116 | npd().table('chats')
117 | .whereIn('room_id', ['room1', 'room2', 'room3'])
118 | .then(function(data){
119 | console.log(data);
120 | })
121 | .catch(function(err){
122 | console.err(err);
123 | });
124 | ```
125 |
126 | ###### Multiple Kyes Usage
127 | ```js
128 | npd().table('chats')
129 | .whereIn(['room_id', 'timestamp'], [['room1', 1429454911], ['room2', 1429454912], ['room3', 1429454913]])
130 | .then(function(data){
131 | console.log(data);
132 | })
133 | .catch(function(err){
134 | console.err(err);
135 | });
136 | ```
137 |
138 | ##### Limit and offset
139 | ```js
140 | npd().table('chats')
141 | .where('room_id', 'room1')
142 | .limit(10)
143 | .offset(ExclusiveStartKey)
144 | .then(function(data){
145 | console.log(data);
146 | })
147 | .catch(function(err){
148 | console.err(err);
149 | });
150 | ```
151 |
152 | ##### Count
153 | ```js
154 | npd().table('chats')
155 | .where('room_id', 'room1')
156 | .count()
157 | .then(function(data){
158 | console.log(data.Count);
159 | })
160 | .catch(function(err){
161 | console.err(err);
162 | });
163 | ```
164 |
165 | ##### Extra options
166 | You can set extra options in callback of `feature` method. All options are transformed from property to method, But its name (camelized) and arguments are same as pure AWS-SDK for node.js.
167 |
168 | ```js
169 | npd().table('users')
170 | .where('name', 'tonny')
171 | .feature(function(f){ // f is raw feature object.
172 | f.consistentRead(true);
173 | f.returnConsumedCapacity('TOTAL');
174 | })
175 | .then(function(data){
176 | console.log(data);
177 | })
178 | .catch(function(err){
179 | console.err(err);
180 | });
181 | ```
182 |
183 | ##### create (Make Overwrite all of values, if key[s] have already existed.)
184 | ```js
185 | npd().table('users')
186 | .create({ // Also can save collection.
187 | id: 2,
188 | name: 'rhodes',
189 | company: {
190 | name: 'Stark Industry',
191 | tel: '123456789',
192 | zip: '123456789',
193 | address: 'foo-bar-123'
194 | }
195 | })
196 | .then(function(data){
197 | console.log(data);
198 | })
199 | .catch(function(err){
200 | console.err(err);
201 | });
202 | ```
203 |
204 | ##### Update
205 | ```js
206 | npd().table('users')
207 | .set("company", "PUT", {
208 | name: 'moved company',
209 | tel: '123-456-789',
210 | zip: '123-456-789',
211 | address: 'foo-bar-456'
212 | })
213 | .set("suite_color", "ADD", 1)
214 | .update()
215 | .then(function(data){
216 | console.log(data);
217 | })
218 | .catch(function(err){
219 | console.err(err);
220 | });
221 | ```
222 |
223 |
224 | ##### Update with expressions
225 | ```js
226 | npd().table('users')
227 | .feature(function(f){
228 | f.updateExpression('SET #gt = if_not_exists(#gt, :one)');
229 |
230 | f.expressionAttributeNames({
231 | '#gt': 'gender_type'
232 | });
233 |
234 | f.expressionAttributeValues({
235 | ':one': 1
236 | });
237 |
238 | f.returnValues('UPDATED_NEW');
239 | })
240 | .update()
241 | .then(function(data){
242 | console.log(data);
243 | })
244 | .catch(function(err){
245 | console.err(err);
246 | });
247 |
248 | ```
249 |
250 | ## Usage of ORM
251 |
252 | Initialization
253 | ```js
254 | var npdynamodb = require('npdynamodb');
255 | var AWS = require('aws-sdk');
256 |
257 | var npd = npdynamodb.createClient(new AWS.DynamoDB({
258 | apiVersion: '2012-08-10'
259 | }));
260 |
261 | var Chat = npdynamodb.define('chats', {
262 | npdynamodb: npd,
263 |
264 | hashKey: 'id',
265 |
266 | rangeKey: 'timestamp'
267 | });
268 | ```
269 |
270 | ##### Fast get with hash_key
271 | ```js
272 | Chat.find(1).then(function(chat){ // where('id', '=', 1)
273 | // Get value of id key
274 | console.log(chat.get('id'));
275 |
276 | // Get attribute keys
277 | console.log(chat.keys());
278 |
279 | // Get attribute values
280 | console.log(chat.values());
281 |
282 | // Pick specified key and value pairs
283 | console.log(chat.pick('chat_id', 'timestamp'));
284 |
285 | // Transform as json string.
286 | console.log(chat.toJson());
287 | });
288 | ```
289 |
290 | ##### fetch with multiple conditions
291 | ```js
292 | Chat.where('id', 1)
293 | // complex conditions
294 | .query(function(qb){
295 | qb.whereBeteen('timestamp', 1429212102, 1429212202);
296 | })
297 | .fetch()
298 | .then(function(data){
299 |
300 | // Check query result is empty?
301 | console.log(data.isEmpty());
302 | // => false
303 |
304 | // Get First Item
305 | console.log(data.first().get('id'));
306 | // => 1
307 |
308 | // Get Last Item
309 | console.log(data.last().get('id'));
310 | // => 1
311 |
312 | // Seequence(Also supported map, find, etc....)
313 | data.each(function(item){
314 | console.log(item.get('id'));
315 | });
316 |
317 | // Pluck specific column values.
318 | console.log(data.pluck('id'));
319 |
320 | // Get as object.
321 | console.log(data.toArray());
322 | // => [{id: 1, name: 'tonny', company: {....}}]
323 |
324 | });
325 | ```
326 |
327 | ##### Save
328 | ```js
329 | // As Static
330 | Chat.save({
331 | room_id: 'room1',
332 | ....
333 | })
334 | .then(function(chat){
335 | console.log(chat.get('room_id'));
336 | });
337 |
338 | // As Instance
339 | var chat = new Chat({
340 | room_id: 'room1',
341 | user_id: 1
342 | });
343 | chat.set('message', 'This is a message.');
344 |
345 | chat.save()
346 | .then(function(chat){
347 | console.log(chat.get('room_id'));
348 | });
349 | ```
350 |
351 | ##### Destroy
352 | ```js
353 | chat.destroy()
354 | .then(function(data){
355 | console.log(data);
356 | });
357 | ```
358 |
359 | ##### Custom Methods and Properties
360 | ```js
361 | var Chat = npdynamodb.define('chats', {
362 | npdynamodb: npd,
363 |
364 | hashKey: 'id',
365 |
366 | rangeKey: 'timestamp',
367 |
368 | customProtoTypeConstant: 1,
369 |
370 | customProtoTypeMethod: function(){
371 | return this.get('id') === 1;
372 | }
373 |
374 | },
375 |
376 | {
377 | customStaticConstant: 1,
378 |
379 | customStaticMethod: function(){
380 | return this.where('room_id', 'room1')
381 | .query(function(qb){
382 | qb.filter('timestamp', '>', 1429212102);
383 | })
384 | .fetch();
385 | }
386 | });
387 |
388 | // prototype
389 | Chat.find(1).then(function(chat){
390 | console.log(chat.customProtoTypeConstant);
391 | console.log(chat.customeProtoTypeMethod());
392 | });
393 |
394 |
395 | // static
396 | console.log(Chat.customStaticConstant);
397 |
398 | Chat.customStaticMethod().then(function(data){
399 | console.log(data);
400 | });
401 | ```
402 |
403 | ## Migration
404 | We support schema migration for Dynamodb.
405 |
406 | ##### First, initialize your project to run migration.
407 | ```sh
408 | npm install -g npdynamodb
409 | # cd /path/to/your/project
410 | npd init
411 | # created npdfile.js
412 | ```
413 |
414 | ##### npdfile.js
415 | ```js
416 | 'use strict';
417 |
418 | var AWS = require('aws-sdk');
419 |
420 | var dynamodb = new AWS.DynamoDB({
421 | apiVersion: '2012-08-10',
422 | accessKeyId: "AWS_KEY",
423 | secretAccessKey: "AWS_SECRET",
424 | region: "ap-northeast-1"
425 | });
426 |
427 | module.exports = {
428 |
429 | // Specify migration file path. Default is `./migrations`
430 | // migration: {
431 | // migrationFilePath: './npdynamodb_migrations'
432 | // },
433 |
434 | development: {
435 | dynamoClient: dynamodb,
436 | migrations: {
437 | ProvisionedThroughput: [10, 10],
438 | tableName: 'npd_migrations'
439 | }
440 | },
441 |
442 | staging: {
443 | dynamoClient: dynamodb,
444 | migrations: {
445 | ProvisionedThroughput: [10, 10],
446 | tableName: 'npd_migrations'
447 | }
448 | },
449 |
450 | production: {
451 | dynamoClient: dynamodb,
452 | migrations: {
453 | ProvisionedThroughput: [10, 10],
454 | tableName: 'npd_migrations'
455 | }
456 | }
457 | };
458 | ```
459 |
460 | ##### Generate migration file.
461 | ```sh
462 | npd migrate:generate create_users
463 | # => /migrations/20150406083039_create_users.js
464 | ```
465 |
466 | ##### Edit migration file
467 | ```js
468 | exports.up = function(migrator){
469 | return migrator().createTable('chats', function(t){
470 | t.string('room_id').hashKey();
471 | t.number('timestamp').rangeKey();
472 | t.provisionedThroughput(100, 100); // read, write
473 |
474 | t.globalSecondaryIndex('indexName1', function(t){
475 | t.string('user_id').hashKey();
476 | t.provisionedThroughput(100, 100); // read, write
477 | t.projectionTypeAll(); //default is NONE
478 | });
479 |
480 | t.localSecondaryIndex('indexName2', function(t){
481 | t.string('room_id').hashKey();
482 | t.number('user_id').rangeKey();
483 | t.projectionTypeAll(); //default is NONE
484 | });
485 | });
486 | };
487 |
488 | exports.down = function(migrator){
489 | return migrator().deleteTable('chats');
490 | };
491 | ```
492 |
493 | ##### UpdateTable Usage
494 | ```js
495 | exports.up = function(migrator, config){
496 | return migrator().updateTable('test_table1', function(t){
497 | t.globalSecondaryIndexUpdates(function(t){
498 |
499 | t.create('indexName3', function(t){
500 | t.string('hash_key2').hashKey();
501 | t.provisionedThroughput(100, 100);
502 | t.projectionTypeAll();
503 | });
504 |
505 | t.delete('indexName2');
506 |
507 | t.update('indexName1', function(t){
508 | t.provisionedThroughput(150, 100);
509 | });
510 |
511 | t.provisionedThroughput(200, 200);
512 |
513 | });
514 | }).then(function(){
515 | // wait until tables state will be ACTIVE.
516 | return migrator().waitUntilTableActivate('test_table1');
517 | });
518 | };
519 | ```
520 |
521 | ##### Run latest migration.
522 | ```sh
523 | npd migrate:run
524 | ```
525 |
526 | ##### Rollback latest migration.
527 | ```sh
528 | npd migrate:rollback
529 | ```
530 |
531 | ## Command Line Interfaces
532 | #### required global install and type `npd`
533 | ### Commands
534 | * `init`: Create a fresh npdfile.js.
535 | * `migrate:generate ` Create a named migration file.
536 | * `migrate:run` Run all migrations that have not yet been run.
537 | * `migrate:rollback` Rollback the last set of migrations performed.
538 | * `listTables` List existing tables.
539 | * `dump `: Dump amount of records in specified table to stdout.
540 | * `desc `: Show result of the describe operation
541 | * `get [rangeKey]`: Show results of the query operation by given conditions.
542 | * `dropTable `: Drop the specified table.
543 |
544 | ### Global Options
545 | * `-h`
546 | * `-V`
547 | * `--env`
548 |
549 | ## How to test?
550 | ```sh
551 | npm test
552 | ```
553 |
554 | ## QueryBuilder Callbacks
555 | You can be hooked several events and their can be taken promise.
556 |
557 | Mechanism of Callbacks and Events
558 | ```
559 | operation called.
560 | ↓
561 | callbacks: beforeQuery
562 | ↓
563 | events: beforeQuery
564 | ↓
565 | Sending Request to Dynamodb
566 | ↓
567 | Getting Response from Dynamodb
568 | ↓
569 | callbacks: afterQuery
570 | ↓
571 | events: afterQuery
572 | ```
573 |
574 | ```js
575 | // Register callbacks globally
576 | var npd = npdynamodb.createClient(dynamodb, {
577 | initialize: function(){
578 | this.callbacks('beforeQuery', function(){
579 | if(this._fature.params['hash_key'] !== 1) {
580 | return Promise.reject(new Error('invalid value'));
581 | }
582 | });
583 |
584 | this.callbacks('afterQuery', function(result){
585 | return npd().table('related').create({
586 | foo_id: result.Items[0]['hash_key'],
587 | bar: 'string value'
588 | });
589 | });
590 | }
591 | });
592 |
593 | // Register callbacks at only this time.
594 | npd().table('foo').callbacks('beforeQuery', Func).create({
595 | hoo: 'hoo',
596 | bar: 'bar'
597 | });
598 | ```
599 |
600 | ## Plugin and Extending
601 | Npdynamodb can be extended via plugins.
602 |
603 | ```js
604 | npdynamodb.plugin(function(Klass){
605 |
606 | // Extend QueryBuilder
607 | Klass.QueryBuilder.extend({
608 | protoFn: function(){
609 | console.log('foo');
610 | }
611 | },
612 | {
613 | staticFn: function(){
614 | console.log('bar');
615 | }
616 | });
617 |
618 | // Extend Orm Collection
619 | Klass.Collection.extend({
620 | protoFn: function(){
621 | console.log('foo');
622 | }
623 | },
624 | {
625 | staticFn: function(){
626 | console.log('bar');
627 | }
628 | });
629 |
630 | // Extend Orm Model
631 | Klass.Model.extend({
632 | protoFn: function(){
633 | console.log('foo');
634 | }
635 | },
636 | {
637 | staticFn: function(){
638 | console.log('bar');
639 | }
640 | });
641 |
642 | });
643 | ```
644 |
645 | ### Available Plugins
646 | * [npdynamodb-typecast](https://github.com/noppoMan/npdynamodb-typecast) For casting hash and range key to actual attribution type
647 |
648 | ## Browser Support
649 | Npdynamodb can be built using browserify or webpack, and pre-built or pre-built with uglified version can be found in the build directory.
650 |
651 | ### For Browserify or Webpack
652 | ```js
653 | var AWS = require('aws-sdk');
654 | var npdynamodb = require('npdynamodb/build/npdynamodb');
655 |
656 | var dynamodb = new AWS.DynamoDB({
657 | apiVersion: '2012-08-10',
658 | accessKeyId: "here is key",
659 | secretAccessKey: "here is secret key",
660 | region: "ap-northeast-1",
661 | sslEnabled: true,
662 | });
663 |
664 | var npd = npdynamodb.createClient(dynamodb);
665 | npd().table('table_name').where('id', 1).then(function(data){
666 | console.log(data);
667 | });
668 | ```
669 |
670 | ### For HTML
671 | ```html
672 |
673 |
674 |
675 |
676 |
690 | ```
691 |
692 | ## Upgrading and Release Note
693 | #### Upgrading 0.1x -> 0.2x
694 |
695 | ##### QueryBuilder
696 | There should be a minor change for QueryBuilder. 0.2x QueryBuilder can take options as second argument of createClient.
697 | * 0.2.0: `timeout` option supported.
698 | * 0.2.6: `initialize` option and [callbacks](#querybuilder-callbacks) supported.
699 | * 0.2.7: [whereIn](wherein) method supported.
700 |
701 | ##### ORM
702 | There should be a major change for ORM. 0.2x ORM constructor need to pass the npdynamodb instance instead of pure dynamodb instance.
703 | * 0.2.7: Supported to parse `whereIn` results.
704 |
705 | ## License
706 |
707 | (The MIT License)
708 |
709 | Copyright (c) 2015 Yuki Takei(Noppoman) yuki@miketokyo.com
710 |
711 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
712 |
713 | The above copyright notice and marthis permission notice shall be included in all copies or substantial portions of the Software.
714 |
715 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
716 |
--------------------------------------------------------------------------------
/build/npdynamodb.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("lodash"),require("aws-sdk"),require("bluebird")):"function"==typeof define&&define.amd?define(["lodash","aws-sdk","bluebird"],e):"object"==typeof exports?exports.npdynamodb=e(require("lodash"),require("aws-sdk"),require("bluebird")):t.npdynamodb=e(t._,t.AWS,t.Promise)}(this,function(t,e,n){return function(t){function e(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return t[r].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";var r=n(2);"object"==typeof window&&(window.npdynamodb=e,window.DynamoDBDatatype=n(3).DynamoDBDatatype,window.DynamoDBFormatter=n(5).DynamoDBFormatter),e.version=n(6).version,e.createClient=n(7),e.define=n(27),e.Migrator=n(1);var i=n(8),o=n(29),s=n(28);[i,o,s].forEach(function(t){t.extend=function(e,n){r.extend(t.prototype,e||{}),r.extend(t,n||{})}}),e.plugin=function(t){if("function"!=typeof t)throw new Error("The plugin must be function.");t({QueryBuilder:i,Collection:o,Model:s})},e.Collection=n(29),e.Model=n(28)},function(t,e){},function(e,n){e.exports=t},function(t,e,n){"use strict";function r(){function t(t){var e=typeof t;return"number"===e||"string"===e||"boolean"===e||t instanceof Uint8Array&&c.util.isBrowser()||t instanceof c.util.Buffer||null===t}function e(t){return"SS"===t.datatype||"NS"===t.datatype||"BS"===t.datatype}function r(t){return Array.isArray(t)||"object"==typeof t}function i(t,e){return"NS"===t?e.map(function(t){return t.toString()}):e}function o(t){var e={},n={},r="M";Array.isArray(t)&&(n=[],r="L");for(var i in t)n[i]=this.formatDataType(t[i]);return e[r]=n,e}function s(t){if(null==t)return{NULL:!0};var e=typeof t;if("string"===e)return{S:t};if("number"===e)return{N:String(t)};if("boolean"===e)return{BOOL:t};if(t instanceof c.util.Buffer)return{B:t};if(t instanceof Uint8Array){if(c.util.isBrowser())return{B:t};throw new Error(f)}throw new Error(p)}function a(t){if("string"!=typeof t)throw new Error(d);if(c.util.isBrowser()){for(var e=t.length,n=new Uint8Array(new ArrayBuffer(e)),r=0;e>r;r++)n[r]=t.charCodeAt(r);return n}return c.util.Buffer(t)}function u(t){if(!(t instanceof c.util.Buffer||t instanceof Uint8Array))throw new Error(h);return c.util.isBrowser()?String.fromCharCode.apply(null,t):t.toString("utf-8").valueOf()}var c="undefined"==typeof window?n(4):window.AWS,f="Uint8Array can only be used for Binary in Browser.",p="Unrecognized Scalar Datatype to be formatted.",l="Unrecognized Datatype to be formatted.",h="Need to pass in Buffer or Uint8Array. ",d="Need to pass in string primitive to be converted to binary.";this.formatDataType=function(n){if(t(n))return s(n);if(e(n))return n.format();if(r(n))return o.call(this,n);throw new Error(l)},this.strToBin=function(t){return a.call(this,t)},this.binToStr=function(t){return u.call(this,t)},this.createSet=function(t,e){if("N"!==e&&"S"!==e&&"B"!==e)throw new Error(e+" is an invalid type for Set");var n=function(t,e){if(this.datatype=e+"S",this.contents={},this.add=function(t){if("SS"===this.datatype&&"string"==typeof t)this.contents[t]=t;else if("NS"===this.datatype&&"number"==typeof t)this.contents[t]=t;else if("BS"===this.datatype&&t instanceof c.util.Buffer)this.contents[u(t)]=t;else{if(!("BS"===this.datatype&&t instanceof Uint8Array))throw new Error("Inconsistent in this "+e+" Set");if(!c.util.isBrowser())throw new Error(f);this.contents[u(t)]=t}},this.contains=function(t){var e=t;return(t instanceof c.util.Buffer||t instanceof Uint8Array)&&(e=u(t)),void 0===this.contents[e]?!1:!0},this.remove=function(t){var e=t;(t instanceof c.util.Buffer||t instanceof Uint8Array)&&(e=u(t)),delete this.contents[e]},this.toArray=function(){var t=Object.keys(this.contents),e=[];for(var n in t){var r=t[n];this.contents.hasOwnProperty(r)&&e.push(this.contents[r])}return e},this.format=function(){var t=this.toArray(),e={};return e[this.datatype]=i(this.datatype,t),e},t)for(var n in t)this.add(t[n])};return new n(t,e)},this.formatWireType=function(t,e){switch(t){case"S":case"B":case"BOOL":return e;case"N":return Number(e);case"NULL":return null;case"L":for(var n=0;n (http://miketokyo.com)",license:"MIT",bugs:{url:"https://github.com/noppoMan/npdynamodb/issues"},homepage:"https://github.com/noppoMan/npdynamodb",dependencies:{bluebird:"^2.9.24",chalk:"^1.0.0",commander:"^2.7.1","dynamodb-doc":"^1.0.0",glob:"^5.0.3",interpret:"^0.5.2",liftoff:"^2.0.3",lodash:"^3.5.0",minimist:"^1.1.1",readline:"0.0.7",v8flags:"^2.0.3"},devDependencies:{"aws-sdk":"^2.1.18",chai:"^2.2.0"},browser:{"./lib/migrate/migrator.js":!1,"./lib/dialects/2012-08-10/schema.js":!1,"aws-sdk":!1}}},function(t,e,n){"use strict";function r(t,e){var n=new i(t,e);return n}var i=n(8),o=n(25),s=n(18),a={};t.exports=function(t,e){var i=t.config.apiVersion,u=n(26)("./"+i+"/api");a[i]||(a[i]=o(t,u.originalApis));var c={dynamodb:"function"==typeof t.Condition?t:new s.DynamoDB(t),promisifidRawClient:a[i]};return function(){return r(c,e)}}},function(t,e,n){"use strict";function r(t,e){u.call(this);var n=new f[t.dynamodb.config.apiVersion](t),r=e||{};r.initialize;this.apiVersion=n.client.config.apiVersion,this._feature=n,this._options=s.omit(r,"initialize"),this._initializer=r.initialize,this._callbacks={},"function"==typeof this._initializer&&this._initializer.bind(this)()}function i(t,e){return(t||[]).map(function(t){return t.bind(this)(e)}.bind(this))}var o=n(9),s=n(2),a=n(10),u=n(11).EventEmitter,c=n(12),f=(n(16),{"2012-08-10":n(17)});t.exports=r,c.inherits(r,u),o.forEach(function(t){r.prototype[t]=function(){return this._feature[t].apply(this._feature,s.toArray(arguments)),this}}),r.prototype.freshBuilder=function(){return new r({dynamodb:this._feature.client,promisifidRawClient:this._feature.promisifidRawClient},s.clone(s.extend(this._options,{initialize:this._initializer})))},r.prototype.tableName=function(){return this._feature.conditions.TableName},r.prototype.normalizationRawResponse=function(t){return this._feature.normalizationRawResponse(t)},r.prototype.feature=function(t){return t(this._feature),this},r.prototype.rawClient=function(t){return this._feature.promisifidRawClient},r.prototype.callbacks=function(t,e){return this._callbacks[t]||(this._callbacks[t]=[]),this._callbacks[t].push(e),this},s.each(["then"],function(t){r.prototype[t]=function(t){var e,n=this,r=n._feature,o=this._callbacks;return a.all(i.bind(n)(o.beforeQuery)).then(function(){var t=r.buildQuery();return n.emit("beforeQuery",t.params),new a(function(i,o){var s=r.client[t.method](t.params,function(t,n){return e&&(clearTimeout(e),e=null),t?o(t):void i(n)});null!==n._options.timeout&&(e=setTimeout(function(){s.abort(),o(new Error("The connection has timed out."))},n._options.timeout||5e3))})}).then(function(t){return a.all(i.bind(n)(o.afterQuery,t)).then(function(){return n.emit("afterQuery",t),t})}).then(function(e){return t.bind(n)(e)})}})},function(t,e){"use strict";t.exports=["select","table","count","all","where","first","whereIn","whereBetween","whereBeginsWith","filterBetween","filterBeginsWith","filter","filterIn","filterNull","filterNotNull","filterContains","filterNotContains","limit","offset","desc","asc","create","update","set","delete","showTables","indexName","describe","createTable","deleteTable"]},function(t,e){t.exports=n},function(t,e){function n(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function r(t){return"function"==typeof t}function i(t){return"number"==typeof t}function o(t){return"object"==typeof t&&null!==t}function s(t){return void 0===t}t.exports=n,n.EventEmitter=n,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.prototype.setMaxListeners=function(t){if(!i(t)||0>t||isNaN(t))throw TypeError("n must be a positive number");return this._maxListeners=t,this},n.prototype.emit=function(t){var e,n,i,a,u,c;if(this._events||(this._events={}),"error"===t&&(!this._events.error||o(this._events.error)&&!this._events.error.length)){if(e=arguments[1],e instanceof Error)throw e;throw TypeError('Uncaught, unspecified "error" event.')}if(n=this._events[t],s(n))return!1;if(r(n))switch(arguments.length){case 1:n.call(this);break;case 2:n.call(this,arguments[1]);break;case 3:n.call(this,arguments[1],arguments[2]);break;default:for(i=arguments.length,a=new Array(i-1),u=1;i>u;u++)a[u-1]=arguments[u];n.apply(this,a)}else if(o(n)){for(i=arguments.length,a=new Array(i-1),u=1;i>u;u++)a[u-1]=arguments[u];for(c=n.slice(),i=c.length,u=0;i>u;u++)c[u].apply(this,a)}return!0},n.prototype.addListener=function(t,e){var i;if(!r(e))throw TypeError("listener must be a function");if(this._events||(this._events={}),this._events.newListener&&this.emit("newListener",t,r(e.listener)?e.listener:e),this._events[t]?o(this._events[t])?this._events[t].push(e):this._events[t]=[this._events[t],e]:this._events[t]=e,o(this._events[t])&&!this._events[t].warned){var i;i=s(this._maxListeners)?n.defaultMaxListeners:this._maxListeners,i&&i>0&&this._events[t].length>i&&(this._events[t].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[t].length),"function"==typeof console.trace&&console.trace())}return this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(t,e){function n(){this.removeListener(t,n),i||(i=!0,e.apply(this,arguments))}if(!r(e))throw TypeError("listener must be a function");var i=!1;return n.listener=e,this.on(t,n),this},n.prototype.removeListener=function(t,e){var n,i,s,a;if(!r(e))throw TypeError("listener must be a function");if(!this._events||!this._events[t])return this;if(n=this._events[t],s=n.length,i=-1,n===e||r(n.listener)&&n.listener===e)delete this._events[t],this._events.removeListener&&this.emit("removeListener",t,e);else if(o(n)){for(a=s;a-->0;)if(n[a]===e||n[a].listener&&n[a].listener===e){i=a;break}if(0>i)return this;1===n.length?(n.length=0,delete this._events[t]):n.splice(i,1),this._events.removeListener&&this.emit("removeListener",t,e)}return this},n.prototype.removeAllListeners=function(t){var e,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[t]&&delete this._events[t],this;if(0===arguments.length){for(e in this._events)"removeListener"!==e&&this.removeAllListeners(e);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[t],r(n))this.removeListener(t,n);else for(;n.length;)this.removeListener(t,n[n.length-1]);return delete this._events[t],this},n.prototype.listeners=function(t){var e;return e=this._events&&this._events[t]?r(this._events[t])?[this._events[t]]:this._events[t].slice():[]},n.listenerCount=function(t,e){var n;return n=t._events&&t._events[e]?r(t._events[e])?1:t._events[e].length:0}},function(t,e,n){(function(t,r){function i(t,n){var r={seen:[],stylize:s};return arguments.length>=3&&(r.depth=arguments[2]),arguments.length>=4&&(r.colors=arguments[3]),y(n)?r.showHidden=n:n&&e._extend(r,n),_(r.showHidden)&&(r.showHidden=!1),_(r.depth)&&(r.depth=2),_(r.colors)&&(r.colors=!1),_(r.customInspect)&&(r.customInspect=!0),r.colors&&(r.stylize=o),u(r,t,r.depth)}function o(t,e){var n=i.styles[e];return n?"["+i.colors[n][0]+"m"+t+"["+i.colors[n][1]+"m":t}function s(t,e){return t}function a(t){var e={};return t.forEach(function(t,n){e[t]=!0}),e}function u(t,n,r){if(t.customInspect&&n&&A(n.inspect)&&n.inspect!==e.inspect&&(!n.constructor||n.constructor.prototype!==n)){var i=n.inspect(r,t);return g(i)||(i=u(t,i,r)),i}var o=c(t,n);if(o)return o;var s=Object.keys(n),y=a(s);if(t.showHidden&&(s=Object.getOwnPropertyNames(n)),T(n)&&(s.indexOf("message")>=0||s.indexOf("description")>=0))return f(n);if(0===s.length){if(A(n)){var m=n.name?": "+n.name:"";return t.stylize("[Function"+m+"]","special")}if(x(n))return t.stylize(RegExp.prototype.toString.call(n),"regexp");if(E(n))return t.stylize(Date.prototype.toString.call(n),"date");if(T(n))return f(n)}var v="",b=!1,w=["{","}"];if(d(n)&&(b=!0,w=["[","]"]),A(n)){var _=n.name?": "+n.name:"";v=" [Function"+_+"]"}if(x(n)&&(v=" "+RegExp.prototype.toString.call(n)),E(n)&&(v=" "+Date.prototype.toUTCString.call(n)),T(n)&&(v=" "+f(n)),0===s.length&&(!b||0==n.length))return w[0]+v+w[1];if(0>r)return x(n)?t.stylize(RegExp.prototype.toString.call(n),"regexp"):t.stylize("[Object]","special");t.seen.push(n);var D;return D=b?p(t,n,r,y,s):s.map(function(e){return l(t,n,r,y,e,b)}),t.seen.pop(),h(D,v,w)}function c(t,e){if(_(e))return t.stylize("undefined","undefined");if(g(e)){var n="'"+JSON.stringify(e).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return t.stylize(n,"string")}return b(e)?t.stylize(""+e,"number"):y(e)?t.stylize(""+e,"boolean"):m(e)?t.stylize("null","null"):void 0}function f(t){return"["+Error.prototype.toString.call(t)+"]"}function p(t,e,n,r,i){for(var o=[],s=0,a=e.length;a>s;++s)N(e,String(s))?o.push(l(t,e,n,r,String(s),!0)):o.push("");return i.forEach(function(i){i.match(/^\d+$/)||o.push(l(t,e,n,r,i,!0))}),o}function l(t,e,n,r,i,o){var s,a,c;if(c=Object.getOwnPropertyDescriptor(e,i)||{value:e[i]},c.get?a=c.set?t.stylize("[Getter/Setter]","special"):t.stylize("[Getter]","special"):c.set&&(a=t.stylize("[Setter]","special")),N(r,i)||(s="["+i+"]"),a||(t.seen.indexOf(c.value)<0?(a=m(n)?u(t,c.value,null):u(t,c.value,n-1),a.indexOf("\n")>-1&&(a=o?a.split("\n").map(function(t){return" "+t}).join("\n").substr(2):"\n"+a.split("\n").map(function(t){return" "+t}).join("\n"))):a=t.stylize("[Circular]","special")),_(s)){if(o&&i.match(/^\d+$/))return a;s=JSON.stringify(""+i),s.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(s=s.substr(1,s.length-2),s=t.stylize(s,"name")):(s=s.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),s=t.stylize(s,"string"))}return s+": "+a}function h(t,e,n){var r=0,i=t.reduce(function(t,e){return r++,e.indexOf("\n")>=0&&r++,t+e.replace(/\u001b\[\d\d?m/g,"").length+1},0);return i>60?n[0]+(""===e?"":e+"\n ")+" "+t.join(",\n ")+" "+n[1]:n[0]+e+" "+t.join(", ")+" "+n[1]}function d(t){return Array.isArray(t)}function y(t){return"boolean"==typeof t}function m(t){return null===t}function v(t){return null==t}function b(t){return"number"==typeof t}function g(t){return"string"==typeof t}function w(t){return"symbol"==typeof t}function _(t){return void 0===t}function x(t){return D(t)&&"[object RegExp]"===S(t)}function D(t){return"object"==typeof t&&null!==t}function E(t){return D(t)&&"[object Date]"===S(t)}function T(t){return D(t)&&("[object Error]"===S(t)||t instanceof Error)}function A(t){return"function"==typeof t}function O(t){return null===t||"boolean"==typeof t||"number"==typeof t||"string"==typeof t||"symbol"==typeof t||"undefined"==typeof t}function S(t){return Object.prototype.toString.call(t)}function B(t){return 10>t?"0"+t.toString(10):t.toString(10)}function C(){var t=new Date,e=[B(t.getHours()),B(t.getMinutes()),B(t.getSeconds())].join(":");return[t.getDate(),L[t.getMonth()],e].join(" ")}function N(t,e){return Object.prototype.hasOwnProperty.call(t,e)}var j=/%[sdj%]/g;e.format=function(t){if(!g(t)){for(var e=[],n=0;n=o)return t;switch(t){case"%s":return String(r[n++]);case"%d":return Number(r[n++]);case"%j":try{return JSON.stringify(r[n++])}catch(e){return"[Circular]"}default:return t}}),a=r[n];o>n;a=r[++n])s+=m(a)||!D(a)?" "+a:" "+i(a);return s},e.deprecate=function(n,i){function o(){if(!s){if(r.throwDeprecation)throw new Error(i);r.traceDeprecation?console.trace(i):console.error(i),s=!0}return n.apply(this,arguments)}if(_(t.process))return function(){return e.deprecate(n,i).apply(this,arguments)};if(r.noDeprecation===!0)return n;var s=!1;return o};var I,k={};e.debuglog=function(t){if(_(I)&&(I=r.env.NODE_DEBUG||""),t=t.toUpperCase(),!k[t])if(new RegExp("\\b"+t+"\\b","i").test(I)){var n=r.pid;k[t]=function(){var r=e.format.apply(e,arguments);console.error("%s %d: %s",t,n,r)}}else k[t]=function(){};return k[t]},e.inspect=i,i.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},i.styles={special:"cyan",number:"yellow","boolean":"yellow",undefined:"grey","null":"bold",string:"green",date:"magenta",regexp:"red"},e.isArray=d,e.isBoolean=y,e.isNull=m,e.isNullOrUndefined=v,e.isNumber=b,e.isString=g,e.isSymbol=w,e.isUndefined=_,e.isRegExp=x,e.isObject=D,e.isDate=E,e.isError=T,e.isFunction=A,e.isPrimitive=O,e.isBuffer=n(14);var L=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];e.log=function(){console.log("%s - %s",C(),e.format.apply(e,arguments))},e.inherits=n(15),e._extend=function(t,e){if(!e||!D(e))return t;for(var n=Object.keys(e),r=n.length;r--;)t[n[r]]=e[n[r]];return t}}).call(e,function(){return this}(),n(13))},function(t,e){function n(){c=!1,s.length?u=s.concat(u):f=-1,u.length&&r()}function r(){if(!c){var t=setTimeout(n);c=!0;for(var e=u.length;e;){for(s=u,u=[];++f1)for(var n=1;n0&&this.whereConditions.length>0)throw new Error("Can not specify the parameters of batchGetImte and Query operation at the same time");var r=t(l[e])||function(){return{beforeQuery:function(){},conditions:{},nextThen:e}},i=r();return i.beforeQuery.call(this),this.nextThen=i.nextThen,this.conditions=o.extend(this.conditions,i.conditions),{params:this.conditions,method:this.nextThen}},t.exports=r},function(t,e,n){"use strict";function r(t){var e="undefined"==typeof window,r=e?n(4):window.AWS,i=e?n(19).DynamoDBCondition:window.DynamoDBCondition,o=e?n(3).DynamoDBDatatype:window.DynamoDBDatatype,s=new o,a=e?n(5).DynamoDBFormatter:window.DynamoDBFormatter,u=new a,c=t||new r.DynamoDB,f=c.setupRequestListeners;return c.setupRequestListeners=function(t){f.call(this,t),t._events.validate.unshift(u.formatInput),t.on("extractData",u.formatOutput)},c.__proto__.Set=function(t,e){return s.createSet(t,e)},c.__proto__.Condition=function(t,e,n,r){return i(t,e,n,r)},c.__proto__.StrToBin=function(t){return s.strToBin(t)},c.__proto__.BinToStr=function(t){return s.binToStr(t)},c}var e=t.exports={};e.DynamoDB=r},function(t,e,n){"use strict";function r(t,e,r,i){var o="undefined"==typeof window?n(3).DynamoDBDatatype:window.DynamoDBDatatype,s=new o,a=function(t,e,n,r){this.key=t,this.operator=e,this.val1=n,this.val2=r,this.format=function(){var t={},e=[];return void 0!==this.val1&&e.push(s.formatDataType(this.val1)),void 0!==this.val2&&e.push(s.formatDataType(this.val2)),e.length>0&&(t.AttributeValueList=e),t.ComparisonOperator=this.operator,t}},u=new a(t,e,r,i);return u.prototype=Object.create(Object.prototype),u.prototype.instanceOf="DynamoDBConditionObject",u}var e=t.exports={};e.DynamoDBCondition=r},function(t,e,n){(function(e){function r(){if(this.AWS)return this.AWS.apiLoader.services.dynamodb["2012-08-10"].operations;var t="aws-sdk/apis/dynamodb-2012-08-10.min.json";try{return n(22)(t).operations}catch(r){var i=e.cwd()+"/node_modules/"+t;if(s.existsSync(i))return n(22)(i).operations;throw new TypeError("Module `aws-sdk` is required for npdynamodb")}}var i=n(2),o=n(16),s=n(21),a=r(this),u=[{origin:"createTable",transformed:"createTable"},{origin:"deleteTable",transformed:"deleteTable"},{origin:"deleteItem",transformed:"delete"},{origin:"describeTable",transformed:"describe"},{origin:"listTables",transformed:"showTables"},{origin:"putItem",transformed:"create"},{origin:"updateItem",transformed:"update"},{origin:"getItem",transformed:"first"},{origin:"query",transformed:"query"},{origin:"scan",transformed:"all"},{origin:"updateTable",transformed:"alterTable"},{origin:"waitFor",transformed:"waitFor"}],c={"=":"EQ","!=":"NE","<=":"LE","<":"LT",">=":"GE",">":"GT"};t.exports={operations:a,originalApis:i.keys(a).map(function(t){return i.camelCase(t)}).concat(["waitFor"]),transformFunctionMap:o.collectionFlatten(u.map(function(t){return o.newObject(t.transformed,t.origin)})),transformOperatorMap:c,availableOperators:i.keys(c)}}).call(e,n(13))},function(t,e){},function(t,e,n){function r(t){return n(i(t))}function i(t){return o[t]||function(){throw new Error("Cannot find module '"+t+"'.")}()}var o={"./api":20,"./api.js":20,"./feature":17,"./feature.js":17,"./schema":23,"./schema.js":24};r.keys=function(){return Object.keys(o)},r.resolve=i,t.exports=r,r.id=22},function(t,e){},function(t,e){},function(t,e,n){"use strict";var r=n(10);t.exports=function(t,e){var n={};return e.forEach(function(e){n[e]=r.promisify(t[e],t)}),n}},function(t,e,n){function r(t){return n(i(t))}function i(t){return o[t]||function(){throw new Error("Cannot find module '"+t+"'.")}()}var o={"./2012-08-10/api":20};r.keys=function(){return Object.keys(o)},r.resolve=i,t.exports=r,r.id=26},function(t,e,n){"use strict";var r=n(2),i=(n(12),n(16),n(7),n(28));t.exports=function(t,e,n){function o(n){this.tableName=t,r.extend(this,r.pick.apply(null,[e].concat(s))),this._attributes=n||{},this._builder=this.npdynamodb().table(t)}var s=["hashKey","rangeKey","npdynamodb"];return r.extend(o.prototype,r.clone(i.prototype)),r.each(r.omit.apply(null,[e].concat(s)),function(t,e){t.hasOwnProperty("bind")?o.prototype[e]=function(){return t.bind(this,r.toArray(arguments))}:o.prototype[e]=t}),r.each(["find","where","query","fetch","save"],function(t){o[t]=function(){var e=new o;return e[t].apply(e,r.toArray(arguments))}}),r.each(n,function(t,e){t.hasOwnProperty("bind")?o[e]=t.bind(o):o[e]=t}),o}},function(t,e,n){"use strict";function r(t,e){var n=this;return a(t,function(t){return n._refreshBuilder(),e(t)}).bind(this)}function i(){}var o=n(2),s=n(29),a=n(30);t.exports=i,i.prototype.where=function(){return this._builder.where.apply(this._builder,arguments),this},i.prototype.query=function(t){return"function"==typeof t?(t(this._builder),this):this._builder},i.prototype.find=function(t,e){var n=this,i=this._builder.where(this.hashKey,t);return e&&i.where(this.rangeKey,e),
2 | r.bind(this)(i.first(),function(t){return n._attributes=n._builder.normalizationRawResponse(t),n})},i.prototype.reload=function(){return this.find(this.get(this.hashKey),this.get(this.rangeKey))},i.prototype.fetch=function(){var t=this;return r.bind(this)(this._builder,function(e){var n=t._builder.normalizationRawResponse(e),r=n.map(function(e){return new t.constructor(e)});return new s(r)})},i.prototype.save=function(t){var e=this;return"object"==typeof t&&o.extend(this._attributes,t),r.bind(this)(this._builder.create(this.attributes()),function(){return e})},i.prototype.destroy=function(t){var e=this,n=this._builder.where(this.hashKey,this.get(this.hashKey));return this.rangeKey&&n.where(this.rangeKey,this.get(this.rangeKey)),r.bind(this)(n["delete"](),function(){return e._attributes={},e})},i.prototype.set=function(t,e){return this._attributes[t]=e,this},i.prototype.unset=function(t){return this._attributes[t]&&delete this._attributes[t],this},i.prototype.extend=function(t){return o.extend(this._attributes,t),this},i.prototype.get=function(t){return this._attributes[t]},i.prototype.isEmpty=function(){return o.isEmpty(this._attributes)},i.prototype.attributes=function(){return this._attributes},i.prototype.toJson=function(){return JSON.stringify(this._attributes)},i.prototype._refreshBuilder=function(){this._builder=this.npdynamodb().table(this.tableName)},o.each(["each","map","includes","contains","keys","pick","values"],function(t){i.prototype[t]=function(){var e=[this._item].concat(o.map(arguments,function(t){return t}));return o[t].apply(o,e)}})},function(t,e,n){"use strict";function r(t){this._items=t}var i=n(2);t.exports=r,r.prototype.toArray=function(){return this._items.map(function(t){return t.attributes()})},r.prototype.toJSON=function(){return JSON.stringify(this.toArray())},r.prototype.indexAt=function(t){return this._items[t]},r.prototype.at=function(t){return this.indexAt(t)},i.each(["pluck"],function(t){r.prototype[t]=function(){var e=[this.toArray()].concat(i.map(arguments,function(t){return t}));return i[t].apply(i,e)}}),i.each(["each","map","reduce","reduceRight","find","filter","where","findWhere","reject","every","some","invoke","sortBy","groupBy","indexBy","countBy","shuffle","sample","size","partition","first","last","isEmpty"],function(t){r.prototype[t]=function(){var e=[this._items].concat(i.map(arguments,function(t){return t}));return i[t].apply(i,e)}})},function(t,e,n){"use strict";var r=n(10);t.exports=function(t,e){return new r(function(n,r){return t.then(function(t){return e||(e=function(t){return t}),n(e(t))})["catch"](r)})}}])});
--------------------------------------------------------------------------------
/docs/migration_apis.md:
--------------------------------------------------------------------------------
1 | # Migration Apis
2 |
3 | #### Migrator
4 | * createTable
5 | * updateTable
6 | * deleteTable
7 | * waitUntilTableActivate
8 | * waitForTableExists
9 | * waitForTableNotExists
10 |
11 | #### Schema Builder
12 | * string
13 | * number
14 | * binary
15 | * globalSecondaryIndexUpdates
16 | - create
17 | - update
18 | - delete
19 | * localSecondaryIndex
20 | * globalSecondaryIndex
21 | * provisionedThroughput
22 | * streamSpecificationEnabled
23 | * streamSpecificationViewType
24 | * projectionTypeAll
25 | * projectionTypeKeysOnly
26 | * projectionTypeInclude
27 |
28 | ##### chainable
29 | * rangeKey
30 | * hashKey
31 |
--------------------------------------------------------------------------------
/docs/orm_apis.md:
--------------------------------------------------------------------------------
1 | # ORM Apis
2 |
3 | ##### Operations
4 | * find
5 | * all
6 | * where
7 | * then
8 | * save
9 | * destroy
10 |
11 | ##### Model
12 | * get
13 | * set
14 | * unset
15 | * extend
16 | * each
17 | * map
18 | * keys
19 | * values
20 | * contains
21 | * pick
22 | * toJson
23 | * attributes
24 |
25 | ##### Collection
26 | * pluck
27 | * each
28 | * map
29 | * reduce
30 | * reduceRight
31 | * find
32 | * filter
33 | * where
34 | * findWhere
35 | * reject
36 | * every
37 | * some
38 | * invoke
39 | * sortBy
40 | * groupBy
41 | * indexBy
42 | * countBy
43 | * shuffle
44 | * sample
45 | * size
46 | * partition
47 | * first
48 | * last
49 | * toJson
50 | * toArray
51 |
--------------------------------------------------------------------------------
/docs/query_builder_apis.md:
--------------------------------------------------------------------------------
1 | # QueryBuilder Apis
2 |
3 | ##### Options
4 | * timeout: default is 5000(ms)
5 | * initialize
6 |
7 | ##### Operations
8 | * createTable
9 | * deleteTable
10 | * all
11 | * count
12 | * create
13 | * update
14 | * delete
15 | * describe
16 | * showTables
17 | * feature
18 | * rawClient: Return promisified AWS.DynamoDB
19 | * freshBuilder: Getting fresh QueryBuilder instance with extending same options.
20 |
21 | ##### Where
22 | * where
23 | * whereIn: Using batchGetItem
24 | * whereBetween
25 | * whereBeginsWith
26 |
27 | ##### Filter
28 | * filter
29 | * filterBetween
30 | * filterIn
31 | * filterBeginsWith
32 | * filterContains
33 | * filterNotContains
34 | * filterNull
35 | * filterNotNull
36 |
37 |
38 | ##### Other conditions
39 | * select :alias of `feature.attributesToGet(['attr1', 'attr2'])`
40 | * table
41 | * indexName
42 | * asc :alias of `feature.scanIndexForward(true)`
43 | * desc :alias of `feature.scanIndexForward(false)`
44 | * limit
45 | * offset: alias of `feature.exclusiveStartKey(Object)`
46 |
47 |
48 | ##### feature methods (2012-08-10)
49 | * requestItems
50 | * returnConsumedCapacity
51 | * returnItemCollectionMetrics
52 | * attributeDefinitions
53 | * tableName
54 | * keySchema
55 | * key
56 | * expected
57 | * conditionalOperator
58 | * returnValues
59 | * conditionExpression
60 | * expressionAttributeNames
61 | * expressionAttributeValues
62 | * attributesToGet
63 | * consistentRead
64 | * projectionExpression
65 | * exclusiveStartTableName
66 | * item
67 | * keyConditions
68 | * queryFilter
69 | * scanIndexForward
70 | * exclusiveStartKey
71 | * filterExpression
72 | * scanFilter
73 | * totalSegments
74 | * segment
75 | * attributeUpdates
76 | * updateExpression
77 |
78 | ### Events
79 | * `beforeQuery`: Fired before sending request
80 | * `afterQuery`: Fired after getting response
81 |
82 | ### Callbacks
83 | * `beforeQuery`: Executed before sending request
84 | * `afterQuery`: Executed after getting response
85 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 |
5 | // For Browser
6 | if(typeof(window) === 'object') {
7 | window.npdynamodb = exports;
8 | window.DynamoDBDatatype = require('./node_modules/dynamodb-doc/lib/datatypes').DynamoDBDatatype;
9 | window.DynamoDBFormatter = require('./node_modules/dynamodb-doc/lib/formatter').DynamoDBFormatter;
10 | }
11 |
12 | exports.version = require('./package.json').version;
13 |
14 | exports.createClient = require('./lib/npdynamodb');
15 |
16 | exports.define = require('./lib/orm/index');
17 |
18 | exports.Migrator = require('./lib/migrate/migrator');
19 |
20 | var QueryBuilder = require('./lib/query_builder'),
21 | Collection = require('./lib/orm/collection'),
22 | Model = require('./lib/orm/model')
23 | ;
24 |
25 | [QueryBuilder, Collection, Model].forEach(function(Klass){
26 | Klass.extend = function(protoProps, staticProps){
27 | _.extend(Klass.prototype, protoProps || {});
28 | _.extend(Klass, staticProps || {});
29 | };
30 | });
31 |
32 | exports.plugin = function(pluginFn){
33 | if(typeof pluginFn !== 'function') {
34 | throw new Error('The plugin must be function.');
35 | }
36 | pluginFn({
37 | QueryBuilder: QueryBuilder,
38 | Collection: Collection,
39 | Model: Model
40 | });
41 | };
42 |
43 | /******* TODO Will be duplicated in 0.3.x *******/
44 |
45 | exports.Collection = require('./lib/orm/collection');
46 |
47 | exports.Model = require('./lib/orm/model');
48 |
49 | /******* TODO Will be duplicated in 0.3.x *******/
50 |
--------------------------------------------------------------------------------
/lib/bin/npd:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | var Liftoff = require('liftoff');
5 | var interpret = require('interpret');
6 | var program = require('commander');
7 | var fs = require('fs');
8 | var templates = require('../templates/index');
9 | var readline = require('readline');
10 | var argv = require('minimist')(process.argv.slice(2));
11 | var chalk = require('chalk');
12 | var _ = require('lodash');
13 | var Migrator = require('../migrate/migrator');
14 | var fs = require('fs');
15 | var repl = require("repl");
16 | var util = require('util');
17 | var npdynamod = require('../../index');
18 |
19 | function padDate(t){
20 | if(t < 10) t = "0"+t.toString();
21 | return t;
22 | }
23 |
24 | function exitWithError(text) {
25 | console.error(chalk.red(text));
26 | process.exit(1);
27 | }
28 |
29 | function exit(text) {
30 | if(text) {
31 | console.log(text);
32 | }
33 | process.exit(0);
34 | }
35 |
36 | var npdfile;
37 |
38 | function npdFile(){
39 | npdfile = npdfile || require(process.cwd() + "/npdfile");
40 | return npdfile;
41 | }
42 |
43 | function migrationFilePath(){
44 | var _migrationFilePath = (npdFile().migration && npdFile().migration.migrationFilePath) ? npdFile().migration.migrationFilePath : 'migrations'
45 | return process.cwd() + '/' + _migrationFilePath;
46 | }
47 |
48 | function npdAA(){
49 | return fs.readFileSync(__dirname + '/../templates/npd_aa.stub', 'utf-8');
50 | }
51 |
52 | function checkNpdfileExists(){
53 | if(!fs.existsSync(process.cwd() + "/npdfile.js")){
54 | console.error(chalk.red("npdfile.js was not found. Please type `npd init` to create npdfile.js"));
55 | process.exit(1);
56 | }
57 | }
58 |
59 | function inspect(data){
60 | return util.inspect(data, {depth: null });
61 | }
62 |
63 | function initCli(){
64 | var env = program.env || argv.e || 'development';
65 |
66 | if(argv.e) {
67 | var name = program.args[0]._name;
68 | if(name) {
69 | console.log(chalk.yellow('DEPRECATION WARNING') + ': npd: using `' + name+ '` with a `-e` option is deprecated!');
70 | }
71 | }
72 | checkNpdfileExists();
73 | var config = npdFile()[env];
74 |
75 | if(!config) {
76 | exitWithError(env + ' is not specified environemnt.');
77 | }
78 | config.env = env;
79 | config.cwd = migrationFilePath();
80 |
81 | console.log("Environment: " + chalk.yellow(config.env));
82 |
83 | return new Promise(function(resolve){
84 | resolve(config);
85 | });
86 | }
87 |
88 | function initialize(){
89 | program
90 | .version(require('../../package.json').version)
91 | .option('--env [value]', 'Specify the environment to run command.')
92 | ;
93 |
94 | program
95 | .command('init')
96 | .description(' Create a fresh npdfile.')
97 | .action(function() {
98 | var rl = readline.createInterface({
99 | input: process.stdin,
100 | output: process.stdout
101 | });
102 |
103 | var npdPath = process.cwd() + "/npdfile.js";
104 |
105 | if(fs.existsSync(npdPath)){
106 | rl.question('Npdfile is already existed. Do you want to overwrite it? [Y/n]', function(answer) {
107 | if(answer.toLowerCase() == 'y'){
108 | fs.writeFileSync(npdPath, templates.npdfile);
109 | exit(chalk.yellow('Overwrote the npdfile.'));
110 | }
111 | rl.close();
112 | });
113 |
114 | }else{
115 | fs.writeFileSync(npdPath, templates.npdfile);
116 | exit(
117 | [
118 | "\n",
119 | chalk.cyan('Created npdfile.js'),
120 | "\n",
121 | npdAA(),
122 | "\n",
123 | "Thanks for installing npdynamodb! \n",
124 | "--------------------------------------------------------",
125 | "HomePage: " + chalk.underline('https://github.com/noppoMan/npdynamodb'),
126 | "Documantation: " + chalk.underline('https://goo.gl/NgH8TT'),
127 | "Issues: " + chalk.underline('https://goo.gl/KdxAuc'),
128 | "Contact: Noppoman ",
129 | "--------------------------------------------------------\n",
130 | "Enjoy hack with npdynamodb!\n"
131 | ].join("\n")
132 | );
133 | }
134 |
135 | });
136 |
137 | program
138 | .command('migrate:generate ')
139 | .description(' Create a named migration file.')
140 | .action(function(name) {
141 | var migrationDir = migrationFilePath();
142 |
143 | if(!fs.existsSync(migrationDir)){
144 | fs.mkdirSync(migrationDir);
145 | }
146 |
147 | var d = new Date();
148 | var migrateFileParts = [
149 | d.getUTCFullYear(),
150 | padDate(d.getUTCMonth()+1),
151 | padDate(d.getUTCDate()+1),
152 | padDate(d.getUTCHours()),
153 | padDate(d.getUTCMinutes()),
154 | padDate(d.getUTCSeconds()),
155 | '_',
156 | name,
157 | '.js'
158 | ];
159 |
160 | var fPath = migrationDir+'/'+migrateFileParts.join('');
161 | fs.writeFileSync(fPath, templates.generator);
162 | exit('Created ' + fPath);
163 | });
164 |
165 | program
166 | .command('migrate:run')
167 | .option('-e, --environment [value]', 'Specify the environment to run.')
168 | .description(' Run all migrations that have not yet been run.')
169 | .action(function() {
170 | return initCli().then(function(config){
171 | return new Migrator.Runner(config).run().then(function(data){
172 | if(data.length === 0) {
173 | exit(chalk.cyan("Already up to date"));
174 | }else{
175 | exit(chalk.cyan(data.map(function(path){ return "Migrated" + path; }).join("\n")));
176 | }
177 | });
178 | }).catch(exitWithError);
179 | });
180 |
181 | program
182 | .command('migrate:rollback')
183 | .option('-e, --environment [value]', 'Specify the environment to run.')
184 | .description(' Rollback the last set of migrations performed.')
185 | .action(function() {
186 | return initCli().then(function(config){
187 | return new Migrator.Runner(config).rollback().then(function(data){
188 | if(!data) {
189 | return exit(chalk.cyan('There is no rollbackable generation.'));
190 | }
191 | exit(chalk.cyan('Rollbacked ' + data));
192 | });
193 | }).catch(exitWithError);
194 | });
195 |
196 | program
197 | .command('listTables')
198 | .option('-e, --environment [value]', 'Specify the environment to run.')
199 | .description(' list all tables')
200 | .action(function() {
201 | return initCli().then(function(config){
202 | var npd = npdynamod.createClient(config.dynamoClient, config.options);
203 |
204 | return npd().showTables().then(function(tables){
205 | exit(tables.TableNames.join("\n"));
206 | });
207 | }).catch(exitWithError);
208 | });
209 |
210 | program
211 | .command('dump ')
212 | .option('-e, --environment [value]', 'Specify the environment to run.')
213 | .description(' Dump specified table contents.')
214 | .action(function(table) {
215 | return initCli().then(function(config){
216 | var npd = npdynamod.createClient(config.dynamoClient, config.options);
217 |
218 | return npd().table(table).all().then(function(data){
219 | exit(inspect(data));
220 | });
221 | }).catch(exitWithError);
222 | });
223 |
224 | program
225 | .command('desc ')
226 | .description(' Show result of the describe operation')
227 | .action(function(table) {
228 | return initCli().then(function(config){
229 | var npd = npdynamod.createClient(config.dynamoClient, config.options);
230 |
231 | return npd().table(table).describe().then(function(data){
232 | exit(inspect(data));
233 | });
234 | }).catch(exitWithError);
235 | });
236 |
237 | program
238 | .command('get [rangeKey]')
239 | .description(' Show results of the query operation')
240 | .action(function(table, hashKey, rangeKey) {
241 | return initCli().then(function(config){
242 | var npd = npdynamod.createClient(config.dynamoClient, config.options);
243 | return npd().table(table).describe().then(function(data){
244 |
245 | var hashKeyName = data.Table.KeySchema[0].AttributeName,
246 | rangeKeyName = data.Table.KeySchema[1].AttributeName
247 | ;
248 |
249 | var query = npd().table(table).where(hashKeyName, hashKey);
250 | if(rangeKeyName && rangeKey) {
251 | query.where(rangeKeyName, rangeKey);
252 | }
253 |
254 | return query.then(function(result){
255 | exit(inspect(result));
256 | });
257 | });
258 | }).catch(exitWithError);
259 | });
260 |
261 | program
262 | .command('dropTable ')
263 | .description(' Drop the specified table.')
264 | .action(function(table, hashKey, rangeKey) {
265 | return initCli().then(function(config){
266 | var npd = npdynamod.createClient(config.dynamoClient, config.options);
267 | return npd().table(table).deleteTable().then(function(result){
268 | exit(inspect(result));
269 | });
270 | }).catch(exitWithError);
271 | });
272 |
273 | program.parse(process.argv);
274 |
275 | if(argv._.length === 0 || !_.last(program.args)._name) {
276 | program.help();
277 | }
278 | }
279 |
280 | var cli = new Liftoff({
281 | name: 'npd',
282 | extensions: interpret.jsVariants,
283 | v8flags: require('v8flags')
284 | });
285 |
286 | cli.launch({
287 | cwd: argv.cwd,
288 | configPath: argv.knexfile,
289 | require: argv.require,
290 | completion: argv.completion
291 | }, initialize);
292 |
--------------------------------------------------------------------------------
/lib/dialects/2012-08-10/api.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var utils = require('../../utils');
3 | var fs = require('fs');
4 |
5 | function getDynamoDBOperations(){
6 | // Browser.
7 | if(this.AWS) {
8 | return this.AWS.apiLoader.services.dynamodb['2012-08-10'].operations;
9 | }
10 |
11 | // Node.js
12 | var dynamoApiJsonPath = 'aws-sdk/apis/dynamodb-2012-08-10.min.json';
13 |
14 | try {
15 |
16 | return require(dynamoApiJsonPath).operations;
17 |
18 | } catch(e) {
19 | var pathFromWorkingDir = process.cwd() + "/node_modules/" + dynamoApiJsonPath;
20 |
21 | if(fs.existsSync(pathFromWorkingDir)){
22 | return require(pathFromWorkingDir).operations;
23 | }
24 |
25 | throw new TypeError("Module `aws-sdk` is required for npdynamodb");
26 | }
27 | }
28 |
29 | var operations = getDynamoDBOperations(this);
30 |
31 | //available methods.(api version 2012-08-10)
32 | var apis = [
33 | {
34 | origin: 'createTable',
35 | transformed: 'createTable'
36 | },
37 |
38 | {
39 | origin: 'deleteTable',
40 | transformed: 'deleteTable'
41 | },
42 |
43 | {
44 | origin: 'deleteItem',
45 | transformed: 'delete'
46 | },
47 |
48 | {
49 | origin: 'describeTable',
50 | transformed: 'describe'
51 | },
52 |
53 | {
54 | origin: 'listTables',
55 | transformed: 'showTables',
56 | },
57 |
58 | {
59 | origin: 'putItem',
60 | transformed: 'create'
61 | },
62 |
63 | {
64 | origin: 'updateItem',
65 | transformed: 'update'
66 | },
67 |
68 | {
69 | origin: 'getItem',
70 | transformed: 'first'
71 | },
72 |
73 | {
74 | origin: 'query',
75 | transformed: 'query',
76 | },
77 |
78 | {
79 | origin: 'scan',
80 | transformed: 'all'
81 | },
82 |
83 | {
84 | origin: 'updateTable',
85 | transformed: 'alterTable'
86 | },
87 |
88 | {
89 | origin: 'waitFor',
90 | transformed: 'waitFor'
91 | }
92 | ];
93 |
94 | var transformOperatorMap = {
95 | '=' : 'EQ',
96 | '!=': 'NE',
97 | '<=': 'LE',
98 | '<': 'LT',
99 | '>=': 'GE',
100 | '>': 'GT',
101 | };
102 |
103 | module.exports = {
104 | operations: operations,
105 |
106 | originalApis: _.keys(operations).map(function(api){
107 | return _.camelCase(api);
108 | }).concat(['waitFor']),
109 |
110 | transformFunctionMap: utils.collectionFlatten(apis.map(function(api){
111 | return utils.newObject(api.transformed, api.origin);
112 | })),
113 |
114 | transformOperatorMap: transformOperatorMap,
115 |
116 | availableOperators: _.keys(transformOperatorMap)
117 | };
118 |
--------------------------------------------------------------------------------
/lib/dialects/2012-08-10/feature.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var util = require('util');
5 | var EventEmitter = require('events').EventEmitter;
6 | var DOC = require("dynamodb-doc");
7 |
8 | var utils = require('../../utils');
9 | var api = require('./api');
10 |
11 | var clientPools = {};
12 |
13 | var extraOperators = {
14 | where: [
15 | 'BEGINS_WITH',
16 | 'BETWEEN'
17 | ],
18 | filter: [
19 | 'BETWEEN',
20 | 'BEGINS_WITH',
21 | 'NOT_NULL',
22 | 'NULL',
23 | 'CONTAINS',
24 | 'NOT_CONTAINS',
25 | 'IN'
26 | ]
27 | };
28 |
29 | var availableOperators = api.availableOperators.concat(extraOperators.filter);
30 |
31 | var parameterBuilder = {};
32 |
33 | parameterBuilder.createTable = parameterBuilder.deleteTable = function(feature){
34 | return { conditions: feature.params };
35 | };
36 |
37 | parameterBuilder.deleteItem = parameterBuilder.getItem = parameterBuilder.updateItem = function(feature){
38 | var cond = utils.collectionFlatten(_.map(feature.whereConditions, function(param){
39 | return utils.newObject(param.key, param.values[0]);
40 | }));
41 |
42 | return { conditions: { Key: cond } };
43 | };
44 |
45 | parameterBuilder.query = function(feature){
46 | var obj = {};
47 |
48 | obj.KeyConditions = feature.toDocClientConditon(feature.whereConditions);
49 |
50 | if(!_.isEmpty(feature.filterConditions)){
51 | obj.QueryFilter = feature.toDocClientConditon(feature.filterConditions);
52 | }
53 |
54 | return { conditions: obj };
55 | };
56 |
57 | parameterBuilder.putItem = function(feature){
58 | if(_.isArray(feature.params)) {
59 | var items = feature.params.map(function(item){
60 | return {PutRequest: { Item: item } };
61 | });
62 |
63 | var tableName = feature.conditions.TableName;
64 |
65 | return {
66 | beforeQuery: function(){
67 | this.nonTable();
68 | },
69 | nextThen: 'batchWriteItem',
70 | conditions: {
71 | RequestItems: utils.newObject(tableName, items)
72 | }
73 | };
74 |
75 | }else{
76 | return {conditions: { Item: feature.params } };
77 | }
78 | };
79 |
80 | parameterBuilder.batchGetItem = function(feature){
81 | var requestItems = {};
82 | requestItems[feature.conditions.TableName] = {
83 | Keys: feature.whereInConditions
84 | };
85 |
86 | ['AttributesToGet', 'ConsistentRead', 'ProjectionExpression', 'ExpressionAttributeNames'].forEach(function(attr){
87 | if(feature.conditions[attr]){
88 | requestItems[feature.conditions.TableName][attr] = feature.conditions[attr];
89 | delete feature.conditions[attr];
90 | }
91 | });
92 |
93 | return {
94 | beforeQuery: function(){
95 | this.nonTable();
96 | },
97 | conditions: {
98 | RequestItems : requestItems
99 | }
100 | };
101 | };
102 |
103 | function Feature(clients){
104 | EventEmitter.call(this);
105 |
106 | this.client = clients.dynamodb;
107 |
108 | this.promisifidRawClient = clients.promisifidRawClient;
109 |
110 | this.nextThen = undefined;
111 |
112 | this.params = {};
113 |
114 | this.whereConditions = [];
115 |
116 | this.whereInConditions = [];
117 |
118 | this.filterConditions = [];
119 |
120 | this.conditions = {};
121 |
122 | this.schema = {};
123 | }
124 |
125 | util.inherits(Feature, EventEmitter);
126 |
127 | _.each(api.operations, function(spec, method){
128 | _.each(spec.input.members, function(typeSpec, member){
129 | Feature.prototype[_.camelCase(member)] = function(params){
130 | this.conditions[member] = params;
131 | return this;
132 | };
133 | });
134 | });
135 |
136 | _.each(api.transformFunctionMap, function(oldM, newM){
137 | Feature.prototype[newM] = function(params){
138 | this.nextThen = oldM;
139 | this.params = params;
140 | };
141 | });
142 |
143 | Feature.prototype.select = function(){
144 | this.attributesToGet(_.toArray(arguments));
145 | };
146 |
147 | Feature.prototype.table = function(tableName){
148 | this.tableName(tableName);
149 | };
150 |
151 | Feature.prototype.count = function(){
152 | this.conditions.Select = 'COUNT';
153 | this.nextThen = 'query';
154 | };
155 |
156 | Feature.prototype.offset = function(exclusiveStartKey){
157 | this.exclusiveStartKey(exclusiveStartKey);
158 | };
159 |
160 | Feature.prototype.whereIn = function(keys, values){
161 | //items[this.conditions.TableName] = {};
162 | if(!_.isArray(keys)) {
163 | keys = [keys];
164 | }
165 | this.whereInConditions = this.whereInConditions.concat(values.map(function(val){
166 | if(!_.isArray(val)){
167 | val = [val];
168 | }
169 |
170 | if(val.length !== keys.length) {
171 | throw new Error('the length of key and value did not match.');
172 | }
173 | return utils.pairEach(keys, val);
174 | }));
175 | this.nextThen = 'batchGetItem';
176 | };
177 |
178 | _.each([
179 | 'filter',
180 | 'where'
181 | ], function(operator){
182 |
183 | Feature.prototype[operator] = function(){
184 | addConditions.apply(this, [operator].concat(_.toArray(arguments)));
185 | return this;
186 | };
187 |
188 | _.each(extraOperators[operator], function(_operator){
189 | Feature.prototype[operator + utils.toPascalCase(_operator.toLowerCase())] = function(){
190 | var args = _.toArray(arguments);
191 | var newArgs = [operator, args.shift(), _operator].concat(args);
192 | addConditions.apply(this, newArgs);
193 | return this;
194 | };
195 | });
196 | });
197 |
198 | function addConditions(){
199 | var args = _.toArray(arguments);
200 | var col = args[1], op = args[2], val = args[3];
201 | if(!_.contains(availableOperators, op)){
202 | val = op;
203 | op = '=';
204 | }
205 |
206 | this[args[0]+'Conditions'].push({
207 | key: col,
208 | values: [val].concat(Array.prototype.slice.call(args, 4)),
209 | operator: op
210 | });
211 | }
212 |
213 | Feature.prototype.toDocClientConditon = function(conditions){
214 | var self = this;
215 | return conditions.map(function(cond){
216 | var args = [
217 | cond.key,
218 | api.transformOperatorMap[cond.operator] || cond.operator
219 | ].concat(cond.values);
220 | return self.client.Condition.apply(null, args);
221 | });
222 | };
223 |
224 | Feature.prototype.set = function(key, action, value){
225 | if(!this.conditions.AttributeUpdates) {
226 | this.conditions.AttributeUpdates = {};
227 | }
228 |
229 | this.conditions.AttributeUpdates[key] = {
230 | Action: action,
231 | Value: value
232 | };
233 |
234 | return this;
235 | };
236 |
237 | Feature.prototype.asc = function(){
238 | this.scanIndexForward(true);
239 | };
240 |
241 | Feature.prototype.desc = function(){
242 | this.scanIndexForward(false);
243 | };
244 |
245 | Feature.prototype.nonTable = function(){
246 | this.conditions = _.omit(this.conditions, 'TableName');
247 | };
248 |
249 | Feature.prototype.normalizationRawResponse = function(data){
250 | // query operation
251 | if(data.Items) {
252 | return data.Items;
253 | }
254 |
255 | // getItem operation
256 | if(data.Item) {
257 | return data.Item;
258 | }
259 |
260 | // batchGetItem Operation
261 | if(data.Responses && data.Responses[this.conditions.TableName]) {
262 | return data.Responses[this.conditions.TableName].reverse();
263 | }
264 | };
265 |
266 | Feature.prototype.buildQuery = function(){
267 | var nextThen = this.nextThen || 'query';
268 | var self = this;
269 |
270 | if(this.whereInConditions.length > 0 && this.whereConditions.length > 0) {
271 | throw new Error('Can not specify the parameters of batchGetImte and Query operation at the same time');
272 | }
273 |
274 | function supplement(builder){
275 | if(!builder) return undefined;
276 |
277 | return function(){
278 | var result = builder(self);
279 | if(!result.beforeQuery) result.beforeQuery = function(){};
280 | if(!result.nextThen) result.nextThen = nextThen;
281 |
282 | return result;
283 | };
284 | }
285 |
286 | var builder = supplement(parameterBuilder[nextThen]) || function(){
287 | return {
288 | beforeQuery: function(){},
289 | conditions: {},
290 | nextThen: nextThen
291 | };
292 | };
293 |
294 | var built = builder();
295 | built.beforeQuery.call(this);
296 |
297 | this.nextThen = built.nextThen;
298 | this.conditions = _.extend(this.conditions, built.conditions);
299 |
300 | return {
301 | params: this.conditions,
302 | method: this.nextThen
303 | };
304 | };
305 |
306 |
307 | module.exports = Feature;
308 |
--------------------------------------------------------------------------------
/lib/dialects/2012-08-10/schema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 |
5 | function makeBaseItems(){
6 | var items = {
7 | TableName: this._schema.tableName
8 | };
9 |
10 | _.each(_.omit(this._schema.tableInfo, 'Projection'), function(val, key){
11 | if(!_.isEmpty(val)) {
12 | items[key] = val;
13 | }
14 | });
15 |
16 | var AttributeDefinitions = [], KeySchema = [];
17 |
18 | _.each(this._schema._schema, function(s, key){
19 | AttributeDefinitions.push({
20 | AttributeName: key,
21 | AttributeType: s.type
22 | });
23 |
24 | KeySchema.push({
25 | AttributeName: key,
26 | KeyType: s.chainable.attributes().keyType
27 | });
28 | });
29 |
30 | if(AttributeDefinitions.length > 0) {
31 | _.extend(items, {
32 | AttributeDefinitions: AttributeDefinitions,
33 | KeySchema: KeySchema
34 | });
35 | }
36 |
37 | return items;
38 | }
39 |
40 | // TODO Need to Refactor
41 | exports.buildCreateTable = function(){
42 |
43 | var items = makeBaseItems.call(this);
44 |
45 | _.each(this.childBuilders, function(def){
46 | var indexType = def._schema.IndexType;
47 | if(!items[indexType]) items[indexType] = [];
48 |
49 | var KeySchema = [];
50 |
51 | _.each(def._schema._schema, function(s, key){
52 |
53 | var alreadyDefined = _.find(items.AttributeDefinitions, function(ad){
54 | return (ad.AttributeName == key);
55 | });
56 |
57 | if(!alreadyDefined)
58 | items.AttributeDefinitions.push({
59 | AttributeName: key,
60 | AttributeType: s.type
61 | });
62 |
63 | KeySchema.push({
64 | AttributeName: key,
65 | KeyType: s.chainable.attributes().keyType
66 | });
67 | });
68 |
69 | var tableInfo = {
70 | KeySchema: KeySchema
71 | };
72 |
73 | items[indexType].push(
74 | _.extend(tableInfo, def._schema.tableInfo)
75 | );
76 | });
77 |
78 | return items;
79 | };
80 |
81 | exports.buildUpdateTable = function(){
82 |
83 | var items = makeBaseItems.call(this);
84 | var indexType = this._schema.IndexType;
85 |
86 | items[indexType] = [];
87 |
88 | _.each(this.childBuilders, function(builder){
89 | var param = {};
90 | var act = builder._schema.Action;
91 | param[act] = {};
92 |
93 | if(builder._schema.Action === 'Create') {
94 | var KeySchema = [], AttributeDefinitions = [];
95 | _.each(builder._schema._schema, function(s, key){
96 | AttributeDefinitions.push({
97 | AttributeName: key,
98 | AttributeType: s.type
99 | });
100 |
101 | KeySchema.push({
102 | AttributeName: key,
103 | KeyType: s.chainable.attributes().keyType
104 | });
105 | });
106 |
107 | if(AttributeDefinitions.length > 0) {
108 | items.AttributeDefinitions = AttributeDefinitions;
109 | param[act].KeySchema = KeySchema;
110 | }
111 | }
112 |
113 | _.extend(param[act], builder._schema.tableInfo);
114 |
115 | param[act].IndexName = builder._schema.IndexName;
116 |
117 | items[indexType].push(param);
118 | });
119 |
120 | return items;
121 | };
122 |
--------------------------------------------------------------------------------
/lib/interface.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = [
4 | 'select',
5 | 'table',
6 | 'count',
7 | 'all',
8 | 'where',
9 | 'first',
10 | 'whereIn',
11 | 'whereBetween',
12 | 'whereBeginsWith',
13 | 'filterBetween',
14 | 'filterBeginsWith',
15 | 'filter',
16 | 'filterIn',
17 | 'filterNull',
18 | 'filterNotNull',
19 | 'filterContains',
20 | 'filterNotContains',
21 | 'limit',
22 | 'offset',
23 | 'desc',
24 | 'asc',
25 | 'create',
26 | 'update',
27 | 'set',
28 | 'delete',
29 | 'showTables',
30 | 'indexName',
31 | 'describe',
32 | 'createTable',
33 | 'deleteTable',
34 | ];
35 |
--------------------------------------------------------------------------------
/lib/migrate/migrator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var Promise = require('bluebird');
5 | var path = require('path');
6 | var glob = require('glob');
7 | var fs = require('fs');
8 | var SchemaBuilder = require('../schema/builder');
9 | var utils = require('../utils');
10 |
11 | var npdynamodb = require('../npdynamodb');
12 |
13 | var migration_suffix = '.js'
14 |
15 | function create(npd){
16 | return function(){
17 | return new Migrator(npd);
18 | };
19 | }
20 |
21 | function Migrator(npd){
22 | this.npd = npd;
23 | }
24 |
25 | Migrator.prototype.createTable = function(tableName, callback){
26 | var query = this.npd();
27 | var builder = new SchemaBuilder({
28 | apiVer: query.apiVersion,
29 | tableName: tableName
30 | });
31 |
32 | callback(builder);
33 |
34 | var params = builder.buildCreateTable();
35 | return query.table(tableName).createTable(params).then(function(){
36 | return this.waitForTableExists(tableName);
37 | }.bind(this));
38 | };
39 |
40 | Migrator.prototype.updateTable = function(tableName, callback) {
41 | var query = this.npd();
42 | var builder = new SchemaBuilder({
43 | apiVer: query.apiVersion,
44 | tableName: tableName,
45 | IndexType: SchemaBuilder.Schema.IndexType.GSIU,
46 | withoutDefaultTableInfo: true
47 | });
48 |
49 | callback(builder);
50 |
51 | var params = builder.buildUpdateTable();
52 | return query.table(tableName).rawClient().updateTable(params);
53 | };
54 |
55 | Migrator.prototype.deleteTable = function(tableName) {
56 | return this.npd().table(tableName).deleteTable().then(function(){
57 | return this.waitForTableNotExists(tableName);
58 | }.bind(this));
59 | };
60 |
61 | Migrator.prototype.waitUntilTableActivate = function(tableName, timeoutms){
62 | var self = this;
63 | timeoutms = timeoutms || 10000;
64 | return new Promise(function(resolve, reject){
65 | var timer = setTimeout(function(){
66 | reject(new Error("Operations is timed out."));
67 | }, timeoutms);
68 |
69 | function retry() {
70 | self.npd().table(tableName).describe().then(function(result){
71 | const deactiveCounts = (result.Table.GlobalSecondaryIndexes || []).filter(function(index){
72 | return index.IndexStatus !== 'ACTIVE';
73 | }).length;
74 |
75 | if(result.Table.TableStatus === 'ACTIVE' && deactiveCounts === 0){
76 | clearTimeout(timer);
77 | timer = null;
78 | resolve(result);
79 | } else {
80 | setTimeout(retry, 1000);
81 | }
82 | }).catch(function(error){
83 | reject(error);
84 | });
85 | }
86 | setTimeout(retry, 1000);
87 | });
88 |
89 | };
90 |
91 | Migrator.prototype.waitForTableExists = function(tableName) {
92 | return this.npd().rawClient().waitFor('tableExists', {
93 | TableName: tableName
94 | });
95 | };
96 |
97 | Migrator.prototype.waitForTableNotExists = function(tableName) {
98 | return this.npd().rawClient().waitFor('tableNotExists', {
99 | TableName: tableName
100 | });
101 | };
102 |
103 | function MigrateRunner(config){
104 | this.config = config;
105 | var npd = npdynamodb(config.dynamoClient, config.options);
106 | this.npd = npd;
107 | this.migrator = create(npd);
108 | }
109 |
110 | MigrateRunner.prototype._createMigrateTableIfNotExits = function(){
111 | var self = this;
112 | var tableName = this.config.migrations.tableName;
113 | return this.npd().showTables()
114 | .then(function(tables){
115 | var isFound = _.find(tables.TableNames, function(t){ return t === tableName });
116 | if(isFound) { return; }
117 |
118 | return self.migrator().createTable(tableName, function(t){
119 | t.number('version').hashKey();
120 | t.provisionedThroughput.apply(t, self.config.migrations.ProvisionedThroughput);
121 | })
122 | .then(function(){
123 | return self.npd().rawClient().waitFor('tableExists', {TableName: tableName});
124 | });
125 | });
126 | };
127 |
128 | MigrateRunner.prototype.run = function(){
129 | var self = this;
130 | var tableName = this.config.migrations.tableName;
131 |
132 | return this._createMigrateTableIfNotExits().then(function(){
133 | return self.npd().table(tableName).all().then(function(data){
134 | var dirs = fs.readdirSync(self.config.cwd);
135 |
136 | var versions = _.sortBy(_.map(data.Items, function(data){
137 | return data.version;
138 | }));
139 |
140 | var incompletePaths = dirs.filter(function(dir){
141 | var version = dir.split('_')[0];
142 | if (version == parseInt(version)
143 | && dir.indexOf(migration_suffix, dir.length - migration_suffix.length) !== -1){
144 | if(!_.contains(versions, parseInt(version))) {
145 | return dir;
146 | }
147 | }
148 | });
149 |
150 | var tasks = incompletePaths.map(function(dir){
151 | var version = dir.split('_')[0];
152 | var migratorFile = require(self.config.cwd+'/'+dir);
153 | return utils.lazyPromiseRunner(function(){
154 | return migratorFile.up(self.migrator, self.config).then(function(){
155 | return self.npd().table(tableName).create({version: parseInt(version)});
156 | })
157 | .then(function(){
158 | return self.migrator().waitUntilTableActivate(tableName);
159 | })
160 | .then(function(){
161 | return self.config.cwd+'/'+dir;
162 | });
163 | });
164 | });
165 |
166 | return utils.PromiseWaterfall(tasks);
167 | });
168 | });
169 | };
170 |
171 |
172 | MigrateRunner.prototype.rollback = function(){
173 | var self = this;
174 | var tableName = this.config.migrations.tableName;
175 |
176 | var pglob = Promise.promisify(glob);
177 |
178 | return this.npd().table(tableName).all().then(function(data){
179 |
180 | var versions = _.sortBy(_.map(data.Items, function(data){
181 | return data.version;
182 | })).reverse();
183 |
184 | var lastVersion = _.first(versions);
185 | if(!lastVersion) {
186 | return Promise.resolve(null);
187 | }
188 |
189 | return pglob(path.join(self.config.cwd, '/' + lastVersion + "_*.js")).then(function(maches){
190 | return new Promise(function(resolve, reject){
191 | return require(maches[0]).down(self.migrator, self.config).then(function(){
192 | return self.npd()
193 | .table(tableName)
194 | .where('version', lastVersion)
195 | .delete()
196 | .then(function(){
197 | return self.migrator().waitUntilTableActivate(tableName);
198 | })
199 | .then(function(){
200 | resolve(maches[0]);
201 | });
202 | })
203 | .catch(reject);
204 | });
205 | });
206 | });
207 | };
208 |
209 |
210 | module.exports = {
211 | create: create,
212 |
213 | Runner: MigrateRunner,
214 |
215 | Migrator: Migrator
216 | };
217 |
--------------------------------------------------------------------------------
/lib/npdynamodb.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var QueryBuilder = require('./query_builder');
4 | var promisify = require('./promisify');
5 | var DOC = require("dynamodb-doc");
6 |
7 | var promisifiedPool = {};
8 |
9 | function npdynamodb(clients, options){
10 | var qb = new QueryBuilder(clients, options);
11 | return qb;
12 | }
13 |
14 | module.exports = function(dynamodb, options){
15 | var v = dynamodb.config.apiVersion,
16 | api = require('./dialects/' + v + '/' + 'api')
17 | ;
18 |
19 | if(!promisifiedPool[v]) {
20 | promisifiedPool[v] = promisify(dynamodb, api.originalApis);
21 | }
22 |
23 | var clients = {
24 | dynamodb: typeof dynamodb.Condition === 'function' ? dynamodb: new DOC.DynamoDB(dynamodb),
25 | promisifidRawClient: promisifiedPool[v]
26 | };
27 |
28 | return function(){
29 | return npdynamodb(clients, options);
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/lib/orm/collection.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 |
5 | module.exports = Collection;
6 |
7 | function Collection(items){
8 | this._items = items;
9 | }
10 |
11 | Collection.prototype.toArray = function(){
12 | return this._items.map(function(item){
13 | return item.attributes();
14 | });
15 | };
16 |
17 | Collection.prototype.toJSON = function(){
18 | return JSON.stringify(this.toArray());
19 | };
20 |
21 | Collection.prototype.indexAt = function(index){
22 | return this._items[index];
23 | };
24 |
25 | Collection.prototype.at = function(index){
26 | return this.indexAt(index);
27 | };
28 |
29 | _.each([
30 | 'pluck'
31 | ], function(name){
32 | Collection.prototype[name] = function(){
33 | var args = [this.toArray()].concat(_.map(arguments, function(arg){ return arg; }));
34 | return _[name].apply(_, args);
35 | };
36 | });
37 |
38 | _.each([
39 | 'each',
40 | 'map',
41 | 'reduce',
42 | 'reduceRight',
43 | 'find',
44 | 'filter',
45 | 'where',
46 | 'findWhere',
47 | 'reject',
48 | 'every',
49 | 'some',
50 | 'invoke',
51 | 'sortBy',
52 | 'groupBy',
53 | 'indexBy',
54 | 'countBy',
55 | 'shuffle',
56 | 'sample',
57 | 'size',
58 | 'partition',
59 | 'first',
60 | 'last',
61 | 'isEmpty'
62 | ], function(name){
63 | Collection.prototype[name] = function(){
64 | var args = [this._items].concat(_.map(arguments, function(arg){ return arg; }));
65 | return _[name].apply(_, args);
66 | };
67 | });
68 |
--------------------------------------------------------------------------------
/lib/orm/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var util = require('util');
5 |
6 | var utils = require('../utils');
7 | var npdynamodb = require('../npdynamodb');
8 |
9 | var BaseModel = require('./model');
10 |
11 | module.exports = function(tableName, prototypeProps, staticProps){
12 |
13 | var reservedProps = ['hashKey', 'rangeKey', 'npdynamodb'];
14 |
15 | function Model(attributes){
16 | this.tableName = tableName;
17 |
18 | _.extend(this, _.pick.apply(null, [prototypeProps].concat(reservedProps)));
19 |
20 | this._attributes = attributes || {};
21 |
22 | this._builder = this.npdynamodb().table(tableName);
23 | }
24 |
25 | _.extend(Model.prototype, _.clone(BaseModel.prototype));
26 |
27 | _.each(_.omit.apply(null, [prototypeProps].concat(reservedProps)), function(val, name){
28 | if(val.hasOwnProperty('bind')) {
29 | Model.prototype[name] = function(){
30 | return val.bind(this, _.toArray(arguments));
31 | };
32 | }else{
33 | Model.prototype[name] = val;
34 | }
35 | });
36 |
37 | _.each([
38 | 'find',
39 | 'where',
40 | 'query',
41 | 'fetch',
42 | 'save'
43 | ], function(_interface){
44 | Model[_interface] = function(){
45 | var model = new Model();
46 | return model[_interface].apply(model, _.toArray(arguments));
47 | };
48 | });
49 |
50 | _.each(staticProps, function(val, name){
51 | if(val.hasOwnProperty('bind')) {
52 | Model[name] = val.bind(Model);
53 | }else{
54 | Model[name] = val;
55 | }
56 | });
57 |
58 | return Model;
59 | };
60 |
--------------------------------------------------------------------------------
/lib/orm/model.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var Collection = require('./collection');
5 | var promiseRunner = require('./promise_runner');
6 |
7 | module.exports = Model;
8 |
9 | function Runner(query, formatter){
10 | var self = this;
11 | return promiseRunner(query, function(data){
12 | self._refreshBuilder();
13 | return formatter(data);
14 | })
15 | .bind(this);
16 | }
17 |
18 | function Model(){}
19 |
20 | Model.prototype.where = function(){
21 | this._builder.where.apply(this._builder, arguments);
22 | return this;
23 | };
24 |
25 | Model.prototype.query = function(fn){
26 | if(typeof fn === 'function') {
27 | fn(this._builder);
28 | return this;
29 | }
30 |
31 | return this._builder;
32 | };
33 |
34 | Model.prototype.find = function(hashKeyVal, rangeKeyVal){
35 | var self = this;
36 | var query = this._builder.where(this.hashKey, hashKeyVal);
37 |
38 | if(rangeKeyVal) {
39 | query.where(this.rangeKey, rangeKeyVal);
40 | }
41 |
42 | return Runner.bind(this)(query.first(), function(data){
43 | self._attributes = self._builder.normalizationRawResponse(data);
44 | return self;
45 | });
46 | };
47 |
48 | Model.prototype.reload = function(){
49 | return this.find(this.get(this.hashKey), this.get(this.rangeKey));
50 | };
51 |
52 | Model.prototype.fetch = function(){
53 | var self = this;
54 |
55 | return Runner.bind(this)(this._builder, function(data){
56 | var items = self._builder.normalizationRawResponse(data);
57 | var models = items.map(function(item){
58 | return new self.constructor(item);
59 | });
60 |
61 | return new Collection(models);
62 | });
63 | };
64 |
65 | Model.prototype.save = function(item){
66 | var self = this;
67 |
68 | if(typeof item === 'object'){
69 | _.extend(this._attributes, item);
70 | }
71 |
72 | return Runner.bind(this)(this._builder.create(this.attributes()), function(){
73 | return self;
74 | });
75 | };
76 |
77 | Model.prototype.destroy = function(item){
78 | var self = this;
79 |
80 | var query = this._builder.where(this.hashKey, this.get(this.hashKey));
81 |
82 | if(this.rangeKey) {
83 | query.where(this.rangeKey, this.get(this.rangeKey));
84 | }
85 |
86 | return Runner.bind(this)(query.delete(), function(){
87 | self._attributes = {};
88 | return self;
89 | });
90 | };
91 |
92 | Model.prototype.set = function(key, value){
93 | this._attributes[key] = value;
94 | return this;
95 | };
96 |
97 | Model.prototype.unset = function(key){
98 | if(this._attributes[key]) {
99 | delete this._attributes[key];
100 | }
101 | return this;
102 | };
103 |
104 | Model.prototype.extend = function(attributes){
105 | _.extend(this._attributes, attributes);
106 | return this;
107 | };
108 |
109 | Model.prototype.get = function(key){
110 | return this._attributes[key];
111 | };
112 |
113 | Model.prototype.isEmpty = function(){
114 | return _.isEmpty(this._attributes);
115 | };
116 |
117 | Model.prototype.attributes = function(){
118 | return this._attributes;
119 | };
120 |
121 | Model.prototype.toJson = function(){
122 | return JSON.stringify(this._attributes);
123 | };
124 |
125 | Model.prototype._refreshBuilder = function(){
126 | this._builder = this.npdynamodb().table(this.tableName);
127 | };
128 |
129 | _.each([
130 | 'each',
131 | 'map',
132 | 'includes',
133 | 'contains',
134 | 'keys',
135 | 'pick',
136 | 'values',
137 | ], function(name){
138 | Model.prototype[name] = function(){
139 | var args = [this._item].concat(_.map(arguments, function(arg){ return arg; }));
140 | return _[name].apply(_, args);
141 | };
142 | });
143 |
--------------------------------------------------------------------------------
/lib/orm/promise_runner.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Promise = require('bluebird');
4 |
5 | module.exports = function(promsie, formatter) {
6 | var self = this;
7 | return new Promise(function(resolve, reject){
8 | return promsie.then(function(data){
9 | if(!formatter) {
10 | formatter = function(data){ return data; };
11 | }
12 | return resolve(formatter(data));
13 | }).catch(reject);
14 | });
15 | };
16 |
--------------------------------------------------------------------------------
/lib/promisify.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Promise = require('bluebird');
4 |
5 | module.exports = function(lib, apis){
6 | var promisifiedMethods = {};
7 |
8 | apis.forEach(function(m){
9 | // TODO Need to support all apis
10 | if(!lib[m]) {
11 | return;
12 | }
13 | promisifiedMethods[m] = Promise.promisify(lib[m], lib);
14 | });
15 |
16 | return promisifiedMethods;
17 | };
18 |
--------------------------------------------------------------------------------
/lib/query_builder.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var interfaces = require('./interface');
4 | var _ = require('lodash');
5 | var Promise = require('bluebird');
6 | var EventEmitter = require('events').EventEmitter;
7 | var util = require('util');
8 |
9 | var utils = require('./utils');
10 |
11 | var Features = {
12 | '2012-08-10': require('./dialects/2012-08-10/feature')
13 | };
14 |
15 | module.exports = QueryBuilder;
16 |
17 | function QueryBuilder(clients, options){
18 | EventEmitter.call(this);
19 | var feature = new Features[clients.dynamodb.config.apiVersion](clients);
20 |
21 | var opts = options || {};
22 | var initialize = opts.initialize;
23 |
24 | this.apiVersion = feature.client.config.apiVersion;
25 | this._feature = feature;
26 | this._options = _.omit(opts, 'initialize');
27 | this._initializer = opts.initialize;
28 | this._callbacks = {};
29 |
30 | if(typeof this._initializer === 'function') {
31 | this._initializer.bind(this)();
32 | }
33 | }
34 | util.inherits(QueryBuilder, EventEmitter);
35 |
36 | interfaces.forEach(function(m){
37 | QueryBuilder.prototype[m] = function(){
38 | this._feature[m].apply(this._feature, _.toArray(arguments));
39 | return this;
40 | };
41 | });
42 |
43 | QueryBuilder.prototype.freshBuilder = function(){
44 | return new QueryBuilder({
45 | dynamodb: this._feature.client,
46 | promisifidRawClient: this._feature.promisifidRawClient
47 | },
48 | _.clone(_.extend(this._options, {initialize: this._initializer}))
49 | );
50 | };
51 |
52 | QueryBuilder.prototype.tableName = function(){
53 | return this._feature.conditions.TableName;
54 | };
55 |
56 | QueryBuilder.prototype.normalizationRawResponse = function(data){
57 | return this._feature.normalizationRawResponse(data);
58 | };
59 |
60 | QueryBuilder.prototype.feature = function(cb){
61 | cb(this._feature);
62 | return this;
63 | };
64 |
65 | QueryBuilder.prototype.rawClient = function(cb){
66 | return this._feature.promisifidRawClient;
67 | };
68 |
69 | QueryBuilder.prototype.callbacks = function(name, fn){
70 | if(!this._callbacks[name]){
71 | this._callbacks[name] = [];
72 | }
73 | this._callbacks[name].push(fn);
74 | return this;
75 | };
76 |
77 | function callbacksPromisified(callbacks, data){
78 | return (callbacks || []).map(function(f){
79 | return f.bind(this)(data);
80 | }.bind(this));
81 | }
82 |
83 | _.each([
84 | 'then',
85 | ], function(promiseInterface){
86 | QueryBuilder.prototype[promiseInterface] = function(cb){
87 | var self = this;
88 | var feature = self._feature;
89 | var callbacks = this._callbacks;
90 | var timer;
91 |
92 | return Promise.all(callbacksPromisified.bind(self)(callbacks.beforeQuery)).then(function(){
93 | var built = feature.buildQuery();
94 | self.emit('beforeQuery', built.params);
95 |
96 | return new Promise(function(resolve, reject){
97 | var request = feature.client[built.method](built.params, function(err, data){
98 | if(timer) {
99 | clearTimeout(timer);
100 | timer = null;
101 | }
102 | if(err) {
103 | return reject(err);
104 | }
105 | resolve(data);
106 | });
107 |
108 | // Handle timeout
109 | if(self._options.timeout !== null) {
110 | timer = setTimeout(function(){
111 | request.abort();
112 | reject(new Error("The connection has timed out."));
113 | }, self._options.timeout || 5000);
114 | }
115 | });
116 | })
117 | .then(function(data){
118 | return Promise.all(callbacksPromisified.bind(self)(callbacks.afterQuery, data)).then(function(){
119 | self.emit('afterQuery', data);
120 | return data;
121 | });
122 | })
123 | .then(function(data){
124 | return cb.bind(self)(data);
125 | });
126 | };
127 | });
128 |
--------------------------------------------------------------------------------
/lib/schema/builder.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var types = require('./types');
4 | var Chainable = require('./chainable');
5 | var _ = require('lodash');
6 | var utils = require('../utils');
7 |
8 | module.exports = SchemaBuilder;
9 |
10 | function SchemaBuilder(options){
11 | this._schema = new Schema(options);
12 |
13 | this.childBuilders = [];
14 |
15 | this.initialized = false;
16 |
17 | this.builtSchema = {
18 | hashKeys: [],
19 | rangeKeys: [],
20 | struct: {},
21 | };
22 |
23 |
24 | _.each(_.omit(types, 'map', 'list'), function(originalType, npdType){
25 | SchemaBuilder.prototype[npdType] = function(column){
26 | var chainable = new Chainable();
27 | this._schema.extend(
28 | utils.newObject(column, {
29 | type: originalType,
30 | chainable: chainable
31 | })
32 | );
33 | return chainable;
34 | };
35 | });
36 | }
37 |
38 | SchemaBuilder.Schema = Schema;
39 |
40 | SchemaBuilder.prototype._definePropsIfNotExists = function(propName){
41 | if(!this._schema.tableInfo[propName]) {
42 | this._schema.tableInfo[propName] = {};
43 | }
44 | };
45 |
46 | SchemaBuilder.prototype.provisionedThroughput = function(r, w){
47 | this._definePropsIfNotExists('ProvisionedThroughput');
48 | this._schema.tableInfo.ProvisionedThroughput.ReadCapacityUnits = r;
49 | this._schema.tableInfo.ProvisionedThroughput.WriteCapacityUnits = w;
50 | };
51 |
52 | SchemaBuilder.prototype.projectionTypeAll = function() {
53 | return this.ProjectionTypeAll();
54 | };
55 |
56 | SchemaBuilder.prototype.projectionTypeKeysOnly = function() {
57 | return this.ProjectionTypeKeysOnly();
58 | };
59 |
60 | SchemaBuilder.prototype.projectionTypeInclude = function() {
61 | return this.ProjectionTypeInclude();
62 | };
63 |
64 | /******* TODO Will be duplicated in 0.3.x *******/
65 | SchemaBuilder.prototype.ProjectionTypeAll = function() {
66 | this._definePropsIfNotExists('Projection');
67 | this._schema.tableInfo.Projection.ProjectionType = 'ALL';
68 | };
69 |
70 | SchemaBuilder.prototype.ProjectionTypeKeysOnly = function() {
71 | this._definePropsIfNotExists('Projection');
72 | this._schema.tableInfo.Projection.ProjectionType = 'KEYS_ONLY';
73 | };
74 |
75 | SchemaBuilder.prototype.ProjectionTypeInclude = function(nonKeyAttributes) {
76 | var projection = {
77 | ProjectionType: 'INCLUDE'
78 | };
79 | if(_.isArray(nonKeyAttributes)) projection.NonKeyAttributes = nonKeyAttributes;
80 |
81 | _.extend(this._schema.tableInfo.Projection, projection);
82 | };
83 | /******* TODO Will be duplicated in 0.3.x *******/
84 |
85 | SchemaBuilder.prototype.streamSpecificationEnabled = function(bool){
86 | this._definePropsIfNotExists('StreamSpecification');
87 | this._schema.tableInfo.StreamSpecification.StreamEnabled = bool;
88 | };
89 |
90 | SchemaBuilder.prototype.streamSpecificationViewType = function(type){
91 | this._definePropsIfNotExists('StreamSpecification');
92 | this._schema.tableInfo.StreamSpecification.StreamViewType = type;
93 | };
94 |
95 | SchemaBuilder.prototype.globalSecondaryIndex = function(indexName, callback){
96 | callback(this.__newBuilder({
97 | IndexType: Schema.IndexType.GSI,
98 | tableInfo: {
99 | IndexName: indexName
100 | }
101 | }));
102 | };
103 |
104 | SchemaBuilder.prototype.globalSecondaryIndexUpdates = function(callback){
105 | var self = this;
106 |
107 | function capitalizeFirstLetter(str) {
108 | return str.charAt(0).toUpperCase() + str.slice(1);
109 | }
110 |
111 | function __newBuilder(indexName, action) {
112 | return self.__newBuilder({
113 | IndexName: indexName,
114 | Action: action,
115 | withoutDefaultTableInfo: true
116 | });
117 | }
118 |
119 | var actions = {
120 | create: function(indexName, callback){
121 | var builder = __newBuilder(indexName, 'Create');
122 | callback(builder);
123 | },
124 |
125 | delete: function(indexName){
126 | var builder = __newBuilder(indexName, 'Delete');
127 | },
128 |
129 | update: function(indexName, callback){
130 | var builder = __newBuilder(indexName, 'Update');
131 | callback(builder);
132 | }
133 | };
134 |
135 | callback(actions);
136 | };
137 |
138 | SchemaBuilder.prototype.localSecondaryIndex = function(indexName, callback){
139 | var def = this.__newBuilder({
140 | IndexType: Schema.IndexType.LSI,
141 | tableInfo: {
142 | IndexName: indexName
143 | }
144 | });
145 |
146 | def._schema.tableInfo = _.omit(def._schema.tableInfo, 'ProvisionedThroughput');
147 |
148 | callback(def);
149 | };
150 |
151 | SchemaBuilder.prototype.__newBuilder = function(params){
152 | var child = new SchemaBuilder(params);
153 | this.childBuilders.push(child);
154 | return child;
155 | };
156 |
157 | ['buildCreateTable', 'buildUpdateTable'].forEach(function(name){
158 | SchemaBuilder.prototype[name] = function(){
159 | return require('../dialects/' + this._schema.apiVer + "/schema")[name].call(this);
160 | };
161 | });
162 |
163 | Schema.IndexType = {
164 | DEFAULT: 'Default',
165 | GSI: 'GlobalSecondaryIndexes',
166 | GSIU: 'GlobalSecondaryIndexUpdates',
167 | LSI: 'LocalSecondaryIndexes'
168 | };
169 |
170 | function Schema(options){
171 | this.apiVer;
172 |
173 | this.tableName;
174 |
175 | this.column;
176 |
177 | this.type;
178 |
179 | this._schema = {};
180 |
181 | this.IndexType = Schema.IndexType.DEFAULT;
182 |
183 | this.tableInfo = options.withoutDefaultTableInfo ? {} : {
184 | IndexName: null,
185 | ProvisionedThroughput: {
186 | ReadCapacityUnits: 10,
187 | WriteCapacityUnits: 10
188 | },
189 | Projection: {
190 | ProjectionType: 'NONE'
191 | }
192 | };
193 |
194 | this.mergeProps(options);
195 | }
196 |
197 | Schema.prototype.extend = function(props){
198 | _.extend(this._schema, props);
199 | };
200 |
201 | Schema.prototype.mergeProps = function(props){
202 | var keys = _.keys(props);
203 | var self = this;
204 | keys.forEach(function(key){
205 | if(_.isObject(props[key])) {
206 | self[key] = _.extend(self[key], props[key]);
207 | }else{
208 | self[key] = props[key];
209 | }
210 | });
211 | };
212 |
--------------------------------------------------------------------------------
/lib/schema/chainable.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = Chainable;
4 |
5 | function Chainable(){
6 | this.attr = {
7 | keyType: null,
8 | optional: false
9 | };
10 | }
11 |
12 | Chainable.prototype.attributes = function(){
13 | return this.attr;
14 | };
15 |
16 | Chainable.prototype.hashKey = function(){
17 | this.attr.keyType = 'HASH';
18 | };
19 |
20 | Chainable.prototype.rangeKey = function(){
21 | this.attr.keyType = 'RANGE';
22 | };
23 |
24 | Chainable.prototype.optional = function(){
25 | this.attr.optional = true;
26 | };
27 |
--------------------------------------------------------------------------------
/lib/schema/types.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | string: 'S',
5 | number: 'N',
6 | binary: 'B',
7 | stringSet: 'SS',
8 | numberSet: 'NS',
9 | binarySet: 'BS',
10 | map: 'M',
11 | list: 'L',
12 | null: 'NULL',
13 | bool: 'BOOL'
14 | };
15 |
--------------------------------------------------------------------------------
/lib/templates/generator.stub:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.up = function(migrator, config){
4 |
5 | };
6 |
7 | exports.down = function(migrator, config){
8 |
9 | };
10 |
--------------------------------------------------------------------------------
/lib/templates/index.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | module.exports = {
4 | npdfile : fs.readFileSync(__dirname + '/npdfile.stub'),
5 |
6 | generator: fs.readFileSync(__dirname + '/generator.stub')
7 | }
8 |
--------------------------------------------------------------------------------
/lib/templates/npd_aa.stub:
--------------------------------------------------------------------------------
1 | __ _ _____ ______ __ __ __ _ _______ _______ _____ ______ ______
2 | | \ | |_____] | \ \_/ | \ | |_____| | | | | | | \ |_____]
3 | | \_| | |_____/ | | \_| | | | | | |_____| |_____/ |_____]
4 |
--------------------------------------------------------------------------------
/lib/templates/npdfile.stub:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var AWS = require('aws-sdk');
4 |
5 | var dynamodb = new AWS.DynamoDB({
6 | apiVersion: '2012-08-10',
7 | accessKeyId: process.env['DYNAMO_KEY'] || "AWS_KEY",
8 | secretAccessKey: process.env['DYNAMO_SECRET_KEY'] || "AWS_SECRET",
9 | region: process.env['AWS_REGION'] || "ap-northeast-1",
10 | });
11 |
12 | module.exports = {
13 |
14 | // Specify migration file path. Default is `./migrations`
15 | // migration: {
16 | // migrationFilePath: './npdynamodb_migrations'
17 | // },
18 |
19 | development: {
20 | dynamoClient: dynamodb,
21 | migrations: {
22 | ProvisionedThroughput: [10, 10],
23 | tableName: 'npd_migrations'
24 | }
25 | },
26 |
27 | staging: {
28 | dynamoClient: dynamodb,
29 | migrations: {
30 | ProvisionedThroughput: [10, 10],
31 | tableName: 'npd_migrations'
32 | }
33 | },
34 |
35 | production: {
36 | dynamoClient: dynamodb,
37 | migrations: {
38 | ProvisionedThroughput: [10, 10],
39 | tableName: 'npd_migrations'
40 | }
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var Promise = require('bluebird');
5 |
6 | exports.isEmpty = function(val){
7 | if(val === null) return true;
8 | if(val === '') return true;
9 | if(_.isArray(val) && val.length === 0) return true;
10 | if(_.isObject(val) && _.keys(val).length === 0) return true;
11 | return false;
12 | };
13 |
14 | exports.newObject = function(key, val){
15 | var o = {};
16 | o[key] = val;
17 | return o;
18 | };
19 |
20 | exports.toPascalCase = function(string){
21 | var camelized = string.replace(/_./g, function(str) {
22 | return str.charAt(1).toUpperCase();
23 | });
24 |
25 | return camelized.charAt(0).toUpperCase() + camelized.slice(1);
26 | };
27 |
28 | exports.collectionFlatten = function(collection){
29 | var newObj = {};
30 | _.each(collection, function(obj){
31 | _.extend(newObj, obj);
32 | });
33 | return newObj;
34 | };
35 |
36 | exports.PromiseWaterfall = function(promises){
37 |
38 | return new Promise(function(resolve, reject){
39 | var results = [];
40 |
41 | function watefallThen(promise){
42 |
43 | if(promise && typeof promise.then === 'function') {
44 | promise.then(function(data){
45 | results.push(data);
46 | watefallThen(promises.shift());
47 | }).catch(reject);
48 | } else if(promise && typeof promise.then !== 'function') {
49 |
50 | reject(new TypeError("Function return value should be a promise."));
51 |
52 | } else {
53 | resolve(results);
54 | }
55 | }
56 |
57 | watefallThen(promises.shift());
58 | });
59 |
60 | };
61 |
62 | exports.lazyPromiseRunner = function(cb) {
63 | return {
64 | then: function(callback){
65 | return cb().then(callback);
66 | }
67 | };
68 | };
69 |
70 | exports.pairEach = function(keys, values) {
71 | var obj = {};
72 | keys.forEach(function(key, i){
73 | obj[key] = values[i];
74 | });
75 | return obj;
76 | };
77 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "npdynamodb",
3 | "version": "0.2.15",
4 | "description": "A Node.js Simple Query Builder and ORM for AWS DynamoDB.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "find ./test -name *_spec.js | xargs mocha --reporter spec -t 20000"
8 | },
9 | "keywords": [
10 | "dynamodb",
11 | "aws",
12 | "activerecord",
13 | "orm",
14 | "migration"
15 | ],
16 | "bin": {
17 | "npd": "./lib/bin/npd"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/noppoMan/npdynamodb.git"
22 | },
23 | "author": "noppoMan (http://miketokyo.com)",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/noppoMan/npdynamodb/issues"
27 | },
28 | "homepage": "https://github.com/noppoMan/npdynamodb",
29 | "dependencies": {
30 | "bluebird": "^2.9.24",
31 | "chalk": "^1.0.0",
32 | "commander": "^2.7.1",
33 | "dynamodb-doc": "^1.0.0",
34 | "glob": "^5.0.3",
35 | "interpret": "^0.5.2",
36 | "liftoff": "^2.0.3",
37 | "lodash": "^3.5.0",
38 | "minimist": "^1.1.1",
39 | "readline": "0.0.7",
40 | "v8flags": "^2.0.3"
41 | },
42 | "devDependencies": {
43 | "aws-sdk": "^2.1.18",
44 | "chai": "^2.2.0"
45 | },
46 | "browser": {
47 | "./lib/migrate/migrator.js": false,
48 | "./lib/dialects/2012-08-10/schema.js": false,
49 | "aws-sdk": false
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/scripts/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | npm install webpack@1.10.1 json-loader@0.5.2 uglify-js@2.4.24
4 |
5 | webpack=node_modules/.bin/webpack
6 | uglifyjs=node_modules/.bin/uglifyjs
7 |
8 | DEST_DIR='./build'
9 |
10 | if [ ! -d "$DEST_DIR" ]; then
11 | mkdir $DEST_DIR
12 | fi
13 |
14 | $webpack --config scripts/webpack.config.js index.js $DEST_DIR/npdynamodb.js
15 | $uglifyjs $DEST_DIR/npdynamodb.js -o $DEST_DIR/npdynamodb.min.js -c -m
16 |
--------------------------------------------------------------------------------
/scripts/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var webpack = require('webpack');
4 |
5 | var externals = [
6 | {
7 | "bluebird": {
8 | root: "Promise",
9 | commonjs2: "bluebird",
10 | commonjs: "bluebird",
11 | amd: "bluebird"
12 | },
13 | "lodash": {
14 | root: "_",
15 | commonjs2: "lodash",
16 | commonjs: "lodash",
17 | amd: "lodash"
18 | },
19 | "aws-sdk": {
20 | root: "AWS",
21 | commonjs2: "aws-sdk",
22 | commonjs: "aws-sdk",
23 | amd: "aws-sdk"
24 | }
25 | }
26 | ];
27 |
28 | module.exports = {
29 |
30 | module: {
31 | loaders: [
32 | {
33 | include: /\.json$/, loaders: ["json-loader"]
34 | }
35 | ],
36 | resolve: {
37 | extensions: ['', '.json', '.js']
38 | }
39 | },
40 |
41 | output: {
42 | library: 'npdynamodb',
43 | libraryTarget: 'umd'
44 | },
45 |
46 | node: {
47 | fs: "empty"
48 | },
49 |
50 | externals: externals,
51 |
52 | verbose: true
53 |
54 | };
55 |
--------------------------------------------------------------------------------
/test/data/complex_table_seed.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var Buffer = require('buffer').Buffer;
5 |
6 |
7 | var stabData = _.range(1, 11).map(function(i){
8 | return {
9 | hash_key: "key1",
10 | range_key: i,
11 | gsi_hash_key: "gkey1", //gsi
12 | lsi_range_key: i, // lsi
13 | bool: true,
14 | binary: new Buffer([1,2,3,4]),
15 | null: true,
16 | stringSet: ["foo", "bar"],
17 | numberSet: [1, 2],
18 | binarySet: [new Buffer([1,2,3,4]), new Buffer([5,6,7,8])],
19 | document: {
20 | number1: 1,
21 | string1: "foo",
22 | list1: [
23 | {
24 | number2: 2,
25 | string2: 'bar',
26 | list2: [
27 | {
28 | number3:3,
29 | string3: 'foobar',
30 | list3: [
31 | {
32 | number4:4,
33 | string4: 'barfoo'
34 | }
35 | ]
36 | }
37 | ]
38 | }
39 | ]
40 | }
41 | };
42 | });
43 |
44 | module.exports = stabData;
45 |
--------------------------------------------------------------------------------
/test/data/test_tables.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.compex_table = {
4 | AttributeDefinitions: [
5 | { AttributeName: 'hash_key', AttributeType: 'S' },
6 | { AttributeName: 'range_key', AttributeType: 'N' },
7 | { AttributeName: 'gsi_hash_key', AttributeType: 'S' },
8 | { AttributeName: 'lsi_range_key', AttributeType: 'N' }
9 | ],
10 | KeySchema: [
11 | { AttributeName: 'hash_key', KeyType: 'HASH' },
12 | { AttributeName: 'range_key', KeyType: 'RANGE' }
13 | ],
14 | TableName: 'complex_table',
15 | ProvisionedThroughput: {
16 | ReadCapacityUnits: 100, WriteCapacityUnits: 100
17 | },
18 | GlobalSecondaryIndexes: [
19 | {
20 | KeySchema: [
21 | { AttributeName: 'gsi_hash_key', KeyType: 'HASH' }
22 | ],
23 | IndexName: 'indexName1',
24 | ProvisionedThroughput: { ReadCapacityUnits: 100, WriteCapacityUnits: 100 },
25 | Projection: { ProjectionType: 'ALL' }
26 | }
27 | ],
28 | LocalSecondaryIndexes: [
29 | {
30 | KeySchema: [
31 | { AttributeName: 'hash_key', KeyType: 'HASH' },
32 | { AttributeName: 'lsi_range_key', KeyType: 'RANGE' }
33 | ],
34 | IndexName: 'indexName2',
35 | Projection: { ProjectionType: 'ALL' }
36 | }
37 | ]
38 | };
39 |
40 |
41 | exports.chats = {
42 | AttributeDefinitions: [
43 | { AttributeName: 'room_id', AttributeType: 'S' },
44 | { AttributeName: 'timestamp', AttributeType: 'N' },
45 | ],
46 | KeySchema: [
47 | { AttributeName: 'room_id', KeyType: 'HASH' },
48 | { AttributeName: 'timestamp', KeyType: 'RANGE' }
49 | ],
50 | TableName: 'chats',
51 | ProvisionedThroughput: {
52 | ReadCapacityUnits: 100, WriteCapacityUnits: 100
53 | },
54 | };
55 |
56 | exports.for_schema_test = {
57 | AttributeDefinitions: [
58 | { AttributeName: 'hash_key', AttributeType: 'S' },
59 | { AttributeName: 'range_key', AttributeType: 'B' },
60 | { AttributeName: 'gsi_hash_key', AttributeType: 'S' },
61 | { AttributeName: 'gsi_hash_key2', AttributeType: 'N' },
62 | { AttributeName: 'gsi_hash_key3', AttributeType: 'B' },
63 | { AttributeName: 'lsi_range_key', AttributeType: 'N' },
64 | { AttributeName: 'lsi_range_key2', AttributeType: 'S' },
65 | { AttributeName: 'lsi_range_key3', AttributeType: 'B' }
66 | ],
67 | KeySchema: [
68 | { AttributeName: 'hash_key', KeyType: 'HASH' },
69 | { AttributeName: 'range_key', KeyType: 'RANGE' }
70 | ],
71 | TableName: 'complex_table',
72 | ProvisionedThroughput: { ReadCapacityUnits: 100, WriteCapacityUnits: 100 },
73 | GlobalSecondaryIndexes: [
74 | {
75 | KeySchema: [
76 | { AttributeName: 'gsi_hash_key', KeyType: 'HASH' }
77 | ],
78 | IndexName: 'indexName1',
79 | ProvisionedThroughput: { ReadCapacityUnits: 100, WriteCapacityUnits: 100 },
80 | Projection: { ProjectionType: 'ALL' }
81 | },
82 | {
83 | KeySchema: [
84 | { AttributeName: 'gsi_hash_key2', KeyType: 'HASH' }
85 | ],
86 | IndexName: 'indexName2',
87 | ProvisionedThroughput: { ReadCapacityUnits: 100, WriteCapacityUnits: 100 },
88 | Projection: { ProjectionType: 'ALL' }
89 | },
90 | {
91 | KeySchema: [
92 | { AttributeName: 'gsi_hash_key3', KeyType: 'HASH' }
93 | ],
94 | IndexName: 'indexName3',
95 | ProvisionedThroughput: { ReadCapacityUnits: 100, WriteCapacityUnits: 100 },
96 | Projection: { ProjectionType: 'ALL' }
97 | }
98 | ],
99 | LocalSecondaryIndexes: [
100 | {
101 | KeySchema: [
102 | { AttributeName: 'hash_key', KeyType: 'HASH' },
103 | { AttributeName: 'lsi_range_key', KeyType: 'RANGE' }
104 | ],
105 | IndexName: 'indexName4',
106 | Projection: { ProjectionType: 'ALL' }
107 | },
108 | {
109 | KeySchema: [
110 | { AttributeName: 'hash_key', KeyType: 'HASH' },
111 | { AttributeName: 'lsi_range_key2', KeyType: 'RANGE' }
112 | ],
113 | IndexName: 'indexName5',
114 | Projection: { ProjectionType: 'ALL' }
115 | },
116 | {
117 | KeySchema: [
118 | { AttributeName: 'hash_key', KeyType: 'HASH' },
119 | { AttributeName: 'lsi_range_key3', KeyType: 'RANGE' }
120 | ],
121 | IndexName: 'indexName6',
122 | Projection: { ProjectionType: 'ALL' }
123 | }
124 | ]
125 | };
126 |
--------------------------------------------------------------------------------
/test/dynamodb_2012_08_10.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var AWS = require('aws-sdk');
4 |
5 | module.exports = new AWS.DynamoDB({
6 | apiVersion: '2012-08-10',
7 | accessKeyId: process.env['DYNAMO_KEY'] || "AWS_KEY",
8 | secretAccessKey: process.env['DYNAMO_SECRET_KEY'] || "AWS_SECRET",
9 | region: process.env['AWS_REGION'] || "ap-northeast-1",
10 | sslEnabled: false,
11 | endpoint: process.env['DYNAMO_ENDPOINT'] || 'localhost:8000'
12 | });
13 |
--------------------------------------------------------------------------------
/test/migrations/20150404071625_create_test_table1.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.up = function(migrator){
4 | return migrator().createTable('test_table1', function(t){
5 | t.string('hash_key').hashKey();
6 | t.number('range_key').rangeKey();
7 | t.provisionedThroughput(100, 100);
8 |
9 | t.globalSecondaryIndex('indexName1', function(t){
10 | t.string('gsi_hash_key').hashKey();
11 | t.provisionedThroughput(100, 100);
12 | t.projectionTypeAll();
13 | });
14 |
15 | t.localSecondaryIndex('indexName2', function(t){
16 | t.string('hash_key').hashKey();
17 | t.number('lsi_range_key').rangeKey();
18 | t.projectionTypeAll();
19 | });
20 | });
21 | };
22 |
23 | exports.down = function(migrator){
24 | return migrator().deleteTable('test_table1');
25 | };
26 |
--------------------------------------------------------------------------------
/test/migrations/20150404071722_create_test_table2.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.up = function(migrator){
4 | return migrator().createTable('test_table2', function(t){
5 | t.string('hash_key').hashKey();
6 | t.binary('range_key').rangeKey();
7 | t.provisionedThroughput(100, 100);
8 |
9 | t.globalSecondaryIndex('indexName1', function(t){
10 | t.string('gsi_hash_key').hashKey();
11 | t.provisionedThroughput(100, 100);
12 | t.projectionTypeAll();
13 | });
14 |
15 | t.globalSecondaryIndex('indexName2', function(t){
16 | t.number('gsi_hash_key2').hashKey();
17 | t.provisionedThroughput(100, 100);
18 | t.projectionTypeAll();
19 | });
20 |
21 | t.globalSecondaryIndex('indexName3', function(t){
22 | t.binary('gsi_hash_key3').hashKey();
23 | t.provisionedThroughput(100, 100);
24 | t.projectionTypeAll();
25 | });
26 |
27 | t.localSecondaryIndex('indexName4', function(t){
28 | t.string('hash_key').hashKey();
29 | t.number('lsi_range_key').rangeKey();
30 | t.projectionTypeAll();
31 | });
32 |
33 | t.localSecondaryIndex('indexName5', function(t){
34 | t.string('hash_key').hashKey();
35 | t.string('lsi_range_key2').rangeKey();
36 | t.projectionTypeAll();
37 | });
38 |
39 | t.localSecondaryIndex('indexName6', function(t){
40 | t.string('hash_key').hashKey();
41 | t.binary('lsi_range_key3').rangeKey();
42 | t.projectionTypeAll();
43 | });
44 | });
45 | };
46 |
47 | exports.down = function(migrator){
48 | return migrator().deleteTable('test_table2');
49 | };
50 |
--------------------------------------------------------------------------------
/test/migrations/20150819155839_alter_test_table2.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.up = function(migrator, config){
4 | return migrator().updateTable('test_table2', function(t){
5 | t.globalSecondaryIndexUpdates(function(t){
6 | t.delete('indexName2');
7 | t.update('indexName1', function(t){
8 | t.provisionedThroughput(20, 20);
9 | });
10 | });
11 | }).then(function(){
12 | return migrator().waitUntilTableActivate('test_table2');
13 | });
14 | };
15 |
16 | exports.down = function(migrator, config){
17 | return migrator().updateTable('test_table2', function(t){
18 | t.globalSecondaryIndexUpdates(function(t){
19 | t.create('indexName2', function(t){
20 | t.number('gsi_hash_key2').hashKey();
21 | t.provisionedThroughput(100, 100);
22 | t.projectionTypeAll();
23 | });
24 | t.update('indexName1', function(t){
25 | t.provisionedThroughput(10, 10);
26 | });
27 | });
28 | });
29 | };
30 |
--------------------------------------------------------------------------------
/test/migrations/20150819155840_alter_test_table1.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.up = function(migrator, config){
4 | return migrator().updateTable('test_table1', function(t){
5 | t.globalSecondaryIndexUpdates(function(t){
6 | t.create('indexName3', function(t){
7 | t.string('hash_key2').hashKey();
8 | t.provisionedThroughput(100, 100);
9 | t.projectionTypeAll();
10 | });
11 | });
12 | }).then(function(){
13 | return migrator().waitUntilTableActivate('test_table1');
14 | });
15 | };
16 |
17 | exports.down = function(migrator, config){
18 | return migrator().updateTable('test_table1', function(t){
19 | t.globalSecondaryIndexUpdates(function(t){
20 | t.delete('indexName3');
21 | });
22 | });
23 | };
24 |
--------------------------------------------------------------------------------
/test/migrations/20150819155841_alter_test_table1_2.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.up = function(migrator, config){
4 | return migrator().updateTable('test_table1', function(t){
5 | t.globalSecondaryIndexUpdates(function(t){
6 | t.create('indexName4', function(t){
7 | t.string('hash_key3').hashKey();
8 | t.provisionedThroughput(5, 5);
9 | t.projectionTypeAll();
10 | });
11 | });
12 | }).then(function(){
13 | return migrator().waitUntilTableActivate('test_table1');
14 | });
15 | };
16 |
17 | exports.down = function(migrator, config){
18 | return migrator().updateTable('test_table1', function(t){
19 | t.globalSecondaryIndexUpdates(function(t){
20 | t.delete('indexName4');
21 | });
22 | });
23 | };
24 |
--------------------------------------------------------------------------------
/test/migrator_spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var chai = require('chai');
4 | var expect = chai.expect;
5 | var Promise = require('bluebird');
6 |
7 | var SchemaBuilder = require('../lib/schema/builder');
8 | var Migrator = require('../lib/migrate/migrator');
9 | var npdynamodb = require('../index');
10 |
11 | var dynamodb = require('./dynamodb_2012_08_10');
12 | var npd = npdynamodb.createClient(dynamodb);
13 |
14 | var migrator = new Migrator.Runner({
15 | cwd: __dirname + "/migrations",
16 | dynamoClient: dynamodb,
17 | migrations: {
18 | ProvisionedThroughput: [10, 10],
19 | tableName: 'npd_migrations_for_testing'
20 | }
21 | });
22 |
23 | describe('Migrator', function(){
24 |
25 | before(function(done){
26 | Promise.all([
27 | npd().rawClient().deleteTable({ TableName: 'test_table1'}),
28 | npd().rawClient().deleteTable({ TableName: 'test_table2' }),
29 | npd().rawClient().deleteTable({ TableName: 'npd_migrations_for_testing' })
30 | ])
31 | .then(function(){
32 | done();
33 | })
34 | .catch(function(){
35 | done();
36 | });
37 | });
38 |
39 | describe('run', function(){
40 | it("Should create test_table1 and test_table2", function(done){
41 | migrator.run().then(function(data){
42 | return Promise.all([
43 | npd().table('test_table1').describe(),
44 | npd().table('test_table2').describe(),
45 | ]);
46 | })
47 | .then(function(tables){
48 | expect(tables[0].Table.TableName).to.equal('test_table1');
49 | expect(tables[1].Table.TableName).to.equal('test_table2');
50 | done();
51 | })
52 | .catch(function(err){
53 | done(err);
54 | });
55 | });
56 | });
57 |
58 | describe('rollback', function(){
59 | it("Should delete test_table2 and remove value of 20150404071722 from npd_migrations_for_testing", function(done){
60 | migrator.migrator().waitUntilTableActivate("npd_migrations_for_testing")
61 | .then(function(){
62 | // 20150819155841_alter_test_table1_2
63 | return migrator.rollback();
64 | })
65 | .then(function(){
66 | // 20150819155840_alter_test_table1
67 | return migrator.rollback();
68 | })
69 | .then(function(){
70 | // 20150819155839_alter_test_table2
71 | return migrator.rollback();
72 | })
73 | .then(function(){
74 | // 20150404071722_create_test_table2
75 | return migrator.rollback();
76 | })
77 | .then(function(){
78 | // wait for the test_table2 deletion
79 | return new Promise(function(resolve){
80 | setTimeout(resolve, 2000);
81 | });
82 | })
83 | .then(function(){
84 | npd().table('test_table2').describe()
85 | .then(function(data){
86 | done(new Error('Here is never called'));
87 | })
88 | .catch(function(err){
89 | expect(err.name).to.equal('ResourceNotFoundException');
90 | npd().table('npd_migrations_for_testing').where('version', 20150404071722)
91 | .then(function(data){
92 | expect(data.Count).to.equal(0);
93 | done();
94 | });
95 | });
96 | });
97 | });
98 | });
99 | });
100 |
--------------------------------------------------------------------------------
/test/orm_spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var npdynamodb = require('../index');
4 | var chai = require('chai');
5 | var expect = chai.expect;
6 |
7 | var npd = npdynamodb.createClient(require('./dynamodb_2012_08_10'));
8 |
9 | var Chat = npdynamodb.define('chats', {
10 | npdynamodb: npd,
11 |
12 | hashKey: 'room_id',
13 |
14 | rangeKey: 'timestamp',
15 |
16 | customProtoConstant: 1,
17 |
18 | customProtoMethod: function(){
19 | return this.get('timestamp') === 1429291245;
20 | }
21 | },
22 |
23 | {
24 |
25 | customStaticConstant: 1,
26 |
27 | customStaticMethod: function(){
28 | return this.where('room_id', 'room1')
29 | .query(function(qb){
30 | qb.filter('timestamp', '>', 1429291245);
31 | })
32 | .fetch();
33 | }
34 |
35 | });
36 |
37 | describe('ORM', function(){
38 |
39 | beforeEach(function(done){
40 | npd().rawClient().createTable(require('./data/test_tables').chats)
41 | .then(function(data){
42 | return npd().table('chats').create([
43 | {
44 | room_id: "room1",
45 | timestamp: 1429291245,
46 | message: 'This is first message'
47 | },
48 | {
49 | room_id: "room1",
50 | timestamp: 1429291246,
51 | message: 'This is second message'
52 | }
53 | ])
54 | .then(function(){
55 | done();
56 | });
57 | })
58 | .catch(function(err){
59 | done(err);
60 | });
61 | });
62 |
63 | afterEach(function(done){
64 | npd().rawClient().deleteTable({
65 | TableName: 'chats'
66 | })
67 | .then(function(){
68 | done();
69 | })
70 | .catch(function(err){
71 | done(err);
72 | });
73 | });
74 |
75 | it('Should get a row with hash and range key', function(done){
76 | Chat.find("room1", 1429291245)
77 | .then(function(chat){
78 | expect(chat.get('room_id')).to.equal('room1');
79 | expect(chat.get('timestamp')).to.equal(1429291245);
80 | done();
81 | })
82 | .catch(function(err){
83 | done(err);
84 | });
85 | });
86 |
87 | it('Should get item collection and processing it with functional apis', function(done){
88 | Chat.query(function(qb){
89 | qb.where('room_id', 'room1');
90 | })
91 | .fetch()
92 | .then(function(chatCollection){
93 | expect(chatCollection.pluck("room_id")).to.deep.equal(['room1', 'room1']);
94 | return chatCollection;
95 | })
96 | .then(function(chatCollection){
97 | var chat = chatCollection.first();
98 | expect(chat.get("room_id")).to.equal('room1');
99 | expect(chat.get("timestamp")).to.equal(1429291245);
100 | return chatCollection;
101 | })
102 | .then(function(chatCollection){
103 | var chat = chatCollection.last();
104 | expect(chat.get("room_id")).to.equal('room1');
105 | expect(chat.get("timestamp")).to.equal(1429291246);
106 | return chatCollection;
107 | })
108 | .then(function(chatCollection){
109 | var chat = chatCollection.filter(function(chat){
110 | return chat.get('timestamp') == 1429291245;
111 | });
112 | expect(chat.length).to.equal(1);
113 | expect(chat[0].get("timestamp")).to.equal(1429291245);
114 | return chatCollection;
115 | })
116 | .then(function(chatCollection){
117 | var chats = chatCollection.map(function(chat){
118 | chat.set('timestamp', chat.get('timestamp') -1);
119 | return chat;
120 | });
121 | expect(chats[0].get("timestamp")).to.equal(1429291244);
122 | expect(chats[1].get("timestamp")).to.equal(1429291245);
123 | return chatCollection;
124 | })
125 | .then(function(chatCollection){
126 | expect(chatCollection.toArray().length).to.equal(2);
127 | return chatCollection;
128 | })
129 | .then(function(){
130 | done();
131 | })
132 | .catch(function(err){
133 | done(err);
134 | });
135 | });
136 |
137 | it('Should get multiple rows with whereIn(batchGetItem)', function(done){
138 | Chat.query(function(qb){
139 | qb.whereIn(['room_id', 'timestamp'], [['room1', 1429291245], ['room1', 1429291246]]);
140 | })
141 | .fetch()
142 | .then(function(chats){
143 | expect(chats.at(0).get('timestamp')).to.equal(1429291246);
144 | expect(chats.at(1).get('timestamp')).to.equal(1429291245);
145 | done();
146 | })
147 | .catch(function(err){
148 | done(err);
149 | });
150 | });
151 |
152 | describe('save', function(){
153 |
154 | it('Should save an item statically', function(done){
155 | Chat.save({
156 | room_id: "room2",
157 | timestamp: 1429291247,
158 | message: 'This is message'
159 | })
160 | .then(function(chat){
161 | expect(chat.get('room_id')).to.equal('room2');
162 | expect(chat.get('timestamp')).to.equal(1429291247);
163 | expect(chat.get('message')).to.equal('This is message');
164 | done();
165 | })
166 | .catch(function(err){
167 | done(err);
168 | });
169 | });
170 |
171 | it("Should save an item from ORM Instance", function(done){
172 |
173 | var chat = new Chat({
174 | room_id: 'room2',
175 | timestamp: 1429291247
176 | });
177 |
178 | chat.set('message', 'This is message');
179 |
180 | chat.save()
181 | .then(function(chat){
182 | expect(chat.get('room_id')).to.equal('room2');
183 | expect(chat.get('timestamp')).to.equal(1429291247);
184 | expect(chat.get('message')).to.equal('This is message');
185 | return chat.set('message', "This is updated message").save();
186 | })
187 | .then(function(chat){
188 | //update
189 | expect(chat.get('message')).to.equal('This is updated message');
190 | done();
191 | })
192 | .catch(function(err){
193 | done(err);
194 | });
195 | });
196 |
197 | it("Should each models in collection save changes", function(done){
198 | Chat.where('room_id', 'room1').fetch().then(function(chats){
199 | return chats.first().set('message', 'this is a updated message').save()
200 | .then(function(chat){
201 | return chat.reload();
202 | });
203 | })
204 | .then(function(chat){
205 | expect(chat.get('message')).to.equal('this is a updated message');
206 | done();
207 | })
208 | .catch(done);
209 | });
210 |
211 | });
212 |
213 | describe('destroy', function(){
214 | it('Should destroy an item', function(done){
215 |
216 | Chat.save({
217 | room_id: "room2",
218 | timestamp: 1429291247,
219 | message: 'This is message'
220 | })
221 | .then(function(chat){
222 | return chat.destroy();
223 | })
224 | .then(function(data){
225 | Chat.find("room2", 1429291247).then(function(chat){
226 | expect(chat.isEmpty()).to.equal(true);
227 | done();
228 | });
229 | })
230 | .catch(function(err){
231 | done(err);
232 | });
233 | });
234 | });
235 |
236 | describe('Custom prototype method and props', function(){
237 | it('Custom property should equals with expected value', function(done){
238 | Chat.find("room1", 1429291245).then(function(chat){
239 | expect(chat.customProtoConstant).to.equal(1);
240 | done();
241 | });
242 | });
243 |
244 | it('Should call custom method and that result should be true', function(done){
245 | Chat.find("room1", 1429291245).then(function(chat){
246 | expect(chat.customProtoMethod()).to.equal(true);
247 | done();
248 | });
249 | });
250 |
251 | it('Should call custom method and that result should be expected values', function(done){
252 | Chat.where('room_id', 'room1').fetch().then(function(chats){
253 | expect(chats.indexAt(0).customProtoMethod()).to.equal(true);
254 | expect(chats.indexAt(1).customProtoMethod()).to.equal(false);
255 | done();
256 | });
257 | });
258 | });
259 |
260 | describe('Custom static method and props', function(){
261 | it('Custom property should equals with expected value', function(){
262 | expect(Chat.customStaticConstant).to.equal(1);
263 | });
264 |
265 | it('Should get rows with custom method', function(done){
266 | Chat.customStaticMethod().then(function(data){
267 | expect(data.pluck('timestamp')).to.deep.equal([1429291246]);
268 | done();
269 | });
270 | });
271 | });
272 |
273 | describe('Customizability', function(){
274 |
275 | it('Collection.prototype should be extended', function(done){
276 | var Collection = npdynamodb.Collection;
277 | Collection.prototype.pluckRoomId = function(){
278 | return this.pluck('room_id');
279 | };
280 |
281 | Chat.where('room_id', 'room1').fetch().then(function(collection){
282 | expect(collection.pluckRoomId()).to.deep.eq(['room1', 'room1']);
283 | done();
284 | });
285 | });
286 |
287 | it('Model.prototype should be extended', function(done){
288 | var Model = npdynamodb.Model;
289 | Model.prototype.relativeTime = function(){
290 | return parseInt(new Date() / 1000) - parseInt(this.get('timestamp'));
291 | };
292 |
293 | var Chat = npdynamodb.define('chats', {
294 | npdynamodb: npd,
295 |
296 | hashKey: 'room_id',
297 |
298 | rangeKey: 'timestamp',
299 | });
300 |
301 | Chat.find('room1', 1429291245).then(function(model){
302 | expect(model.relativeTime()).to.be.an('number');
303 | done();
304 | });
305 | });
306 | });
307 |
308 | });
309 |
--------------------------------------------------------------------------------
/test/query_builder_read_spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Promise = require('bluebird')
4 | var chai = require('chai');
5 | var expect = chai.expect;
6 | var _ = require('lodash');
7 |
8 | var npdynamodb = require('../index');
9 | var npd = npdynamodb.createClient(require('./dynamodb_2012_08_10'));
10 |
11 | var complexTableData = require('./data/complex_table_seed');
12 |
13 | function expectQueryResult(data, count, startIndex, done){
14 | expect(data.Count).to.equal(count);
15 | data.Items.forEach(function(item, i){
16 | expect(item.hash_key).to.equal('key1');
17 | expect(item.range_key).to.equal(startIndex+i);
18 | });
19 | done();
20 | }
21 |
22 | function expectQueryResultMinus(data, count, startIndex, done){
23 | expect(data.Count).to.equal(count);
24 | data.Items.forEach(function(item, i){
25 | expect(item.hash_key).to.equal('key1');
26 | expect(item.range_key).to.equal(startIndex-i);
27 | });
28 | done();
29 | }
30 |
31 | describe('QueryBuilder', function(){
32 | before(function(done){
33 | npd().rawClient().createTable(require('./data/test_tables').compex_table)
34 | .then(function(){
35 | return npd().table('complex_table').create(complexTableData);
36 | })
37 | .then(function(){
38 | done();
39 | })
40 | .catch(function(err){
41 | done(err);
42 | });
43 | });
44 |
45 | after(function(done){
46 | npd().rawClient().deleteTable({
47 | TableName: 'complex_table'
48 | })
49 | .then(function(){
50 | done();
51 | })
52 | .catch(function(err){
53 | done(err);
54 | });
55 | });
56 |
57 | describe('all', function(){
58 | it('Should get amount of rows', function(done){
59 | npd().table('complex_table').all()
60 | .then(function(data){
61 | expectQueryResult(data, 10, 1, done);
62 | })
63 | .catch(function(err){
64 | done(err);
65 | });
66 | });
67 | });
68 |
69 | describe('select', function(){
70 | it('Should get row with specified attributes', function(done){
71 | npd().table('complex_table')
72 | .select('document', 'numberSet')
73 | .where('hash_key', 'key1')
74 | .where('range_key', 1)
75 | .then(function(data){
76 | var item = data.Items[0];
77 | expect(item.hash_key).to.equal(undefined);
78 | expect(item).to.have.property("document");
79 | expect(item).to.have.property("numberSet");
80 | done();
81 | })
82 | .catch(function(err){
83 | done(err);
84 | });
85 | });
86 | });
87 |
88 | describe('where*', function(){
89 | it('Should find row with where(hash_key=key1 and range_key=1)', function(done){
90 | npd().table('complex_table')
91 | .where('hash_key', 'key1')
92 | .where('range_key', 1)
93 | .then(function(data){
94 | expectQueryResult(data, 1, 1, done);
95 | })
96 | .catch(function(err){
97 | done(err);
98 | });
99 | });
100 |
101 | it('Should find rows with where(hash_key=key1 and range_key > 8)', function(done){
102 | npd().table('complex_table')
103 | .where('hash_key', 'key1')
104 | .where('range_key', '>', 8)
105 | .then(function(data){
106 | expectQueryResult(data, 2, 9, done);
107 | })
108 | .catch(function(err){
109 | done(err);
110 | });
111 | });
112 |
113 | it('Should find rows with where(hash_key=key1 and range_key >= 8)', function(done){
114 | npd().table('complex_table')
115 | .where('hash_key', 'key1')
116 | .where('range_key', '>=', 8)
117 | .then(function(data){
118 | expectQueryResult(data, 3, 8, done);
119 | })
120 | .catch(function(err){
121 | done(err);
122 | });
123 | });
124 |
125 | it('Should find rows with where(hash_key=key1 and range_key <= 3)', function(done){
126 | npd().table('complex_table')
127 | .where('hash_key', 'key1')
128 | .where('range_key', '<=', 3)
129 | .then(function(data){
130 | expectQueryResult(data, 3, 1, done);
131 | })
132 | .catch(function(err){
133 | done(err);
134 | });
135 | });
136 |
137 | it('Should find rows with where(hash_key=key1 and range_key < 3)', function(done){
138 | npd().table('complex_table')
139 | .where('hash_key', 'key1')
140 | .where('range_key', '<', 3)
141 | .then(function(data){
142 | expectQueryResult(data, 2, 1, done);
143 | })
144 | .catch(function(err){
145 | done(err);
146 | });
147 | });
148 |
149 | it('Should find rows with whereBetween', function(done){
150 | npd().table('complex_table')
151 | .where('hash_key', 'key1')
152 | .whereBetween('range_key', 1, 3)
153 | .then(function(data){
154 | expectQueryResult(data, 3, 1, done);
155 | })
156 | .catch(function(err){
157 | done(err);
158 | });
159 | });
160 |
161 | it('Should take multiple items with whereIn and single key', function(done){
162 | var tableName = 'where_in_test';
163 | npd().rawClient().createTable({
164 | AttributeDefinitions: [
165 | { AttributeName: 'hk', AttributeType: 'S' },
166 | ],
167 | KeySchema: [
168 | { AttributeName: 'hk', KeyType: 'HASH' },
169 | ],
170 | TableName: tableName,
171 | ProvisionedThroughput: {
172 | ReadCapacityUnits: 100, WriteCapacityUnits: 100
173 | },
174 | })
175 | .then(function(data){
176 | return npd().table(tableName).create([
177 | {
178 | hk: 'key1',
179 | foo: 'bar'
180 | },
181 | {
182 | hk: 'key2',
183 | foo: 'bar'
184 | }
185 | ]);
186 | })
187 | .then(function(){
188 | return npd().table(tableName)
189 | .whereIn('hk', ['key1', 'key2'])
190 | .then(function(data){
191 | expect(data.Responses[tableName][0].hk).to.eq('key1');
192 | expect(data.Responses[tableName][1].hk).to.eq('key2');
193 | done();
194 | });
195 | })
196 | .catch(done)
197 | .finally(function(){
198 | return npd().rawClient().deleteTable({TableName: tableName});
199 | done();
200 | });
201 | });
202 |
203 | it('Should take multiple items with whereIn and multiple keys', function(done){
204 | npd().table('complex_table')
205 | .whereIn(['hash_key', 'range_key'], [['key1', 1], ['key1', 2]])
206 | .then(function(data){
207 | expect(data.Responses.complex_table.length).to.eq(2);
208 | done();
209 | })
210 | .catch(function(err){
211 | done(err);
212 | });
213 | });
214 |
215 | it('Should be raised error with using where and whereIn at the same time', function(done){
216 | npd().table('complex_table')
217 | .whereIn(['hash_key', 'range_key'], [['key1', 1], ['key1', 2]])
218 | .where('hash_key', 'key1')
219 | .then(function(data){
220 | throw new Error('Here is never called.');
221 | })
222 | .catch(function(err){
223 | expect(err).to.be.an.instanceof(Error);
224 | done();
225 | });
226 | });
227 | });
228 |
229 | describe('filter*', function(){
230 |
231 | it('Should find rows with filter(range_key = 1)', function(done){
232 | npd().table('complex_table')
233 | .where('hash_key', 'key1')
234 | .filter('range_key', '=', 1)
235 | .then(function(data){
236 | expectQueryResult(data, 1, 1, done);
237 | })
238 | .catch(function(err){
239 | done(err);
240 | });
241 | });
242 |
243 | it('Should find rows with filter(range_key != 1)', function(done){
244 | npd().table('complex_table')
245 | .where('hash_key', 'key1')
246 | .filter('range_key', '!=', 1)
247 | .then(function(data){
248 | expectQueryResult(data, 9, 2, done);
249 | })
250 | .catch(function(err){
251 | done(err);
252 | });
253 | });
254 |
255 | it('Should find rows with filter(range_key > 8)', function(done){
256 | npd().table('complex_table')
257 | .where('hash_key', 'key1')
258 | .filter('range_key', '>', 8)
259 | .then(function(data){
260 | expectQueryResult(data, 2, 9, done);
261 | })
262 | .catch(function(err){
263 | done(err);
264 | });
265 | });
266 |
267 | it('Should find rows with filter(range_key >= 8)', function(done){
268 | npd().table('complex_table')
269 | .where('hash_key', 'key1')
270 | .filter('range_key', '>=', 8)
271 | .then(function(data){
272 | expectQueryResult(data, 3, 8, done);
273 | })
274 | .catch(function(err){
275 | done(err);
276 | });
277 | });
278 |
279 | it('Should find rows with filter(range_key <= 3)', function(done){
280 | npd().table('complex_table')
281 | .where('hash_key', 'key1')
282 | .filter('range_key', '<=', 3)
283 | .then(function(data){
284 | expectQueryResult(data, 3, 1, done);
285 | })
286 | .catch(function(err){
287 | done(err);
288 | });
289 | });
290 |
291 | it('Should find rows with filter(range_key < 3)', function(done){
292 | npd().table('complex_table')
293 | .where('hash_key', 'key1')
294 | .filter('range_key', '<', 3)
295 | .then(function(data){
296 | expectQueryResult(data, 2, 1, done);
297 | })
298 | .catch(function(err){
299 | done(err);
300 | });
301 | });
302 |
303 |
304 | it('Should find rows with filterIn(range_key (1,2))', function(done){
305 | npd().table('complex_table')
306 | .where('hash_key', 'key1')
307 | .filterIn('range_key', 1, 2)
308 | .then(function(data){
309 | expectQueryResult(data, 2, 1, done);
310 | })
311 | .catch(function(err){
312 | done(err);
313 | });
314 | });
315 |
316 | it('Should find rows with filterBeginsWith(gsi_hash_key "g")', function(done){
317 | npd().table('complex_table')
318 | .where('hash_key', 'key1')
319 | .filterBeginsWith('gsi_hash_key', 'g')
320 | .then(function(data){
321 | expectQueryResult(data, 10, 1, done);
322 | })
323 | .catch(function(err){
324 | done(err);
325 | });
326 | });
327 |
328 | it('Should find rows with filterBeginsWith(gsi_hash_key "hoge")', function(done){
329 | npd().table('complex_table')
330 | .where('hash_key', 'key1')
331 | .filterBeginsWith('gsi_hash_key', 'hoge')
332 | .then(function(data){
333 | expect(data.Count).to.equal(0);
334 | done();
335 | })
336 | .catch(function(err){
337 | done(err);
338 | });
339 | });
340 |
341 | it('Should find rows with filterBetween(range_key 1..5 )', function(done){
342 | npd().table('complex_table')
343 | .where('hash_key', 'key1')
344 | .filterBetween('range_key', 1, 5)
345 | .then(function(data){
346 | expectQueryResult(data, 5, 1, done);
347 | })
348 | .catch(function(err){
349 | done(err);
350 | });
351 | });
352 |
353 | it('Should find rows with filterContains(stringSet "foo")', function(done){
354 | npd().table('complex_table')
355 | .where('hash_key', 'key1')
356 | .filterContains('stringSet', "foo")
357 | .then(function(data){
358 | expectQueryResult(data, 10, 1, done);
359 | })
360 | .catch(function(err){
361 | done(err);
362 | });
363 | });
364 |
365 | it('Should find rows with filterContains(numberSet 2)', function(done){
366 | npd().table('complex_table')
367 | .where('hash_key', 'key1')
368 | .filterContains('numberSet', 2)
369 | .then(function(data){
370 | expectQueryResult(data, 10, 1, done);
371 | })
372 | .catch(function(err){
373 | done(err);
374 | });
375 | });
376 |
377 | it('Should find rows with filterNotContains(stringSet "foo")', function(done){
378 | npd().table('complex_table')
379 | .where('hash_key', 'key1')
380 | .filterNotContains('stringSet', "foo")
381 | .then(function(data){
382 | expect(data.Count).to.equal(0);
383 | done();
384 | })
385 | .catch(function(err){
386 | done(err);
387 | });
388 | });
389 |
390 | it('Should find rows with filterNotContains(numberSet 2)', function(done){
391 | npd().table('complex_table')
392 | .where('hash_key', 'key1')
393 | .filterNotContains('numberSet', 2)
394 | .then(function(data){
395 | expect(data.Count).to.equal(0);
396 | done();
397 | })
398 | .catch(function(err){
399 | done(err);
400 | });
401 | });
402 |
403 | it('Should find rows with filterNull(document)', function(done){
404 | npd().table('complex_table')
405 | .where('hash_key', 'key1')
406 | .filterNull('document')
407 | .then(function(data){
408 | expect(data.Count).to.equal(0);
409 | done();
410 | })
411 | .catch(function(err){
412 | done(err);
413 | });
414 | });
415 |
416 | it('Should find rows with filterNotNull(document)', function(done){
417 | npd().table('complex_table')
418 | .where('hash_key', 'key1')
419 | .filterNotNull('document')
420 | .then(function(data){
421 | expectQueryResult(data, 10, 1, done);
422 | })
423 | .catch(function(err){
424 | done(err);
425 | });
426 | });
427 | });
428 |
429 | describe('count', function(){
430 | it('Num of rows Should be same as specified limit', function(done){
431 | npd().table('complex_table')
432 | .where('hash_key', 'key1')
433 | .count()
434 | .then(function(data){
435 | expect(data.Count).to.equal(10);
436 | done();
437 | })
438 | .catch(function(err){
439 | done(err);
440 | });
441 | });
442 |
443 | it('Num of rows Should be same as specified limit', function(done){
444 | npd().table('complex_table')
445 | .where('hash_key', 'key1')
446 | .count()
447 | .limit(5)
448 | .then(function(data){
449 | expect(data.Count).to.equal(5);
450 | done();
451 | })
452 | .catch(function(err){
453 | done(err);
454 | });
455 | });
456 | });
457 |
458 | describe('limit, offset', function(){
459 | it('Num of rows should be same as specified limit', function(done){
460 | npd().table('complex_table')
461 | .where('hash_key', 'key1')
462 | .limit(4)
463 | .then(function(data){
464 | expect(data.Count).to.equal(4);
465 | done();
466 | })
467 | .catch(function(err){
468 | done(err);
469 | });
470 | });
471 |
472 |
473 | it('Should get rows that are greater than LastEvaluatedKey', function(done){
474 | npd().table('complex_table')
475 | .where('hash_key', 'key1')
476 | .limit(5)
477 | .then(function(data){
478 | expect(data.LastEvaluatedKey).to.deep.equal({ range_key: 5, hash_key: 'key1' });
479 | return npd().table('complex_table')
480 | .where('hash_key', 'key1')
481 | .limit(5)
482 | .offset(data.LastEvaluatedKey)
483 | .then(function(data){
484 | expect(data.LastEvaluatedKey).to.deep.equal({ range_key: 10, hash_key: 'key1' });
485 | done();
486 | });
487 | })
488 | .catch(function(err){
489 | done(err);
490 | });
491 | });
492 | });
493 |
494 | describe('order', function(){
495 |
496 | it('Order Should be ascending', function(done){
497 | npd().table('complex_table')
498 | .where('hash_key', 'key1')
499 | .limit(4)
500 | .asc()
501 | .then(function(data){
502 | expectQueryResult(data, 4, 1, done);
503 | })
504 | .catch(function(err){
505 | done(err);
506 | });
507 | });
508 |
509 |
510 | it('Order Should be descending', function(done){
511 | npd().table('complex_table')
512 | .where('hash_key', 'key1')
513 | .limit(4)
514 | .desc()
515 | .then(function(data){
516 | expectQueryResultMinus(data, 4, 10, done);
517 | })
518 | .catch(function(err){
519 | done(err);
520 | });
521 | });
522 | });
523 |
524 | describe('indexName', function(){
525 | it('Should find rows by GlobalSecondaryIndex', function(done){
526 | npd().table('complex_table')
527 | .where('gsi_hash_key', 'gkey1')
528 | .indexName('indexName1')
529 | .then(function(data){
530 | var item = data.Items[0];
531 | expect(item.gsi_hash_key).to.equal('gkey1');
532 | done();
533 | })
534 | .catch(function(err){
535 | done(err);
536 | });
537 | });
538 |
539 | it('Should find rows by LocalSecondaryIndex', function(done){
540 | npd().table('complex_table')
541 | .where('hash_key', 'key1')
542 | .where('lsi_range_key', 1)
543 | .indexName('indexName2')
544 | .then(function(data){
545 | var item = data.Items[0];
546 | expect(item.hash_key).to.equal("key1");
547 | expect(item.lsi_range_key).to.equal(1);
548 | done();
549 | })
550 | .catch(function(err){
551 | done(err);
552 | });
553 | });
554 | });
555 |
556 | describe('events', function(){
557 | it('Should detect beforeQuery and afterQuery events', function(done){
558 | npd().table('complex_table')
559 | .where('hash_key', 'key1')
560 | .where('range_key', 1)
561 | .on('beforeQuery', function(params){
562 | expect(params).to.have.property("TableName");
563 | expect(params).to.have.property("KeyConditions");
564 | })
565 | .on('afterQuery', function(result){
566 | expect(result.Items[0].hash_key).to.equal("key1");
567 | expect(result.Items[0].range_key).to.equal(1);
568 | })
569 | .then(function(data){
570 | done();
571 | })
572 | .catch(function(err){
573 | done(err);
574 | });
575 | });
576 | });
577 |
578 | describe('options.timeout', function(){
579 | var AWS = require('aws-sdk');
580 |
581 | it('Should handle timeout', function(done){
582 | var config = {
583 | apiVersion: '2012-08-10',
584 | accessKeyId: "AWS_KEY",
585 | secretAccessKey: "AWS_SECRET",
586 | sslEnabled: false,
587 | region: "ap-northeast-1",
588 | endpoint: 'invalid.host'
589 | };
590 |
591 | var npd = npdynamodb.createClient(new AWS.DynamoDB(config), {
592 | timeout: 1000
593 | });
594 |
595 | npd().table('complex_table').all().then(function(data){
596 | throw new Error('Here is never called.');
597 | })
598 | .catch(function(err){
599 | expect(err.toString()).to.eq('Error: The connection has timed out.');
600 | done();
601 | });
602 | });
603 | });
604 |
605 | describe('options.callbacks', function(){
606 | it('Callbacks of beforeQuery and afterQuery should be triggered', function(done){
607 | var npd = npdynamodb.createClient(require('./dynamodb_2012_08_10'), {
608 | initialize: function(){
609 | this.callbacks('beforeQuery', function(){
610 | if(this._feature.whereConditions[1]) {
611 | this._feature.whereConditions[1].values = [parseInt(this._feature.whereConditions[1].values[0]())];
612 | }
613 | });
614 |
615 | this.callbacks('afterQuery', function(result){
616 | if(result.Items) {
617 | result.Items[0].hex = result.Items[0].binary.toString('hex');
618 | result.Items[0].str = result.Items[0].binary.toString('utf8');
619 | return npd().table('complex_table')
620 | .on('afterQuery', function(){
621 | expect(this._feature.params.hash_key).to.eq('key10');
622 | })
623 | .create({
624 | hash_key: "key10",
625 | range_key: 5,
626 | });
627 | }
628 | });
629 | }
630 | });
631 |
632 | npd().table('complex_table').where('hash_key', 'key1').where('range_key', function(){
633 | return '1';
634 | })
635 | .on('beforeQuery', function(params){
636 | expect(params.KeyConditions[1].val1).to.eq(1);
637 | })
638 | .on('afterQuery', function(result){
639 | expect(result.Items[0].hex).to.eq('01020304');
640 | expect(result.Items[0].str).to.eq('\u0001\u0002\u0003\u0004');
641 | })
642 | .then(function(result){
643 | done();
644 | })
645 | .catch(done);
646 | });
647 | });
648 | });
649 |
--------------------------------------------------------------------------------
/test/query_builder_write_spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var chai = require('chai');
4 | var expect = chai.expect;
5 |
6 | var npdynamodb = require('../index');
7 | var npd = npdynamodb.createClient(require('./dynamodb_2012_08_10'));
8 |
9 | var complexTableData = require('./data/complex_table_seed');
10 |
11 | describe('QueryBuilder', function(){
12 |
13 | beforeEach(function(done){
14 | npd().rawClient().createTable(require('./data/test_tables').chats)
15 | .then(function(){
16 | done();
17 | })
18 | .catch(done)
19 | });
20 |
21 | afterEach(function(done){
22 | npd().rawClient().deleteTable({
23 | TableName: 'chats'
24 | })
25 | .then(function(){
26 | done();
27 | })
28 | .catch(done);
29 | });
30 |
31 | describe('create', function(){
32 |
33 | it('Should Create a new row', function(done){
34 | npd().table('chats')
35 | // .feature(function(f){
36 | // f.returnConsumedCapacity('TOTAL');
37 | // f.returnItemCollectionMetrics('SIZE');
38 | // f.returnValues('ALL_OLD');
39 | // })
40 | .create({
41 | room_id: "room1",
42 | timestamp: 1429291245,
43 | message: 'This is message'
44 | })
45 | .then(function(data){
46 | done();
47 | })
48 | .catch(done);
49 | });
50 |
51 | it('Should Update existing row', function(done){
52 |
53 | npd().table('chats')
54 | .create({
55 | room_id: "room1",
56 | timestamp: 1429291245,
57 | message: 'This is message'
58 | })
59 | .then(function(){
60 | return npd().table('chats')
61 | .create({
62 | room_id: "room1",
63 | timestamp: 1429291245,
64 | message: 'This is updated message'
65 | });
66 | })
67 | .then(function(data){
68 | npd().table('chats')
69 | .where('room_id', 'room1')
70 | .where('timestamp', 1429291245)
71 | .then(function(data){
72 | expect(data.Items[0].message).to.equal('This is updated message')
73 | done();
74 | });
75 | })
76 | .catch(done);
77 | });
78 |
79 | it('Should batch create rows', function(done){
80 | npd().table('chats')
81 | .create([
82 | {
83 | room_id: "room1",
84 | timestamp: 1429291246,
85 | message: 'This is first message'
86 | },
87 | {
88 | room_id: "room1",
89 | timestamp: 1429291247,
90 | message: 'This is second message'
91 | },
92 | {
93 | room_id: "room1",
94 | timestamp: 1429291248,
95 | message: 'This is third message2'
96 | }
97 | ])
98 | .then(function(data){
99 | expect(Object.keys(data.UnprocessedItems).length).to.equal(0);
100 | done();
101 | })
102 | .catch(done);
103 | });
104 | });
105 |
106 | describe('update', function(){
107 |
108 | it('Should update message and add user_name field by update method', function(done){
109 | npd().table('chats')
110 | .create([
111 | {
112 | room_id: "room1",
113 | timestamp: 1429291246,
114 | message: 'This is first message',
115 | user: {
116 | name: "Tonny",
117 | age: 40
118 | }
119 | }
120 | ])
121 | .then(function(){
122 | return npd().table('chats')
123 | .where("room_id", "room1")
124 | .where('timestamp', 1429291246)
125 | .set("user", "PUT", {name: 'rhodes', age: 45})
126 | .set("gender_type", "ADD", 1)
127 | .feature(function(f){
128 | f.returnValues('UPDATED_NEW');
129 | })
130 | .update();
131 | })
132 | .then(function(data){
133 | expect(data).to.deep.equal({ Attributes: { gender_type: 1, user: { name: 'rhodes', age: 45 } } });
134 | done();
135 | })
136 | .catch(done);
137 | });
138 |
139 | it('Should update row with expressions', function(done){
140 | npd().table('chats')
141 | .create([
142 | {
143 | room_id: "room1",
144 | timestamp: 1429291246,
145 | message: 'This is first message',
146 | user: {
147 | name: "Tonny"
148 | }
149 | }
150 | ])
151 | .then(function(){
152 | return npd().table('chats')
153 | .where("room_id", "room1")
154 | .where('timestamp', 1429291245)
155 | .feature(function(f){
156 | f.updateExpression('SET #gt = if_not_exists(#gt, :one)');
157 |
158 | f.expressionAttributeNames({
159 | '#gt': 'gender_type'
160 | });
161 |
162 | f.expressionAttributeValues({
163 | ':one': 1
164 | });
165 | f.returnValues('UPDATED_NEW');
166 | })
167 | .update();
168 | })
169 | .then(function(data){
170 | expect(data).to.deep.equal({ Attributes: { gender_type: 1 } });
171 | done();
172 | })
173 | .catch(done);
174 | });
175 | });
176 |
177 | describe('delete', function(){
178 | it('Should delete a row', function(done){
179 | npd().table('chats')
180 | .create({
181 | room_id: "room1",
182 | timestamp: 1429291245,
183 | message: 'This is message'
184 | })
185 | .then(function(){
186 | return npd().table('chats').where('room_id', 'room1').where('timestamp', 1429291245).delete();
187 | })
188 | .then(function(data){
189 | npd().table('chats')
190 | .where('room_id', 'room1')
191 | .where('timestamp', 1429291245)
192 | .then(function(data){
193 | expect(data.Count).to.equal(0);
194 | done();
195 | });
196 | })
197 | .catch(done);
198 | });
199 | });
200 |
201 | });
202 |
--------------------------------------------------------------------------------
/test/schema_spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var chai = require('chai');
4 | var expect = chai.expect;
5 |
6 | var SchemaBuilder = require('../lib/schema/builder');
7 |
8 | describe('Schema', function(){
9 |
10 | it("Should build schema(which contain globalSecondaryIndex and localSecondaryIndex) for createTable operation for 2012-08-10 api", function(){
11 | var t = new SchemaBuilder({
12 | apiVer: '2012-08-10',
13 | tableName: "complex_table"
14 | });
15 |
16 | t.string('hash_key').hashKey();
17 | t.binary('range_key').rangeKey();
18 | t.provisionedThroughput(100, 100);
19 |
20 | t.globalSecondaryIndex('indexName1', function(t){
21 | t.string('gsi_hash_key').hashKey();
22 | t.provisionedThroughput(100, 100);
23 | t.projectionTypeAll();
24 | });
25 |
26 | t.globalSecondaryIndex('indexName2', function(t){
27 | t.number('gsi_hash_key2').hashKey();
28 | t.provisionedThroughput(100, 100);
29 | t.projectionTypeAll();
30 | });
31 |
32 | t.globalSecondaryIndex('indexName3', function(t){
33 | t.binary('gsi_hash_key3').hashKey();
34 | t.provisionedThroughput(100, 100);
35 | t.projectionTypeAll();
36 | });
37 |
38 | t.localSecondaryIndex('indexName4', function(t){
39 | t.string('hash_key').hashKey();
40 | t.number('lsi_range_key').rangeKey();
41 | t.projectionTypeAll();
42 | });
43 |
44 | t.localSecondaryIndex('indexName5', function(t){
45 | t.string('hash_key').hashKey();
46 | t.string('lsi_range_key2').rangeKey();
47 | t.projectionTypeAll();
48 | });
49 |
50 | t.localSecondaryIndex('indexName6', function(t){
51 | t.string('hash_key').hashKey();
52 | t.binary('lsi_range_key3').rangeKey();
53 | t.projectionTypeAll();
54 | });
55 |
56 | expect(t.buildCreateTable()).to.deep.equal(require('./data/test_tables').for_schema_test);
57 | });
58 |
59 | });
60 |
--------------------------------------------------------------------------------