├── .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 [![npm version](https://badge.fury.io/js/npdynamodb.svg)](http://badge.fury.io/js/npdynamodb) [![Code Climate](https://codeclimate.com/github/noppoMan/npdynamodb/badges/gpa.svg)](https://codeclimate.com/github/noppoMan/npdynamodb) [![Build Status](https://travis-ci.org/noppoMan/npdynamodb.svg?branch=master)](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 | --------------------------------------------------------------------------------