├── .gitignore ├── LICENSE ├── Makefile ├── ReadMe.md ├── examples ├── delete_item.js ├── describe_table.js ├── get_item.js ├── list_tables.js ├── put_item.js ├── scan.js └── update_item.js ├── index.js ├── lib └── ddb.js ├── package.json └── test ├── batch.js ├── config.js ├── integration.item.js └── unit.toDDB.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | node_modules 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Totems Tech 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | unit: 2 | node test/unit.toDDB.js 3 | 4 | integration: 5 | node test/integration.item.js 6 | 7 | batch: 8 | node test/batch.js 9 | 10 | test: unit integration batch 11 | 12 | .PHONY: test 13 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | ## Basics 2 | 3 | A Simple, efficient and complete DynamoDB driver for Node.js, with: 4 | 5 | - Syntactic sweeteners/mapping to handle Amazon DynamoDB's JSON format in NodeJS 6 | - Efficient and transparent authentication and authentication refresh using Amazon STS 7 | - Integrated retry logic similar to Amazon's own PhP, Java libraries 8 | - Currently in production in multiple independent organizations with more than 80 write/s, 60 read/s 9 | - Support for all exposed DynamoDB operations 10 | 11 | Discussion Group: http://groups.google.com/group/node-dynamodb 12 | 13 | ## Usage 14 | 15 | var ddb = require('dynamodb').ddb({ accessKeyId: '', 16 | secretAccessKey: '' }); 17 | 18 | Available options for the constructor are the following: 19 | 20 | - `accessKeyId`: the AWS AccessKeyId to use 21 | - `secretAccessKey`: the AWS SecretAccessKey associated 22 | - `endpoint`: the Region endpoint to use (defaults to: `dynamodb.us-east-1.amazonaws.com`) 23 | - `agent`: The NodeJS http.Agent to use (defaults to: `undefined`) 24 | - `sessionToken`: forced temporary session credential (defaults to: `undefined`) 25 | - `sessionExpires`: forced temeporary session credential (defaults to: `undefined`) 26 | 27 | 28 | ### CreateTable 29 | 30 | ddb.createTable('foo', { hash: ['id', ddb.schemaTypes().string], 31 | range: ['time', ddb.schemaTypes().number] }, 32 | {read: 10, write: 10}, function(err, details) {}); 33 | 34 | // res: { "CreationDateTime": 1.310506263362E9, 35 | // "KeySchema": { "HashKeyElement": { "AttributeName": "AttributeName1", 36 | // "AttributeType": "S"}, 37 | // "RangeKeyElement": { "AttributeName": "AttributeName2", 38 | // "AttributeType": "N"} }, 39 | // "ProvisionedThroughput":{ "ReadCapacityUnits": 5, 40 | // "WriteCapacityUnits": 10 }, 41 | // "TableName":"Table1", 42 | // "TableStatus":"CREATING" } 43 | 44 | ### ListTables 45 | 46 | ddb.listTables({}, function(err, res) {}); 47 | 48 | // res: { LastEvaluatedTableName: 'bar', 49 | TableNames: ['test','foo','bar'] } 50 | 51 | ### DescribeTable 52 | 53 | ddb.describeTable('a-table', function(err, res) {}); 54 | 55 | // res: { ... } 56 | 57 | ### PutItem 58 | 59 | // flat [string, number, string array or number array] based json object 60 | var item = { score: 304, 61 | date: (new Date).getTime(), 62 | sha: '3d2d6963', 63 | usr: 'spolu', 64 | lng: ['node', 'c++'] }; 65 | 66 | ddb.putItem('a-table', item, {}, function(err, res, cap) {}); 67 | 68 | ### GetItem 69 | 70 | ddb.getItem('a-table', '3d2d6963', null, {}, function(err, res, cap) {}); 71 | 72 | // res: { score: 304, 73 | // date: 123012398234, 74 | // sha: '3d2d6963', 75 | // usr: 'spolu', 76 | // lng: ['node', 'c++'] }; 77 | 78 | ### DeleteItem 79 | 80 | ddb.deleteItem('a-table', 'sha', null, {}, function(err, res, cap) {}); 81 | 82 | ### UpdateItem 83 | 84 | ddb.updateItem('a-table', '3d2d6963', null, { 'usr': { value: 'smthg' } }, {}, 85 | function(err, res, cap) {}); 86 | 87 | ddb.consumedCapacity(); 88 | 89 | ### BatchGetItem 90 | 91 | ddb.batchGetItem({'table': { keys: ['foo', 'bar'] }}, function(err, res) { 92 | if(err) { 93 | console.log(err); 94 | } else { 95 | console.log(res); 96 | } 97 | }); 98 | 99 | // res: { ConsumedCapacityUnits: 1.5, 100 | items: [...] }; 101 | 102 | ### BatchWriteItem 103 | 104 | //ddb.batchWriteItem(PutRequest, DeleteRequest, cb) 105 | ddb.batchWriteItem({'table': [item1, item2]}, {'table': ['foo', 'bar']}, function(err, res) { 106 | if(err) { 107 | console.log(err); 108 | } else { 109 | console.log(res); 110 | } 111 | }); 112 | 113 | // res: { UnprocessedItems: { ... } }; 114 | 115 | ### Query 116 | 117 | ddb.query('test', '3d2d6963', {}, function(err, res, cap) {...}); 118 | 119 | // res: { count: 23, 120 | // lastEvaluatedKey: { hash: '3d2d6963' }, 121 | // items: [...] }; 122 | 123 | ### Scan 124 | 125 | ddb.scan('test', {}, function(err, res) { 126 | if(err) { 127 | console.log(err); 128 | } else { 129 | console.log(res); 130 | } 131 | }); 132 | 133 | // res: { count: 23, 134 | // lastEvaluatedKey: { hash: '3d2d6963' }, 135 | // items: [...] }; 136 | 137 | 138 | 139 | More complete usage can be found in the examples directory 140 | 141 | ## Run the Tests 142 | 143 | Put in your environment: 144 | 145 | export DYNAMODB_ACCESSKEYID=YOURACCESSKEYID 146 | export DYNAMODB_SECRETACCESSKEY=YOURSECRETKEYID 147 | export DYNAMODB_TEST_TABLE1=test 148 | 149 | Make sure you have a `test` table created and available with `sha` as a hash key (string). Make sure to select `Hash` as the `Primary Key Type`. Then run: 150 | 151 | make test 152 | 153 | If you are getting one of these errors: 154 | 155 | * `User: ... is not authorized to perform: dynamodb:PutItem on resource` you need to add `AmazonDynamoDBFullAccess` to the user in [IAM policy editor](https://console.aws.amazon.com/iam/home). 156 | 157 | ## License 158 | 159 | Distributed under the MIT License. 160 | 161 | ## Contributors 162 | 163 | @karlseguin (Karl Seguin) 164 | @imekinox (Juan Carlos del Valle) 165 | @phstc (Pablo Cantero) 166 | @cstivers78 (Chris Stivers) 167 | @garo (Juho Mäkinen) 168 | @tax (Paul Tax) 169 | @alexbosworth (Alex Bosworth) 170 | @jimbly (Jimb Esser) 171 | @n1t0 (Anthony Moi) 172 | @krasin 173 | @aws-ew-dev 174 | -------------------------------------------------------------------------------- /examples/delete_item.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | /** 23 | * delete_item.js 24 | * 25 | * Command: DeleteItem 26 | * @param table [string] the table name 27 | * @param hash [string|number] the hashKey 28 | * @param range [string|number] the optional rangeKey 29 | * @param options the DynamoDB options for DeleteItem as a dictionary 30 | * expected A dictionary mapping attribute name 31 | * to expected existence or value [see 32 | * example below]. The expected values can 33 | * be strings or numbers only. The expected 34 | * parameter is automatically converted to the 35 | * Amazon JSON format. 36 | * returnValues [string = 'NONE'|'ALL_OLD'] 'NONE' is 37 | * default. If 'ALL_OLD' is specified and 38 | * a value pair has been overwrote, then 39 | * the old item is returned in the callback. 40 | * Otherwise nothing is returned 41 | * @param cb asynchronous callback: 42 | * err: [Error] if an error occured or null 43 | * res: [Object] A dictionary containing the old item if 44 | * the returnValues option in set to 'ALL_OLD' 45 | * The object is automatically converted from 46 | * Amazon JSON format 47 | * cap: [number] the number of read capacity units consumed 48 | * by the operation 49 | */ 50 | 51 | var ddb = require('../lib/ddb.js').ddb({ accessKeyId: 'ACCESS_KEY_ID', 52 | secretAccessKey: 'SECRET_ACCESS_KEY' }); 53 | 54 | // Simple 55 | 56 | ddb.deleteItem('test', '3d2d69', null, {}, function(err, res, cap) { 57 | if(err) 58 | console.log(err); 59 | else { 60 | console.log('DeleteItem: ' + cap); 61 | console.log(res); 62 | } 63 | }); 64 | 65 | ddb.deleteItem('test', 'ffa53', 'A', {}, function(err, res, cap) { 66 | if(err) 67 | console.log(err); 68 | else { 69 | console.log('DeleteItem: ' + cap); 70 | console.log(res); 71 | } 72 | }); 73 | 74 | ddb.deleteItem('test', '91d2eb', 2012, {}, function(err, res, cap) { 75 | if(err) 76 | console.log(err); 77 | else { 78 | console.log('DeleteItem: ' + cap); 79 | console.log(res); 80 | } 81 | }); 82 | 83 | 84 | // With Options 85 | 86 | var options = { expected: { score: { exists: false }, 87 | usr: { value: 'spolu', exists: true } }, 88 | returnValues: 'ALL_OLD' }; 89 | 90 | ddb.deleteItem('test', '91d2eb', null, options, function(err, res, cap) { 91 | if(err) 92 | console.log(err); 93 | else { 94 | console.log('DeleteItem: ' + cap); 95 | console.log(res); 96 | } 97 | }); 98 | -------------------------------------------------------------------------------- /examples/describe_table.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | /** 23 | * describe_table.js 24 | * 25 | * Command: DescribeTable 26 | * @param table [string] the table name 27 | * @param cb asynchronous callback: 28 | * err: [Error] if an error occured or null 29 | * res: [Object] A dictionary containing the 30 | * table description 31 | */ 32 | 33 | var ddb = require('../lib/ddb.js').ddb({ accessKeyId: 'ACCESS_KEY_ID', 34 | secretAccessKey: 'SECRET_ACCESS_KEY' }); 35 | 36 | 37 | ddb.describeTable('test', function(err, res) { 38 | if(err) 39 | console.log(err); 40 | else { 41 | console.log('DescribeTable:'); 42 | console.log(res); 43 | } 44 | }); 45 | 46 | -------------------------------------------------------------------------------- /examples/get_item.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | /** 23 | * get_item.js 24 | * 25 | * getItem returns a flat JSON object made of string and number. It automatically 26 | * converts the returned Amazon JSON format for convenience. 27 | * 28 | * Command: GetItem 29 | * @param table [string] the table name 30 | * @param hash [string|number] the hashKey 31 | * @param range [string|number] the optional rangeKey 32 | * @param options the DynamoDB options for GetItem as a dictionary 33 | * attributesToGet An array of string representing the name 34 | * of the attributes to get 35 | * consistentRead Boolean value wether or not the read 36 | * should be consistent 37 | * @param cb asynchronous callback: 38 | * err: [Error] if an error occured or null 39 | * res: [Object] A dictionary containing the item 40 | * The object is automatically converted from 41 | * Amazon JSON format 42 | * cap: [number] the number of read capacity units consumed 43 | * by the operation 44 | */ 45 | 46 | var ddb = require('../lib/ddb.js').ddb({ accessKeyId: 'ACCESS_KEY_ID', 47 | secretAccessKey: 'SECRET_ACCESS_KEY' }); 48 | 49 | 50 | // Simple 51 | 52 | ddb.getItem('test', '3d2d69', null, {}, function(err, res, cap) { 53 | if(err) 54 | console.log(err); 55 | else { 56 | console.log('GetItem: ' + cap); 57 | console.log(res); 58 | } 59 | }); 60 | 61 | ddb.getItem('test', 'ffa53', 'A', {}, function(err, res, cap) { 62 | if(err) 63 | console.log(err); 64 | else { 65 | console.log('GetItem: ' + cap); 66 | console.log(res); 67 | } 68 | }); 69 | 70 | ddb.getItem('test', '91d2eb', 2012, {}, function(err, res, cap) { 71 | if(err) 72 | console.log(err); 73 | else { 74 | console.log('GetItem: ' + cap); 75 | console.log(res); 76 | } 77 | }); 78 | 79 | 80 | // With Options 81 | 82 | var options = { attributesToGet: ['sha', 'usr'], 83 | consistentRead: true }; 84 | 85 | ddb.getItem('test', '91d2eb', 2012, options, function(err, res, cap) { 86 | if(err) 87 | console.log(err); 88 | else { 89 | console.log('GetItem: ' + cap); 90 | console.log(res); 91 | } 92 | }); 93 | 94 | -------------------------------------------------------------------------------- /examples/list_tables.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | /** 23 | * list_tables.js 24 | * 25 | * Command: ListTables 26 | * @param options the DynamoDB options for ListTables as a dictionary 27 | * limit [number] maximum table names to return 28 | * exclusiveStartTableName [string] the name of the table that 29 | * starts the list 30 | * @param cb asynchronous callback: 31 | * err: [Error] if an error occured or null 32 | * res: [Object] containing the LastEvaluatedTableName 33 | * value and a string array containing the 34 | * table names 35 | */ 36 | 37 | var ddb = require('../lib/ddb.js').ddb({ accessKeyId: 'ACCESS_KEY_ID', 38 | secretAccessKey: 'SECRET_ACCESS_KEY' }); 39 | 40 | // Simple 41 | 42 | ddb.listTables({}, function(err, res) { 43 | if(err) 44 | console.log(err); 45 | else { 46 | console.log('ListTable:'); 47 | console.log(res); 48 | } 49 | }); 50 | 51 | 52 | // With Options 53 | 54 | ddb.listTables({ limit: 12, 55 | exclusiveStartTableName: 'test' }, function(err, res) { 56 | if(err) 57 | console.log(err); 58 | else { 59 | console.log('ListTable from "test":'); 60 | console.log(res); 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /examples/put_item.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | /** 23 | * put_item.js 24 | * 25 | * putItem expects a dictionary (item) containing only strings and numbers 26 | * This object is automatically converted into the expxected Amazon JSON 27 | * format for convenience. 28 | * 29 | * Command: PutItem 30 | * @param table [string] the table name 31 | * @param item the item to put (string/number/string array/number array dictionary) 32 | * @param options the DynamoDB options for PutItem as a dictionary 33 | * expected A dictionary mapping attribute name 34 | * to expected existence or value [see 35 | * example below]. The expected values can 36 | * be strings or numbers only. The expected 37 | * parameter is automatically converted to the 38 | * Amazon JSON format. 39 | * returnValues [string = 'NONE'|'ALL_OLD'] 'NONE' is 40 | * default. If 'ALL_OLD' is specified and 41 | * a value pair has been overwrote, then 42 | * the old item is returned in the callback. 43 | * Otherwise nothing is returned 44 | * @param cb asynchronous callback: 45 | * err: [Error] if an error occured or null 46 | * res: [Object] A dictionary containing the old item if 47 | * the returnValues option in set to 'ALL_OLD' 48 | * The object is automatically converted from 49 | * Amazon JSON format 50 | * cap: [number] the number of read capacity units consumed 51 | * by the operation 52 | */ 53 | 54 | var ddb = require('../lib/ddb.js').ddb({ accessKeyId: 'ACCESS_KEY_ID', 55 | secretAccessKey: 'SECRET_ACCESS_KEY' }); 56 | 57 | var item1 = { date: (new Date).getTime(), 58 | sha: '3d2d69633ffa5368c7971cf15c91d2eb', 59 | usr: 'spolu', 60 | val: [5, 6, 7] }; 61 | 62 | // Simple 63 | 64 | ddb.putItem('test', item1, {}, function(err, res, cap) { 65 | if(err) 66 | console.log(err); 67 | else { 68 | console.log('PutItem: ' + cap); 69 | console.log(res); 70 | } 71 | }); 72 | 73 | 74 | // With Options 75 | 76 | var item2 = { score: 304, 77 | date: (new Date).getTime(), 78 | sha: '3d2d69633ffa5368c7971cf15c91d2eb', 79 | usr: 'spolu', 80 | lng: ['node', 'c++'] }; 81 | 82 | var options = { expected: { score: { exists: false }, 83 | usr: { value: 'spolu', exists: true } }, 84 | returnValues: 'ALL_OLD' }; 85 | 86 | ddb.putItem('test', item2, options, function(err, res, cap) { 87 | if(err) 88 | console.log(err); 89 | else { 90 | console.log('PutItem: ' + cap); 91 | console.log(res); 92 | } 93 | }); 94 | -------------------------------------------------------------------------------- /examples/scan.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | /** 23 | * scan.js 24 | * 25 | * scan scans the table and returns a flat JSON version of the objects retrieved. 26 | * It automatically converts the returned Amazon JSON format for convenience. 27 | * 28 | * Command: Scan 29 | * @param table [string] the table name 30 | * @param options the DynamoDB options for Scan as a dictionary 31 | * attributesToGet An array of string representing the name 32 | * of the attributes to get 33 | * limit Maximum number of items to return 34 | * count Boolean wether the total number of items 35 | * for the scan operation should be returned 36 | * filter A dictionary mapping attribute names to 37 | * filter objects. Filters objects map a 38 | * filter operation (eq, ne, le, lt, ge, 39 | * gt, eq, not_null, null, contains, 40 | * not_contains, begins_with, in, between) 41 | * to a value or an array of value if 42 | * applicable 43 | * @param cb asynchronous callback: 44 | * err: [Error] if an error occured or null 45 | * res: [Object] A dictionary containing the following elemnts: 46 | * count: number of items in the response 47 | * items: the items (transformed from DDB format) 48 | * lastEvaluatedKey: primary key where scan stopped 49 | * ScannedCount: Total num of items (if count is true) 50 | * cap: [number] the number of read capacity units consumed 51 | * by the operation 52 | */ 53 | 54 | var ddb = require('../lib/ddb.js').ddb({ accessKeyId: 'ACCESS_KEY_ID', 55 | secretAccessKey: 'SECRET_ACCESS_KEY' }); 56 | 57 | 58 | // Simple 59 | 60 | ddb.scan('test', {}, function(err, res) { 61 | if(err) { 62 | console.log(err); 63 | } else { 64 | console.log(res); 65 | } 66 | }); 67 | 68 | 69 | 70 | // With Options 71 | 72 | var options = { limit: 100, 73 | filter : { date: { ge: 123012398234 } } }; 74 | 75 | ddb.scan('test', options, function(err, res) { 76 | if(err) { 77 | console.log(err); 78 | } else { 79 | console.log(res); 80 | } 81 | }); 82 | 83 | 84 | var options = { count:true }; 85 | 86 | ddb.scan('test', options, function(err, res) { 87 | if(err) { 88 | console.log(err); 89 | } else { 90 | console.log(res); 91 | } 92 | }); 93 | 94 | 95 | var options = { filter : { date: { null: true } } }; 96 | 97 | ddb.scan('test', options, function(err, res) { 98 | if(err) { 99 | console.log(err); 100 | } else { 101 | console.log(res); 102 | } 103 | }); 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /examples/update_item.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | /** 23 | * update_item.js 24 | * 25 | * updateItem expects a ditionary of updates associating attribute names 26 | * to objects of the form { value: XXX, action: 'PUT|ADD|DELETE' } 27 | * The value part of the update is automatically converted into the Amazon 28 | * JSON format for convenience 29 | * 30 | * Command: UpdateItem 31 | * @param table [string] the table name 32 | * @param hash [string|number] the hashKey 33 | * @param range [string|number] the optional rangeKey 34 | * @param updates the updates to perform as a dictionary 35 | * associating attribute names to udpate objects 36 | * of the form: { value: XXX, action: 'PUT|ADD|DELETE' } 37 | * the possible actions are PUT, ADD or DELETE. 38 | * @param options the DynamoDB options for PutItem as a dictionary 39 | * expected A dictionary mapping attribute name 40 | * to expected existence or value [see 41 | * example below]. The expected values can 42 | * be strings or numbers only. The expected 43 | * parameter is automatically converted to the 44 | * Amazon JSON format. 45 | * returnValues [string = 'NONE'|'ALL_OLD'] 'NONE' is 46 | * default. If 'ALL_OLD' is specified and 47 | * a value pair has been overwrote, then 48 | * the old item is returned in the callback. 49 | * Otherwise nothing is returned 50 | * @param cb asynchronous callback: 51 | * err: [Error] if an error occured or null 52 | * res: [Object] A dictionary containing the old item if 53 | * the returnValues option in set to 'ALL_OLD' 54 | * The object is automatically converted from 55 | * Amazon JSON format 56 | * cap: [number] the number of read capacity units consumed 57 | * by the operation 58 | */ 59 | 60 | var ddb = require('../lib/ddb.js').ddb({ accessKeyId: 'ACCESS_KEY_ID', 61 | secretAccessKey: 'SECRET_ACCESS_KEY' }); 62 | 63 | 64 | // Simple 65 | 66 | ddb.updateItem('test', '3d2d69', null, { 'usr': { value: 'dummy' } }, {}, 67 | function(err, res, cap) { 68 | if(err) 69 | console.log(err); 70 | else { 71 | console.log('UpdateItem: ' + cap); 72 | console.log(res); 73 | } 74 | }); 75 | 76 | 77 | ddb.updateItem('test', '3d2d69', null, { 'usr': { action: 'DELETE' } }, {}, 78 | function(err, res, cap) { 79 | if(err) 80 | console.log(err); 81 | else { 82 | console.log('UpdateItem: ' + cap); 83 | console.log(res); 84 | } 85 | }); 86 | 87 | ddb.updateItem('test', '3d2d69', null, { 'usr': { value: 'spolu', action: 'PUT' } }, {}, 88 | function(err, res, cap) { 89 | if(err) 90 | console.log(err); 91 | else { 92 | console.log('UpdateItem: ' + cap); 93 | console.log(res); 94 | } 95 | }); 96 | 97 | // see put_item.js for use of options 98 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | /** 23 | * Export lib/ddb 24 | */ 25 | 26 | module.exports = require('./lib/ddb'); -------------------------------------------------------------------------------- /lib/ddb.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | var http = require('http'); 23 | var https = require('https'); 24 | var crypto = require('crypto'); 25 | var events = require('events'); 26 | 27 | var fwk = require('fwk'); 28 | 29 | /** 30 | * The DynamoDb Object 31 | * 32 | * @extends events.EventEmitter 33 | * 34 | * @param spec {secretAccessKey, accessKeyId, endpoint, agent, sessionToken, sessionExpires} 35 | */ 36 | 37 | var ddb = function(spec, my) { 38 | my = my || {}; 39 | var _super = {}; 40 | 41 | my.accessKeyId = spec.accessKeyId; 42 | my.secretAccessKey = spec.secretAccessKey; 43 | my.endpoint = spec.endpoint || 'dynamodb.us-east-1.amazonaws.com'; 44 | my.port = spec.port || 80; 45 | my.agent = spec.agent; 46 | 47 | my.retries = spec.retries || 3; 48 | 49 | // Use already obtained temporary session credentials 50 | if(spec.sessionToken && spec.sessionExpires) { 51 | my.access = { sessionToken: spec.sessionToken, 52 | secretAccessKey: spec.secretAccessKey, 53 | accessKeyId: spec.accessKeyId, 54 | expiration: spec.sessionExpires }; 55 | } 56 | 57 | my.inAuth = false; 58 | my.consumedCapacity = 0; 59 | my.schemaTypes = { number: 'N', 60 | string: 'S', 61 | number_array: 'NS', 62 | string_array: 'SS' }; 63 | 64 | // public 65 | var createTable; 66 | var listTables; 67 | var describeTable; 68 | var deleteTable; 69 | var updateTable; 70 | 71 | var getItem; 72 | var putItem; 73 | var deleteItem; 74 | var updateItem; 75 | var query; 76 | var scan; 77 | var batchGetItem; 78 | var batchWriteItem; 79 | 80 | // private 81 | var scToDDB; 82 | var objToDDB; 83 | var objFromDDB; 84 | var arrFromDDB; 85 | var execute; 86 | var auth; 87 | 88 | 89 | var that = new events.EventEmitter(); 90 | that.setMaxListeners(0); 91 | 92 | 93 | /** 94 | * The CreateTable operation adds a new table to your account. 95 | * It returns details of the table. 96 | * @param table the name of the table 97 | * @param keySchema {hash: [attribute, type]} or {hash: [attribute, type], range: [attribute, type]} 98 | * @param provisionedThroughput {write: X, read: Y} 99 | * @param cb callback(err, tableDetails) err is set if an error occured 100 | */ 101 | createTable = function(table, keySchema, provisionedThroughput, cb) { 102 | var data = {}; 103 | data.TableName = table; 104 | data.KeySchema = {}; 105 | data.ProvisionedThroughput = {}; 106 | if(keySchema.hash && keySchema.hash.length == 2) { 107 | data.KeySchema.HashKeyElement = { AttributeName: keySchema.hash[0], 108 | AttributeType: keySchema.hash[1] }; 109 | } 110 | if (keySchema.range && keySchema.range.length == 2) { 111 | data.KeySchema.RangeKeyElement = { AttributeName: keySchema.range[0], 112 | AttributeType: keySchema.range[1] }; 113 | } 114 | if(provisionedThroughput) { 115 | if(provisionedThroughput.read) 116 | data.ProvisionedThroughput.ReadCapacityUnits = provisionedThroughput.read; 117 | if(provisionedThroughput.write) 118 | data.ProvisionedThroughput.WriteCapacityUnits = provisionedThroughput.write; 119 | } 120 | execute('CreateTable', data, function(err, res) { 121 | if(err) { cb(err) } 122 | else { 123 | cb(null, res.TableDescription); 124 | } 125 | }); 126 | }; 127 | 128 | 129 | /** 130 | * Updates the provisioned throughput for the given table. 131 | * It returns details of the table. 132 | * @param table the name of the table 133 | * @param provisionedThroughput {write: X, read: Y} 134 | * @param cb callback(err, tableDetails) err is set if an error occured 135 | */ 136 | updateTable = function(table, provisionedThroughput, cb) { 137 | var data = {}; 138 | data.TableName = table; 139 | data.ProvisionedThroughput = {}; 140 | if(provisionedThroughput) { 141 | if(provisionedThroughput.read) 142 | data.ProvisionedThroughput.ReadCapacityUnits = provisionedThroughput.read; 143 | if(provisionedThroughput.write) 144 | data.ProvisionedThroughput.WriteCapacityUnits = provisionedThroughput.write; 145 | } 146 | execute('UpdateTable', data, function(err, res) { 147 | if(err) { cb(err) } 148 | else { 149 | cb(null, res.TableDescription); 150 | } 151 | }); 152 | }; 153 | 154 | 155 | /** 156 | * The DeleteTable operation deletes a table and all of its items 157 | * It returns details of the table 158 | * @param table the name of the table 159 | * @param cb callback(err, tableDetails) err is set if an error occured 160 | */ 161 | deleteTable = function(table, cb) { 162 | var data = {}; 163 | data.TableName = table; 164 | execute('DeleteTable', data, function(err, res) { 165 | if(err) { cb(err) } 166 | else { 167 | cb(null, res.TableDescription); 168 | } 169 | }); 170 | }; 171 | 172 | 173 | /** 174 | * returns an array of all the tables associated with the current account and endpoint 175 | * @param options {limit, exclusiveStartTableName} 176 | * @param cb callback(err, tables) err is set if an error occured 177 | */ 178 | listTables = function(options, cb) { 179 | var data = {}; 180 | if(options.limit) 181 | data.Limit = options.limit; 182 | if(options.exclusiveStartTableName) 183 | data.ExclusiveStartTableName = options.exclusiveStartTableName; 184 | execute('ListTables', data, cb); 185 | }; 186 | 187 | 188 | /** 189 | * returns information about the table, including the current status of the table, 190 | * the primary key schema and when the table was created 191 | * @param table the table name 192 | * @param cb callback(err, tables) err is set if an error occured 193 | */ 194 | describeTable = function(table, cb) { 195 | var data = {}; 196 | data.TableName = table; 197 | execute('DescribeTable', data, function(err, res) { 198 | if(err) { cb(err) } 199 | else { 200 | cb(null, res.Table); 201 | } 202 | }); 203 | }; 204 | 205 | 206 | /** 207 | * returns a set of Attributes for an item that matches the primary key. 208 | * @param table the tableName 209 | * @param hash the hashKey 210 | * @param range the rangeKey 211 | * @param options {attributesToGet, consistentRead} 212 | * @param cb callback(err, tables) err is set if an error occured 213 | */ 214 | getItem = function(table, hash, range, options, cb) { 215 | var data = {}; 216 | try { 217 | data.TableName = table; 218 | var key = { "HashKeyElement": hash }; 219 | if(typeof range !== 'undefined' && 220 | range !== null) { 221 | key.RangeKeyElement = range; 222 | } 223 | data.Key = objToDDB(key); 224 | if(options.attributesToGet) { 225 | data.AttributesToGet = options.attributesToGet; 226 | } 227 | if(options.consistentRead) { 228 | data.ConsistentRead = options.consistentRead; 229 | } 230 | } 231 | catch(err) { 232 | cb(err); 233 | return; 234 | } 235 | execute('GetItem', data, function(err, res) { 236 | if(err) { cb(err) } 237 | else { 238 | my.consumedCapacity += res.ConsumedCapacityUnits; 239 | try { 240 | var item = objFromDDB(res.Item); 241 | } 242 | catch(err) { 243 | cb(err); 244 | return; 245 | } 246 | cb(null, item, res.ConsumedCapacityUnits); 247 | } 248 | }); 249 | }; 250 | 251 | 252 | /** 253 | * Creates a new item, or replaces an old item with a new item 254 | * (including all the attributes). If an item already exists in the 255 | * specified table with the same primary key, the new item completely 256 | * replaces the existing item. 257 | * putItem expects a dictionary (item) containing only strings and numbers 258 | * This object is automatically converted into the expxected Amazon JSON 259 | * format for convenience. 260 | * @param table the tableName 261 | * @param item the item to put (string/number/string array dictionary) 262 | * @param options {expected, returnValues} 263 | * @param cb callback(err, attrs, consumedCapUnits) err is set if an error occured 264 | */ 265 | putItem = function(table, item, options, cb) { 266 | var data = {}; 267 | try { 268 | data.TableName = table; 269 | data.Item = objToDDB(item); 270 | //console.log('ITEM:==' + JSON.stringify(data) + '=='); 271 | if(options.expected) { 272 | data.Expected = {}; 273 | for(var i in options.expected) { 274 | data.Expected[i] = {}; 275 | if(typeof options.expected[i].exists === 'boolean') { 276 | data.Expected[i].Exists = options.expected[i].exists; 277 | } 278 | if(typeof options.expected[i].value !== 'undefined') { 279 | data.Expected[i].Value = scToDDB(options.expected[i].value); 280 | } 281 | } 282 | } 283 | if(options.returnValues) { 284 | data.ReturnValues = options.returnValues; 285 | } 286 | } 287 | catch(err) { 288 | cb(err); 289 | return; 290 | } 291 | execute('PutItem', data, function(err, res) { 292 | if(err) { cb(err) } 293 | else { 294 | my.consumedCapacity += res.ConsumedCapacityUnits; 295 | try { 296 | var attr = objFromDDB(res.Attributes); 297 | } 298 | catch(err) { 299 | cb(err); 300 | return; 301 | } 302 | cb(null, attr, res.ConsumedCapacityUnits); 303 | } 304 | }); 305 | }; 306 | 307 | 308 | /** 309 | * deletes a single item in a table by primary key. You can perform a conditional 310 | * delete operation that deletes the item if it exists, or if it has an expected 311 | * attribute value. 312 | * @param table the tableName 313 | * @param hash the hashKey 314 | * @param range the rangeKey 315 | * @param options {expected, returnValues} 316 | * @param cb callback(err, attrs, consumedCapUnits) err is set if an error occured 317 | */ 318 | deleteItem = function(table, hash, range, options, cb) { 319 | var data = {}; 320 | try { 321 | data.TableName = table; 322 | var key = { "HashKeyElement": hash }; 323 | if(typeof range !== 'undefined' && 324 | range !== null) { 325 | key.RangeKeyElement = range; 326 | } 327 | data.Key = objToDDB(key); 328 | if(options.expected) { 329 | data.Expected = {}; 330 | for(var i in options.expected) { 331 | data.Expected[i] = {}; 332 | if(typeof options.expected[i].exists === 'boolean') { 333 | data.Expected[i].Exists = options.expected[i].exists; 334 | } 335 | if(typeof options.expected[i].value !== 'undefined') { 336 | data.Expected[i].Value = scToDDB(options.expected[i].value); 337 | } 338 | } 339 | } 340 | if(options.returnValues) 341 | data.ReturnValues = options.returnValues; 342 | } 343 | catch(err) { 344 | cb(err); 345 | return; 346 | } 347 | execute('DeleteItem', data, function(err, res) { 348 | if(err) { cb(err) } 349 | else { 350 | my.consumedCapacity += res.ConsumedCapacityUnits; 351 | try { 352 | var attr = objFromDDB(res.Attributes); 353 | } 354 | catch(err) { 355 | cb(err); 356 | return; 357 | } 358 | cb(null, attr, res.ConsumedCapacityUnits); 359 | } 360 | }); 361 | }; 362 | 363 | 364 | /** 365 | * Updates an item with the supplied update orders. 366 | * @param table the tableName 367 | * @param hash the hashKey 368 | * @param range optional rangeKey 369 | * @param updates dictionary of attributeNames to { value: XXX, action: 'PUT|ADD|DELETE' } 370 | * @param options {expected, returnValues} 371 | * @param cb callback(err, attrs, consumedCapUnits) err is set if an error occured 372 | */ 373 | updateItem = function(table, hash, range, updates, options, cb) { 374 | var data = {}; 375 | try { 376 | data.TableName = table; 377 | var key = { "HashKeyElement": hash }; 378 | if(typeof range !== 'undefined' && 379 | range !== null) { 380 | key.RangeKeyElement = range; 381 | } 382 | data.Key = objToDDB(key); 383 | if(options.expected) { 384 | data.Expected = {}; 385 | for(var i in options.expected) { 386 | data.Expected[i] = {}; 387 | if(typeof options.expected[i].exists === 'boolean') { 388 | data.Expected[i].Exists = options.expected[i].exists; 389 | } 390 | if(typeof options.expected[i].value !== 'undefined') { 391 | data.Expected[i].Value = scToDDB(options.expected[i].value); 392 | } 393 | } 394 | } 395 | if(typeof updates === 'object') { 396 | data.AttributeUpdates = {}; 397 | for(var attr in updates) { 398 | if(updates.hasOwnProperty(attr)) { 399 | data.AttributeUpdates[attr] = {}; 400 | if(typeof updates[attr].action === 'string') 401 | data.AttributeUpdates[attr]["Action"] = updates[attr].action; 402 | if(typeof updates[attr].value !== 'undefined') 403 | data.AttributeUpdates[attr]["Value"] = scToDDB(updates[attr].value); 404 | } 405 | } 406 | } 407 | if(options.returnValues) { 408 | data.ReturnValues = options.returnValues; 409 | } 410 | } 411 | catch(err) { 412 | cb(err); 413 | return; 414 | } 415 | //console.log(require('util').inspect(data, false, 20)); 416 | execute('UpdateItem', data, function(err, res) { 417 | if(err) { cb(err) } 418 | else { 419 | my.consumedCapacity += res.ConsumedCapacityUnits; 420 | try { 421 | var attr = objFromDDB(res.Attributes); 422 | } 423 | catch(err) { 424 | cb(err); 425 | return; 426 | } 427 | cb(null, attr, res.ConsumedCapacityUnits); 428 | } 429 | }); 430 | }; 431 | 432 | 433 | /** 434 | * An object representing a table query, or an array of such objects 435 | * { 'table': { keys: [1, 2, 3], attributesToGet: ['user', 'status'] } } 436 | * or keys: [['id', 'range'], ['id2', 'range2']] 437 | * @param cb callback(err, tables) err is set if an error occured 438 | */ 439 | batchGetItem = function(request, cb) { 440 | var data = {}; 441 | try { 442 | data.RequestItems = {}; 443 | for(var table in request) { 444 | if(request.hasOwnProperty(table)) { 445 | var parts = Array.isArray(request[table]) ? request[table] : [request[table]]; 446 | 447 | for(var i = 0; i < parts.length; ++i) { 448 | var part = parts[i]; 449 | var tableData = {Keys: []}; 450 | var hasRange = Array.isArray(part.keys[0]); 451 | 452 | for(var j = 0; j < part.keys.length; j++) { 453 | var key = part.keys[j]; 454 | var keyData = hasRange ? {"HashKeyElement": scToDDB(key[0]), "RangeKeyElement": scToDDB(key[1])} : {"HashKeyElement": scToDDB(key)}; 455 | tableData.Keys.push(keyData); 456 | } 457 | 458 | if (part.attributesToGet) { 459 | tableData.AttributesToGet = part.attributesToGet; 460 | } 461 | data.RequestItems[table] = tableData; 462 | } 463 | } 464 | } 465 | } 466 | catch(err) { 467 | cb(err); 468 | return; 469 | } 470 | execute('BatchGetItem', data, function(err, res) { 471 | if(err) { cb(err) } 472 | else { 473 | var consumedCapacity = 0; 474 | for(var table in res.Responses) { 475 | var part = res.Responses[table]; 476 | var cap = part.ConsumedCapacityUnits; 477 | if (cap) { 478 | consumedCapacity += cap; 479 | } 480 | if (part.Items) { 481 | try { 482 | part.items = arrFromDDB(part.Items); 483 | } 484 | catch(err) { 485 | cb(err); 486 | return; 487 | } 488 | delete part.Items; 489 | } 490 | if (res.UnprocessedKeys[table]) { 491 | part.UnprocessedKeys = res.UnprocessedKeys[table]; 492 | } 493 | } 494 | my.consumedCapacity += consumedCapacity; 495 | if (parts.length == 1) { 496 | var smartResponse = res.Responses[table]; 497 | cb(null, smartResponse, consumedCapacity); 498 | } 499 | else { 500 | cb(null, res.Responses, consumedCapacity); 501 | } 502 | } 503 | }); 504 | }; 505 | 506 | /** 507 | * Put or delete several items across multiple tables 508 | * @param putRequest dictionnary { 'table': [item1, item2, item3], 'table2': item } 509 | * @param deleteRequest dictionnary { 'table': [key1, key2, key3], 'table2': [[id1, range1], [id2, range2]] } 510 | * @param cb callback(err, res, cap) err is set if an error occured 511 | */ 512 | batchWriteItem = function(putRequest, deleteRequest, cb) { 513 | var data = {}; 514 | try { 515 | data.RequestItems = {}; 516 | 517 | for(var table in putRequest) { 518 | if(putRequest.hasOwnProperty(table)) { 519 | var items = (Array.isArray(putRequest[table]) ? putRequest[table] : [putRequest[table]]); 520 | 521 | for(var i = 0; i < items.length; i++) { 522 | data.RequestItems[table] = data.RequestItems[table] || []; 523 | data.RequestItems[table].push( { "PutRequest": { "Item": objToDDB(items[i]) }} ); 524 | } 525 | } 526 | } 527 | 528 | for(var table in deleteRequest) { 529 | if(deleteRequest.hasOwnProperty(table)) { 530 | var parts = (Array.isArray(deleteRequest[table]) ? deleteRequest[table] : [deleteRequest[table]]); 531 | 532 | for(var i = 0; i < parts.length; i++) { 533 | var part = parts[i]; 534 | var hasRange = Array.isArray(part); 535 | 536 | var keyData = hasRange ? {"HashKeyElement": scToDDB(part[0]), "RangeKeyElement": scToDDB(part[1])} : {"HashKeyElement": scToDDB(part)}; 537 | 538 | data.RequestItems[table] = data.RequestItems[table] || []; 539 | data.RequestItems[table].push( { "DeleteRequest": { "Key" : keyData }} ); 540 | } 541 | } 542 | } 543 | execute('BatchWriteItem', data, function(err, res) { 544 | if(err) 545 | cb(err); 546 | else { 547 | var consumedCapacity = 0; 548 | for(var table in res.Responses) { 549 | if(res.Responses.hasOwnProperty(table)) { 550 | var part = res.Responses[table]; 551 | var cap = part.ConsumedCapacityUnits; 552 | if (cap) { 553 | consumedCapacity += cap; 554 | } 555 | } 556 | } 557 | my.consumedCapacity += consumedCapacity; 558 | cb(null, res.UnprocessedItems, consumedCapacity); 559 | } 560 | }); 561 | } 562 | catch(err) { 563 | cb(err) 564 | } 565 | }; 566 | 567 | /** 568 | * returns a set of Attributes for an item that matches the query 569 | * @param table the tableName 570 | * @param hash the hashKey 571 | * @param options {attributesToGet, limit, consistentRead, count, 572 | * rangeKeyCondition, scanIndexForward, exclusiveStartKey, indexName} 573 | * @param cb callback(err, tables) err is set if an error occured 574 | */ 575 | query = function(table, hash, options, cb) { 576 | var data = {}; 577 | try { 578 | data.TableName = table; 579 | data.HashKeyValue = scToDDB(hash) 580 | if(options.attributesToGet) { 581 | data.AttributesToGet = options.attributesToGet; 582 | } 583 | if(options.limit) { 584 | data.Limit = options.limit; 585 | } 586 | if(options.consistentRead) { 587 | data.ConsistentRead = options.consistentRead; 588 | } 589 | if(options.count && !options.attributesToGet) { 590 | data.Count = options.count; 591 | } 592 | if(options.rangeKeyCondition) { 593 | for(var op in options.rangeKeyCondition) { // supposed to be only one 594 | if(typeof op === 'string') { 595 | data.RangeKeyCondition = {"AttributeValueList":[],"ComparisonOperator": op.toUpperCase()}; 596 | if(op == 'between' && 597 | Array.isArray(options.rangeKeyCondition[op]) && 598 | options.rangeKeyCondition[op].length > 1) { 599 | data.RangeKeyCondition.AttributeValueList.push(scToDDB(options.rangeKeyCondition[op][0])); 600 | data.RangeKeyCondition.AttributeValueList.push(scToDDB(options.rangeKeyCondition[op][1])); 601 | } 602 | else { 603 | data.RangeKeyCondition.AttributeValueList.push(scToDDB(options.rangeKeyCondition[op])); 604 | } 605 | } 606 | } 607 | } 608 | if(options.scanIndexForward === false) { 609 | data.ScanIndexForward = false; 610 | } 611 | if(options.exclusiveStartKey && options.exclusiveStartKey.hash) { 612 | data.ExclusiveStartKey = { HashKeyElement: scToDDB(options.exclusiveStartKey.hash) }; 613 | if(options.exclusiveStartKey.range) 614 | data.ExclusiveStartKey.RangeKeyElement = scToDDB(options.exclusiveStartKey.range); 615 | } 616 | if(options.indexName) { 617 | data.IndexName = options.indexName; 618 | } 619 | } 620 | catch(err) { 621 | cb(err); 622 | return; 623 | } 624 | execute('Query', data, function(err, res) { 625 | if(err) { cb(err) } 626 | else { 627 | my.consumedCapacity += res.ConsumedCapacityUnits; 628 | var r = { count: res.Count, 629 | items: [], 630 | lastEvaluatedKey: {}}; 631 | try { 632 | if (res.Items) { 633 | r.items = arrFromDDB(res.Items); 634 | } 635 | if(res.LastEvaluatedKey) { 636 | var key = objFromDDB(res.LastEvaluatedKey); 637 | r.lastEvaluatedKey = { hash: key.HashKeyElement, 638 | range: key.RangeKeyElement }; 639 | } 640 | } 641 | catch(err) { 642 | cb(err); 643 | return; 644 | } 645 | cb(null, r, res.ConsumedCapacityUnits); 646 | } 647 | }); 648 | }; 649 | 650 | 651 | /** 652 | * returns one or more items and its attributes by performing a full scan of a table. 653 | * @param table the tableName 654 | * @param options {attributesToGet, limit, count, scanFilter, exclusiveStartKey} 655 | * @param cb callback(err, {count, items, lastEvaluatedKey}) err is set if an error occured 656 | */ 657 | scan = function(table, options, cb) { 658 | var data = {}; 659 | try { 660 | data.TableName = table; 661 | if(options.attributesToGet) { 662 | data.AttributesToGet = options.attributesToGet; 663 | } 664 | if(options.limit) { 665 | data.Limit = options.limit; 666 | } 667 | if(options.count && !options.attributesToGet) { 668 | data.Count = options.count; 669 | } 670 | if(options.exclusiveStartKey && options.exclusiveStartKey.hash) { 671 | data.ExclusiveStartKey = { HashKeyElement: scToDDB(options.exclusiveStartKey.hash) }; 672 | if(options.exclusiveStartKey.range) 673 | data.ExclusiveStartKey.RangeKeyElement = scToDDB(options.exclusiveStartKey.range); 674 | } 675 | if(options.filter) { 676 | data.ScanFilter = {} 677 | for(var attr in options.filter) { 678 | if(options.filter.hasOwnProperty(attr)) { 679 | for(var op in options.filter[attr]) { // supposed to be only one 680 | if(typeof op === 'string') { 681 | data.ScanFilter[attr] = {"AttributeValueList":[],"ComparisonOperator": op.toUpperCase()}; 682 | if(op === 'not_null' || op === 'null') { 683 | // nothing ot do 684 | } 685 | else if((op == 'between' || op == 'in') && 686 | Array.isArray(options.filter[attr][op]) && 687 | options.filter[attr][op].length > 1) { 688 | for (var i = 0; i < options.filter[attr][op].length; ++i) { 689 | data.ScanFilter[attr].AttributeValueList.push(scToDDB(options.filter[attr][op][i])); 690 | } 691 | } 692 | else { 693 | data.ScanFilter[attr].AttributeValueList.push(scToDDB(options.filter[attr][op])); 694 | } 695 | } 696 | } 697 | } 698 | } 699 | } 700 | } 701 | catch(err) { 702 | cb(err); 703 | return; 704 | } 705 | //console.log(require('util').inspect(data)); 706 | execute('Scan', data, function(err, res) { 707 | if(err) { cb(err) } 708 | else { 709 | my.consumedCapacity += res.ConsumedCapacityUnits; 710 | var r = { count: res.Count, 711 | items: [], 712 | lastEvaluatedKey: {}, 713 | scannedCount: res.ScannedCount }; 714 | try { 715 | if(Array.isArray(res.Items)) { 716 | r.items = arrFromDDB(res.Items); 717 | } 718 | if(res.LastEvaluatedKey) { 719 | var key = objFromDDB(res.LastEvaluatedKey); 720 | r.lastEvaluatedKey = { hash: key.HashKeyElement, 721 | range: key.RangeKeyElement }; 722 | } 723 | } 724 | catch(err) { 725 | cb(err); 726 | return; 727 | } 728 | cb(null, r, res.ConsumedCapacityUnits); 729 | } 730 | }); 731 | }; 732 | 733 | 734 | 735 | //-- INTERNALS --// 736 | 737 | /** 738 | * converts a JSON object (dictionary of values) to an amazon DynamoDB 739 | * compatible JSON object 740 | * @param json the JSON object 741 | * @throws an error if input object is not compatible 742 | * @return res the converted object 743 | */ 744 | objToDDB = function(json) { 745 | if(typeof json === 'object') { 746 | var res = {}; 747 | for(var i in json) { 748 | if(json.hasOwnProperty(i) && json[i] !== null) { 749 | res[i] = scToDDB(json[i]); 750 | } 751 | } 752 | return res; 753 | } 754 | else 755 | return json; 756 | }; 757 | 758 | 759 | /** 760 | * converts a string, string array, number or number array (scalar) 761 | * JSON object to an amazon DynamoDB compatible JSON object 762 | * @param json the JSON scalar object 763 | * @throws an error if input object is not compatible 764 | * @return res the converted object 765 | */ 766 | scToDDB = function(value) { 767 | if (typeof value === 'number') { 768 | return { "N": value.toString() }; 769 | } 770 | if (typeof value === 'string') { 771 | return { "S": value }; 772 | } 773 | if (Array.isArray(value)) { 774 | var arr = []; 775 | var length = value.length; 776 | var isSS = false; 777 | for(var i = 0; i < length; ++i) { 778 | if(typeof value[i] === 'string') { 779 | arr[i] = value[i]; 780 | isSS = true; 781 | } 782 | else if(typeof value[i] === 'number') { 783 | arr[i] = value[i].toString(); 784 | } 785 | } 786 | return isSS ? {"SS": arr} : {"NS": arr}; 787 | } 788 | throw new Error('Non Compatible Field [not string|number|string array|number array]: ' + value); 789 | } 790 | 791 | 792 | /** 793 | * converts a DynamoDB compatible JSON object into 794 | * a native JSON object 795 | * @param ddb the ddb JSON object 796 | * @throws an error if input object is not compatible 797 | * @return res the converted object 798 | */ 799 | objFromDDB = function(ddb) { 800 | if(typeof ddb === 'object') { 801 | var res = {}; 802 | for(var i in ddb) { 803 | if(ddb.hasOwnProperty(i)) { 804 | if(ddb[i]['S']) 805 | res[i] = ddb[i]['S']; 806 | else if(ddb[i]['SS']) 807 | res[i] = ddb[i]['SS']; 808 | else if(ddb[i]['N']) 809 | res[i] = parseFloat(ddb[i]['N']); 810 | else if(ddb[i]['NS']) { 811 | res[i] = []; 812 | for(var j = 0; j < ddb[i]['NS'].length; j ++) { 813 | res[i][j] = parseFloat(ddb[i]['NS'][j]); 814 | } 815 | } else if(ddb[i]['L']) { 816 | res[i] = []; 817 | ddb[i]['L'].forEach(function(item) { 818 | res[i].push(objFromDDB(item)); 819 | }); 820 | // or if 'M' 821 | } else if(typeof ddb[i] === 'object') { 822 | res[i] = objFromDDB(ddb[i]['M']); 823 | } 824 | else 825 | throw new Error('Non Compatible Field [not "S"|"N"|"NS"|"SS"]: ' + i); 826 | } 827 | } 828 | return res; 829 | } 830 | else 831 | return ddb; 832 | }; 833 | 834 | 835 | /** 836 | * converts an array of DynamoDB compatible JSON object into 837 | * an array of native JSON object 838 | * @param arr the array of ddb objects to convert 839 | * @throws an error if input object is not compatible 840 | * @return res the converted object 841 | */ 842 | arrFromDDB = function(arr) { 843 | var length = arr.length; 844 | for(var i = 0; i < length; ++i) { 845 | arr[i] = objFromDDB(arr[i]); 846 | } 847 | return arr; 848 | }; 849 | 850 | 851 | /** 852 | * executes a constructed request, eventually calling auth. 853 | * @param request JSON request body 854 | * @param cb callback(err, result) err specified in case of error 855 | */ 856 | execute = function(op, data, cb) { 857 | auth(function(err) { 858 | if(err) { cb(err); } 859 | else { 860 | var dtStr = (new Date).toUTCString(); 861 | var rqBody = JSON.stringify(data); 862 | 863 | var sts = ('POST' + '\n' + 864 | '/' + '\n' + 865 | '' + '\n' + 866 | ('host' + ':' + my.endpoint + '\n' + 867 | 'x-amz-date' + ':' + dtStr + '\n' + 868 | 'x-amz-security-token' + ':' + my.access.sessionToken + '\n' + 869 | 'x-amz-target' + ':' + 'DynamoDB_20111205.' + op + '\n') + '\n' + 870 | rqBody); 871 | 872 | var sha = crypto.createHash('sha256'); 873 | sha.update(new Buffer(sts,'utf8')); 874 | var hmac = crypto.createHmac('sha256', my.access.secretAccessKey); 875 | hmac.update(sha.digest()); 876 | 877 | var auth = ('AWS3' + ' ' + 878 | 'AWSAccessKeyId' + '=' + my.access.accessKeyId + ',' + 879 | 'Algorithm' + '=' + 'HmacSHA256' + ',' + 880 | 'SignedHeaders' + '=' + 'host;x-amz-date;x-amz-target;x-amz-security-token' + ',' + 881 | 'Signature' + '=' + hmac.digest(encoding='base64')); 882 | 883 | var headers = { 'Host': my.endpoint, 884 | 'x-amz-date': dtStr, 885 | 'x-amz-security-token': my.access.sessionToken, 886 | 'X-amz-target': 'DynamoDB_20111205.' + op, 887 | 'X-amzn-authorization' : auth, 888 | 'date': dtStr, 889 | 'content-type': 'application/x-amz-json-1.0', 890 | 'content-length': Buffer.byteLength(rqBody,'utf8') }; 891 | 892 | var options = { host: my.endpoint, 893 | port: my.port, 894 | path: '/', 895 | method: 'POST', 896 | headers: headers, 897 | agent: my.agent }; 898 | 899 | var executeRequest = function(cb) { 900 | var req = http.request(options, function(res) { 901 | var body = ''; 902 | res.on('data', function(chunk) { 903 | body += chunk; 904 | }); 905 | res.on('end', function() { 906 | if (!cb) { 907 | // Do not call callback if it's already been called in the error handler. 908 | return; 909 | } 910 | try { 911 | var json = JSON.parse(body); 912 | } 913 | catch(err) { 914 | cb(err); 915 | return; 916 | } 917 | if(res.statusCode >= 300) { 918 | var err = new Error(op + ' [' + res.statusCode + ']: ' + (json.message || json['__type'])); 919 | err.type = json['__type']; 920 | err.statusCode = res.statusCode; 921 | err.requestId = res.headers['x-amzn-requestid']; 922 | err.message = op + ' [' + res.statusCode + ']: ' + (json.message || json['__type']); 923 | err.code = err.type.substring(err.type.lastIndexOf("#") + 1, err.type.length); 924 | err.data = json; 925 | cb(err); 926 | } 927 | else { 928 | cb(null, json); 929 | } 930 | }); 931 | }); 932 | 933 | req.on('error', function(err) { 934 | cb(err); 935 | cb = undefined; // Clear callback so we do not call it twice 936 | }); 937 | 938 | req.write(rqBody); 939 | req.end(); 940 | }; 941 | 942 | // see: https://github.com/amazonwebservices/aws-sdk-for-php/blob/master/sdk.class.php 943 | // for the original php retry logic used here 944 | (function retry(c) { 945 | executeRequest(function (err, json) { 946 | if(err != null) { 947 | if(err.statusCode === 500 || err.statusCode === 503) { 948 | if(c <= my.retries) { 949 | setTimeout(function() { 950 | retry(c + 1); 951 | }, Math.pow(4, c) * 100); 952 | } 953 | else 954 | cb(err); 955 | } 956 | else if(err.statusCode === 400 && 957 | err.code === "ProvisionedThroughputExceededException") { 958 | if(c === 0) { 959 | retry(c + 1); 960 | } 961 | else if(c <= my.retries && c <= 10) { 962 | setTimeout(function() { 963 | retry(c + 1); 964 | }, Math.pow(2, c-1) * Math.floor(25 * (Math.random() + 1))); 965 | } 966 | else 967 | cb(err); 968 | } 969 | else { 970 | cb(err); 971 | } 972 | } else { 973 | cb(null, json); 974 | } 975 | }); 976 | })(0); 977 | 978 | } 979 | }); 980 | }; 981 | 982 | /** 983 | * retrieves a temporary access key and secret from amazon STS 984 | * @param cb callback(err) err specified in case of error 985 | */ 986 | auth = function(cb) { 987 | // auth if necessary and always async 988 | if(my.access && my.access.expiration.getTime() < ((new Date).getTime() + 60000)) { 989 | //console.log('CLEAR AUTH: ' + my.access.expiration + ' ' + new Date); 990 | delete my.access; 991 | my.inAuth = false; 992 | } 993 | if(my.access) { 994 | cb(); 995 | return; 996 | } 997 | that.once('auth', cb); 998 | if(my.inAuth) 999 | return; 1000 | 1001 | my.inAuth = true; 1002 | 1003 | var cqs = ('AWSAccessKeyId' + '=' + encodeURIComponent(my.accessKeyId) + '&' + 1004 | 'Action' + '=' + 'GetSessionToken' + '&' + 1005 | 'DurationSeconds' + '=' + '3600' + '&' + 1006 | 'SignatureMethod' + '=' + 'HmacSHA256' + '&' + 1007 | 'SignatureVersion' + '=' + '2' + '&' + 1008 | 'Timestamp' + '=' + encodeURIComponent((new Date).toISOString().substr(0, 19) + 'Z') + '&' + 1009 | 'Version' + '=' + '2011-06-15'); 1010 | 1011 | var host = 'sts.amazonaws.com'; 1012 | 1013 | var sts = ('GET' + '\n' + 1014 | host + '\n' + 1015 | '/' + '\n' + 1016 | cqs); 1017 | 1018 | var hmac = crypto.createHmac('sha256', my.secretAccessKey); 1019 | hmac.update(sts); 1020 | cqs += '&' + 'Signature' + '=' + encodeURIComponent(hmac.digest(encoding='base64')); 1021 | 1022 | https.get({ host: host, path: '/?' + cqs }, function(res) { 1023 | var xml = ''; 1024 | res.on('data', function(chunk) { 1025 | xml += chunk; 1026 | }); 1027 | res.on('end', function() { 1028 | 1029 | //console.log(xml); 1030 | var st_r = /\(.*)\<\/SessionToken\>/.exec(xml); 1031 | var sak_r = /\(.*)\<\/SecretAccessKey\>/.exec(xml); 1032 | var aki_r = /\(.*)\<\/AccessKeyId\>/.exec(xml); 1033 | var e_r = /\(.*)\<\/Expiration\>/.exec(xml); 1034 | 1035 | if(st_r && sak_r && aki_r && e_r) { 1036 | my.access = { sessionToken: st_r[1], 1037 | secretAccessKey: sak_r[1], 1038 | accessKeyId: aki_r[1], 1039 | expiration: new Date(e_r[1]) }; 1040 | 1041 | //console.log('AUTH OK: ' + require('util').inspect(my.access) + '\n' + 1042 | // ((my.access.expiration - new Date) - 60000)); 1043 | 1044 | my.inAuth = false; 1045 | that.emit('auth'); 1046 | } 1047 | else { 1048 | var tp_r = /\(.*)\<\/Type\>/.exec(xml); 1049 | var cd_r = /\(.*)\<\/Code\>/.exec(xml); 1050 | var msg_r = /\(.*)\<\/Message\>/.exec(xml); 1051 | 1052 | if(tp_r && cd_r && msg_r) { 1053 | var err = new Error('AUTH [' + cd_r[1] + ']: ' + msg_r[1]); 1054 | err.type = tp_r[1]; 1055 | err.code = cd_r[1]; 1056 | my.inAuth = false; 1057 | that.emit('auth', err); 1058 | } 1059 | else { 1060 | var err = new Error('AUTH: Unknown Error'); 1061 | my.inAuth = false; 1062 | that.emit('auth', err); 1063 | } 1064 | } 1065 | }); 1066 | 1067 | }).on('error', function(err) { 1068 | my.inAuth = false; 1069 | that.emit('auth', err); 1070 | }); 1071 | }; 1072 | 1073 | fwk.method(that, 'createTable', createTable, _super); 1074 | fwk.method(that, 'listTables', listTables, _super); 1075 | fwk.method(that, 'describeTable', describeTable, _super); 1076 | fwk.method(that, 'updateTable', updateTable, _super); 1077 | fwk.method(that, 'deleteTable', deleteTable, _super); 1078 | 1079 | fwk.method(that, 'putItem', putItem, _super); 1080 | fwk.method(that, 'getItem', getItem, _super); 1081 | fwk.method(that, 'deleteItem', deleteItem, _super); 1082 | fwk.method(that, 'updateItem', updateItem, _super); 1083 | fwk.method(that, 'query', query, _super); 1084 | fwk.method(that, 'batchGetItem', batchGetItem, _super); 1085 | fwk.method(that, 'batchWriteItem', batchWriteItem, _super); 1086 | fwk.method(that, 'scan', scan, _super); 1087 | 1088 | 1089 | // for testing purpose 1090 | fwk.method(that, 'objToDDB', objToDDB, _super); 1091 | fwk.method(that, 'scToDDB', scToDDB, _super); 1092 | fwk.method(that, 'objFromDDB', objFromDDB, _super); 1093 | fwk.method(that, 'arrFromDDB', arrFromDDB, _super); 1094 | 1095 | 1096 | fwk.getter(that, 'consumedCapacity', my, 'consumedCapacity'); 1097 | fwk.getter(that, 'schemaTypes', my, 'schemaTypes'); 1098 | 1099 | return that; 1100 | }; 1101 | 1102 | 1103 | exports.ddb = ddb; 1104 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamodb", 3 | "version": "0.3.7", 4 | "description": "Simple DynamoDB Library for Node.js", 5 | "keywords": [ 6 | "dynamo", 7 | "node", 8 | "db", 9 | "aws", 10 | "amazon" 11 | ], 12 | "homepage": "https://github.com/teleportd/node-dynamodb", 13 | "scripts": { 14 | "test": "make test" 15 | }, 16 | "author": { 17 | "name": "Stanislas Polu", 18 | "email": "stan@teleportd.com", 19 | "url": "http://twitter.com/spolu" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/teleportd/node-dynamodb.git" 24 | }, 25 | "license": "MIT", 26 | "contributors": [ 27 | "Karl Seguin", 28 | "Juan Carlos del Valle", 29 | "Pablo Cantero", 30 | "Chris Stivers", 31 | "Juho Mäkinen", 32 | "Paul Tax", 33 | "Alex Bosworth", 34 | "Jimb Esser", 35 | "Anthony Moi", 36 | "Alex Gorbatchev" 37 | ], 38 | "dependencies": { 39 | "fwk": "^1.1.8" 40 | }, 41 | "main": "./lib/ddb", 42 | "engines": { 43 | "node": ">=0.4.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/batch.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | var fwk = require('fwk'); 23 | var assert = require('assert'); 24 | 25 | // cfg 26 | var cfg = fwk.populateConfig(require("./config.js").config); 27 | 28 | var ddb = require('../lib/ddb').ddb({accessKeyId: cfg['DYNAMODB_ACCESSKEYID'], 29 | secretAccessKey: cfg['DYNAMODB_SECRETACCESSKEY']}); 30 | 31 | 32 | var toPut = {} 33 | toPut[cfg['DYNAMODB_TEST_TABLE1']] = [{sha: 'blabla', status: 'on'}, 34 | {sha: 'bloblo', status: 'off'}, 35 | {sha: 'another', status: 'pending'}]; 36 | 37 | var toDelete = {}; 38 | toDelete[cfg['DYNAMODB_TEST_TABLE1']] = ['blabla', 'bloblo']; 39 | 40 | var toGet = {}; 41 | toGet[cfg['DYNAMODB_TEST_TABLE1']] = { keys: ['another', 'blabla', 'bloblo']}; 42 | 43 | var seq = [ 44 | function(next) { 45 | ddb.batchWriteItem(toPut, 46 | {}, 47 | function(err, res, cap) { 48 | if(err) 49 | console.log(err); 50 | assert.equal(err, null, 'BatchWriteItem error occured'); 51 | //console.log('RES: ' + require('util').inspect(res, false, 20)); 52 | next(); 53 | }); 54 | }, 55 | function(next) { 56 | ddb.batchWriteItem({}, 57 | toDelete, 58 | function(err, res, cap) { 59 | if(err) 60 | console.log(err); 61 | assert.equal(err, null, 'BatchWriteItem error occured'); 62 | //console.log('RES: ' + require('util').inspect(res, false, 20)); 63 | next(); 64 | }); 65 | }, 66 | function(next) { 67 | ddb.batchGetItem(toGet, function(err, res, cap) { 68 | if(err) console.log(err); 69 | assert.equal(err, null, 'BatchGetItem error occured'); 70 | //console.log('RES: ' + require('util').inspect(res, false, 20)); 71 | next(); 72 | }); 73 | } 74 | ]; 75 | 76 | (function sdo(seq, i) { 77 | seq[i](function() { 78 | if(i+1 < seq.length) 79 | sdo(seq, i + 1); 80 | else 81 | console.log('integration : ok'); 82 | }); 83 | })(seq, 0); -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | var fwk = require('fwk'); 23 | var config = fwk.baseConfig(); 24 | 25 | config['DYNAMODB_ACCESSKEYID'] = 'REPLACE_IN_ENV_OR_ARGS'; 26 | config['DYNAMODB_SECRETACCESSKEY'] = 'REPLACE_IN_ENV_OR_ARGS'; 27 | config['DYNAMODB_TEST_TABLE1'] = 'REPLACE_IN_ENV_OR_ARGS'; 28 | config['DYNAMODB_TEST_TABLE2'] = 'REPLACE_IN_ENV_OR_ARGS'; 29 | 30 | exports.config = config; 31 | -------------------------------------------------------------------------------- /test/integration.item.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | var fwk = require('fwk'); 23 | var assert = require('assert'); 24 | 25 | // cfg 26 | var cfg = fwk.populateConfig(require("./config.js").config); 27 | 28 | var ddb = require('../lib/ddb').ddb({accessKeyId: cfg['DYNAMODB_ACCESSKEYID'], 29 | secretAccessKey: cfg['DYNAMODB_SECRETACCESSKEY']}); 30 | 31 | 32 | var item = { date: (new Date).getTime(), 33 | sha: '3d2d696', 34 | usr: 'spolu', 35 | val: 4 }; 36 | var updt = { sha: '3d2d696', 37 | usr: 'nouser', 38 | val: 8 }; 39 | var ritem = { date: (new Date).getTime(), 40 | hash: 'test', 41 | range: '3d2d696', 42 | usr: 'spolu', 43 | val: 4 }; 44 | 45 | var seq = [ 46 | function(next) { 47 | ddb.putItem(cfg['DYNAMODB_TEST_TABLE1'], item, {}, function(err, res, cap) { 48 | if(err) 49 | console.log(err); 50 | assert.equal(err, null, 'putItem error occured'); 51 | next(); 52 | }); 53 | }, 54 | 55 | function(next) { 56 | ddb.getItem(cfg['DYNAMODB_TEST_TABLE1'], '3d2d696', null, { consistentRead: true }, function(err, res, cap) { 57 | if(err) 58 | console.log(err); 59 | assert.equal(err, null, 'getItem error occured'); 60 | assert.deepEqual(res, item, 'getitem item mismatch'); 61 | next(); 62 | }); 63 | }, 64 | 65 | function(next) { 66 | ddb.updateItem(cfg['DYNAMODB_TEST_TABLE1'], '3d2d696', null, 67 | { usr: { value: 'nouser' }, 68 | date: { action: 'DELETE' }, 69 | val: { value: 4, action: 'ADD'} }, 70 | {}, function(err, res, cap) { 71 | if(err) 72 | console.log(err); 73 | assert.equal(err, null, 'updateItem error occured'); 74 | next(); 75 | }); 76 | }, 77 | 78 | function(next) { 79 | ddb.getItem(cfg['DYNAMODB_TEST_TABLE1'], '3d2d696', null, { consistentRead: true }, function(err, res, cap) { 80 | if(err) 81 | console.log(err); 82 | assert.equal(err, null, 'getItem error occured'); 83 | assert.deepEqual(res, updt, 'getitem item mismatch'); 84 | next(); 85 | }); 86 | }, 87 | 88 | function(next) { 89 | ddb.deleteItem(cfg['DYNAMODB_TEST_TABLE1'], '3d2d696', null, {returnValues: 'ALL_OLD'}, 90 | function(err, res, cap) { 91 | if(err) 92 | console.log(err); 93 | assert.equal(err, null, 'getItem error occured'); 94 | assert.deepEqual(res, updt, 'getitem item mismatch'); 95 | next(); 96 | }); 97 | }, 98 | 99 | function(next) { 100 | ddb.getItem(cfg['DYNAMODB_TEST_TABLE1'], '3d2d696', null, { consistentRead: true }, function(err, res, cap) { 101 | if(err) 102 | console.log(err); 103 | assert.equal(err, null, 'getItem error occured'); 104 | assert.equal(res, undefined, 'getItem error occured'); 105 | next(); 106 | }); 107 | } 108 | 109 | ]; 110 | 111 | (function sdo(seq, i) { 112 | seq[i](function() { 113 | if(i+1 < seq.length) 114 | sdo(seq, i + 1); 115 | else 116 | console.log('integration : ok'); 117 | }); 118 | })(seq, 0); 119 | 120 | -------------------------------------------------------------------------------- /test/unit.toDDB.js: -------------------------------------------------------------------------------- 1 | // Copyright Teleportd Ltd. and other Contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | var fwk = require('fwk'); 23 | var assert = require('assert'); 24 | 25 | var objToDDB = require('../lib/ddb').ddb({accessKeyId: "", secretAccessKey: ""}).objToDDB; 26 | var scToDDB = require('../lib/ddb').ddb({accessKeyId: "", secretAccessKey: ""}).scToDDB; 27 | 28 | assert.deepEqual({key : { S : "str"}}, objToDDB({key : "str"})); 29 | assert.deepEqual({key : { N : "1234"}}, objToDDB({key : 1234})); 30 | assert.deepEqual({key : { SS : ["foo"]}}, objToDDB({key : ["foo"]})); 31 | assert.deepEqual({key : { SS : ["foo", "bar"]}}, objToDDB({key : ["foo", "bar"]})); 32 | assert.deepEqual({key : { NS : ["42"]}}, objToDDB({key : [42]})); 33 | assert.deepEqual({key : { NS : ["4", "5", "42"]}}, objToDDB({key : [4, 5, 42]})); 34 | assert.deepEqual({}, objToDDB({key : null})); 35 | assert.deepEqual({"key1":{"S":"str"}}, objToDDB({key1:"str", key : null})); 36 | assert.deepEqual({"key1":{"N":"1234"}}, objToDDB({key1:1234, key : null})); 37 | 38 | var expect = { 39 | str : {"S" : "string"}, 40 | stringSet : { SS : ["foo", "bar"]} 41 | }; 42 | assert.deepEqual(expect, objToDDB({str : "string", stringSet : ["foo", "bar"]})); 43 | 44 | console.log('objToDDB : ok'); 45 | 46 | assert.deepEqual({ SS : ["foo"]}, scToDDB(["foo"])); 47 | assert.deepEqual({ SS : ["foo", "bar"]}, scToDDB(["foo", "bar"])); 48 | 49 | console.log('scToDDB : ok'); 50 | 51 | --------------------------------------------------------------------------------