├── .babelrc ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .jshintrc ├── .npmignore ├── .nvmrc ├── ChangeLog ├── LICENSE ├── README.md ├── dist ├── dynamodbjs.js └── dynamodbjs.min.js ├── lib ├── batch.js ├── dynamodb.js ├── sqlparser.js └── transact.js ├── package.json ├── src └── sqlparser.jison ├── test ├── 000-index.js ├── credentials ├── lib │ └── common.js ├── res │ ├── 000-create_table.yaml │ ├── 010-describe.yaml │ ├── 030-insert.yaml │ ├── 033-batch_insert.yaml │ ├── 040-update.yaml │ ├── 050-replace.yaml │ ├── 060-delete.yaml │ ├── 070-query.yaml │ ├── 090-scan.yaml │ ├── 991-drop_index.yaml │ └── 999-drop_table.yaml └── tests │ ├── 000-createTable.js │ ├── 010-describe.js │ ├── 020-specialSigns.js │ ├── 030-insert.js │ ├── 031-insert_or_update.js │ ├── 032-insert_or_replace.js │ ├── 040-update.js │ ├── 050-replace.js │ ├── 060-delete.js │ ├── 070-get.js │ ├── 080-query.js │ ├── 090-scan.js │ ├── 0A0-batch_write.js │ ├── 0B0-batch_read.js │ ├── 0C0-transact.js │ └── 999-deleteTable.js └── webpack.browser.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | 2 | name: npm test 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | - main 9 | # temporary disable until build workflow is configured properly 10 | # branches-ignore: 11 | # - '**' 12 | paths: 13 | #- 'dist/**' 14 | - '**' 15 | jobs: 16 | 17 | test: 18 | #if: false # always skip job 19 | runs-on: ubuntu-latest 20 | 21 | # services: 22 | # dynamodb-server: 23 | # image: awspilotdev/dynamodb-ui 24 | # # env: 25 | # # k: v 26 | # ports: 27 | # - 8000 28 | 29 | strategy: 30 | max-parallel: 4 31 | matrix: 32 | node-version: 33 | #- 8.x 34 | - 10.x 35 | - 12.x 36 | - 14.x 37 | include: 38 | # - node-version: 8.x 39 | # region: "us-east-1" 40 | - node-version: 10.x 41 | region: "us-east-2" 42 | - node-version: 12.x 43 | region: "us-west-1" 44 | - node-version: 14.x 45 | region: "us-west-2" 46 | # can not use secrets here 47 | # secret_access_key: ${{ secrets.KEYSECRET_AKIAWS5577QLD5TL6AZN }} 48 | 49 | steps: 50 | - uses: actions/checkout@v2 51 | - name: Use Node.js ${{ matrix.node-version }} 52 | uses: actions/setup-node@v1 53 | with: 54 | node-version: ${{ matrix.node-version }} 55 | 56 | - run: npm install 57 | # peer dependency 58 | - run: npm install aws-sdk 59 | #- run: npm run build --if-present 60 | - 61 | shell: bash 62 | run: CI=true npm test 63 | env: 64 | AWS_ACCESS_KEY_ID: AKIAWS5577QLD5TL6AZN 65 | AWS_SECRET_ACCESS_KEY: ${{ secrets.KEYSECRET_AKIAWS5577QLD5TL6AZN }} 66 | AWS_REGION: ${{ matrix.region }} 67 | CI: true 68 | 69 | tag: 70 | name: Tag 71 | needs: test 72 | runs-on: ubuntu-latest 73 | steps: 74 | - name: checkout 75 | uses: actions/checkout@v2 76 | - uses: Klemensas/action-autotag@stable 77 | with: 78 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 79 | tag_prefix: "v" 80 | #tag_suffix: " (beta)" 81 | #tag_message: "Custom message goes here." 82 | 83 | build: 84 | needs: tag 85 | runs-on: ubuntu-latest 86 | 87 | strategy: 88 | max-parallel: 1 89 | matrix: 90 | node-version: 91 | - 12.x 92 | include: 93 | - node-version: 12.x 94 | region: "us-east-1" 95 | 96 | steps: 97 | - uses: actions/checkout@v2 98 | - name: Use Node.js ${{ matrix.node-version }} 99 | uses: actions/setup-node@v1 100 | with: 101 | node-version: ${{ matrix.node-version }} 102 | 103 | - run: npm install 104 | # peer dependency 105 | - run: npm install aws-sdk 106 | #- run: npm run build --if-present 107 | - 108 | shell: bash 109 | run: npm run build:browser 110 | - 111 | shell: bash 112 | run: git status 113 | env: 114 | AWS_ACCESS_KEY_ID: AKIAWS5577QLD5TL6AZN 115 | AWS_SECRET_ACCESS_KEY: ${{ secrets.KEYSECRET_AKIAWS5577QLD5TL6AZN }} 116 | AWS_REGION: ${{ matrix.region }} 117 | - 118 | name: Commit dynamodb.js 119 | run: | 120 | git config --global user.name 'Adrian Praja' 121 | git config --global user.email 'adrianpraja@users.noreply.github.com' 122 | git commit -m "Browser Dist" dist/dynamodbjs.js dist/dynamodbjs.min.js || echo "Commit failed" 123 | git push 124 | 125 | env: 126 | GIT_COMMITTED_AT: ${{ github.committed_at }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | experiments/ 3 | .DS_Store 4 | browserify.sh 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "undef": true, 3 | "unused": true, 4 | "globals": { 5 | "MY_GLOBAL": true 6 | } 7 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | test/ 4 | src/ 5 | experiments/ 6 | 7 | package-lock.json 8 | browserify.js 9 | browserify.sh 10 | build-parser.sh 11 | 12 | 13 | .nvmrc 14 | .babelrc 15 | .travis.yml 16 | 17 | webpack.browser.js 18 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2.0.2 2 | - support for delete attribute in transaction() update() and insert_or_update() 3 | - console.warn every time schema is not provided and a describeTable is called ( 3.x.x will throw err instead of calling describeTable ) 4 | - moved testing from Travis to Github Actions 5 | - fix describeTable() 6 | - fix ProvisionedThroughput in sql 'DESCRIBE TABLE tbl_name' ( updated @awspilot/dynamodb-sql-util) 7 | - fix 'DESCRIBE TABLE tbl_name' when AWS does not return BillingModeSummary 8 | - updated (dev)dependencies 9 | 2.0.0 10 | - removed npm-shrinkwrap, was causing npm install to also install dev dependencies ( npm bug ?) 11 | - aws-sdk as peer dependency ( shrinks deply size for environments with embedded aws-sdk eg. Lambda ) 12 | - experimental transactWrite support 13 | 1.5.1 14 | - import only dynamodb from aws-sdk 15 | 1.5.0 16 | - wrap around batchGetItem - .batch().table().get().get().read() 17 | - wrap around batchWriteItem - .batch().table().put().del().write() 18 | 1.4.0 19 | - wrap around batchWriteItem - .batch().table().put().put().del().write() 20 | 1.3.0 21 | - 1.2.0+ will follow SemVer (major.minor.patch) and increment "minor" for new features 22 | - JSON keys in SQL can now be keywords without the need of wrapping: eg. { "keyword": 1 } can now be just { keyword: 1 } 23 | 1.2.11 24 | - SQL LIST and MAP support nested StringSet, NumberSet, BinarySet and Binary 25 | - SQL support insert/update BinarySet data type (BS) using Buffer.from( , 'base64') 26 | - SQL support insert/update binary data type (B) using Buffer.from( , 'base64') 27 | 1.2.10 28 | - SQL support for CREATE TABLE 29 | - pass an Array as parameter to select eg. .select(['attr1','attr2']) 30 | 1.2.9 31 | - SQL support for DROP TABLE 32 | - SQL support for DROP INDEX index_name ON tbl_name 33 | - updated SQL parser to support keywords in attribute names, table names and index names (eg: WHERE where = 5) 34 | 1.2.8 35 | 36 | 1.2.7 37 | - fixes a HUGE bug where float numbers may lose decimals in OOP .query(), SQL .query() do not have this bug 38 | 1.2.6 39 | - schema() : new method to supply primary key definitions for tables to avoid unnecesary describeTable calls 40 | 1.2.5 41 | - support for basic expressions that ca be resolved in place, eg. new Date().getTime() - 1000*60*60*24 42 | 1.2.4 43 | - tests for empty strings + fix .query() and .scan() 44 | 1.2.3 45 | - parse / stringify - configurable handling for empty strings, StringSet and NumberSet 46 | - support for Math javascript object in SQL statements 47 | 1.2.2 48 | - support for SELECT ( without HAVING ) 49 | 1.2.1 50 | - basic support for SCAN sql 51 | 1.2.0 52 | - dynalite removed from dev dependencies 53 | 1.1.8 54 | - RawFilterExpression, RawProjectionExpression 55 | - tests for new Set() 56 | 1.1.7 57 | - support for new Date() in SQL 58 | - sql tests 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Adrian Praja 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ### @awspilot/dynamodb 3 | 4 | ##### @awspilot/dynamodb is a NodeJS and Browser utility to access Amazon DynamoDB databases 5 | 6 | 7 | ![npm test](https://github.com/awspilot/dynamodb-oop/workflows/npm%20test/badge.svg) 8 | [![Downloads](https://img.shields.io/npm/dm/@awspilot/dynamodb.svg?maxAge=2592000)](https://www.npmjs.com/package/@awspilot/dynamodb) 9 | [![Downloads](https://img.shields.io/npm/dy/@awspilot/dynamodb.svg?maxAge=2592000)](https://www.npmjs.com/package/@awspilot/dynamodb) 10 | ![License](https://img.shields.io/github/license/awspilot/dynamodb-oop.svg) 11 | 12 | 13 | 14 | ``` 15 | 16 | npm install @awspilot/dynamodb 17 | 18 | ``` 19 | 20 | 21 | #### [ Documentation and Examples ](https://awspilot.dev/) 22 | 23 | WARN: version 2.x moved aws-sdk to peer dependencies. 24 | You should add aws-sdk as dependency in your project. 25 | 26 | #### [ Lambda Layers available ](https://awspilot.dev/pages/layer/) 27 | 28 | --- 29 | Note: 30 | 31 | Please know your npm sources and avoid including npm packages for every small thing 32 | According to [this post](https://iamakulov.com/notes/npm-malicious-packages/) there were packages in the npm registry that steal your environment variables 33 | -------------------------------------------------------------------------------- /lib/batch.js: -------------------------------------------------------------------------------- 1 | 2 | var util = require('@awspilot/dynamodb-util') 3 | 4 | //util.config.empty_string_replace_as = o.empty_string_replace_as; 5 | 6 | util.config.stringset_parse_as_set = true; 7 | util.config.numberset_parse_as_set = true; 8 | util.config.binaryset_parse_as_set = true; 9 | 10 | 11 | function Batch( $client, config ) { 12 | 13 | this.events = config.events // global events 14 | this.describeTables = config.describeTables 15 | this.return_explain = config.return_explain 16 | this.local_events = {} 17 | this.client = $client 18 | 19 | this.current_table = undefined; 20 | this.err = undefined; 21 | this.payload = { 22 | RequestItems: {} 23 | } 24 | } 25 | 26 | 27 | Batch.prototype.routeCall = function(method, params, reset ,callback ) { 28 | var $this = this 29 | this.events.beforeRequest.apply( this, [ method, params ]) 30 | 31 | this.client[method]( params, function( err, data ) { 32 | 33 | if (err) 34 | $this.events.error.apply( $this, [ method, err , params ] ) 35 | 36 | // if ((data || {}).hasOwnProperty('ConsumedCapacity') ) 37 | // $this.ConsumedCapacity = data.ConsumedCapacity 38 | 39 | // if ( reset === true ) 40 | // $this.reset() 41 | 42 | callback.apply( $this, [ err, data ] ) 43 | }) 44 | } 45 | 46 | 47 | 48 | 49 | Batch.prototype.resume = function() {} 50 | Batch.prototype.table = function( tbl_name ) { 51 | if (this.err) 52 | return this; 53 | 54 | this.current_table = tbl_name 55 | return this; 56 | } 57 | 58 | 59 | Batch.prototype.item = function() {} 60 | 61 | 62 | Batch.prototype.put = function( item ) { 63 | if (this.err) 64 | return this; 65 | 66 | if (!this.current_table) { 67 | this.err = { code: 'INVALID_TABLE', message: 'use .table( tbl_name ) before .put()'} 68 | return this; 69 | } 70 | 71 | if (typeof item !== 'object') { 72 | this.err = { code: 'INVALID_ITEM', message: '.put( item ) - must pass in an Object'} 73 | return this; 74 | } 75 | 76 | if (!this.payload.RequestItems.hasOwnProperty( this.current_table )) 77 | this.payload.RequestItems[ this.current_table ] = [] 78 | 79 | this.payload.RequestItems[ this.current_table ].push({ 80 | PutRequest: { 81 | Item: util.stringify( item ).M 82 | } 83 | }) 84 | 85 | return this; 86 | } 87 | 88 | Batch.prototype.delete = function( item ) { 89 | if (this.err) 90 | return this; 91 | 92 | if (!this.current_table) { 93 | this.err = { code: 'INVALID_TABLE', message: 'use .table( tbl_name ) before .delete()'} 94 | return this; 95 | } 96 | 97 | if (typeof item !== 'object') { 98 | this.err = { code: 'INVALID_ITEM', message: '.del( item ) must pass in an Object'} 99 | return this; 100 | } 101 | 102 | if (!this.payload.RequestItems.hasOwnProperty( this.current_table )) 103 | this.payload.RequestItems[ this.current_table ] = [] 104 | 105 | this.payload.RequestItems[ this.current_table ].push({ 106 | DeleteRequest: { 107 | Key: util.stringify( item ).M 108 | } 109 | }) 110 | 111 | return this; 112 | } 113 | Batch.prototype.del = Batch.prototype.delete; 114 | 115 | 116 | 117 | 118 | Batch.prototype.get = function( item ) { 119 | if (this.err) 120 | return this; 121 | 122 | if (!this.current_table) { 123 | this.err = { code: 'INVALID_TABLE', message: 'use .table( tbl_name ) before .get()'} 124 | return this; 125 | } 126 | 127 | if (typeof item !== 'object') { 128 | this.err = { code: 'INVALID_ITEM', message: '.get( item ) - must pass in an Object'} 129 | return this; 130 | } 131 | 132 | if (!this.payload.RequestItems.hasOwnProperty( this.current_table )) 133 | this.payload.RequestItems[ this.current_table ] = { 134 | Keys: [], 135 | // ExpressionAttributeNames 136 | // ProjectionExpression: "" 137 | // AttributesToGet: [] 138 | ConsistentRead: false, 139 | } 140 | 141 | this.payload.RequestItems[ this.current_table ].Keys.push( util.stringify( item ).M ) 142 | 143 | return this; 144 | } 145 | 146 | 147 | 148 | Batch.prototype.consistent_read = function( $value ) { 149 | if (this.err) 150 | return this; 151 | 152 | if (!this.current_table) { 153 | this.err = { code: 'INVALID_TABLE', message: 'use .table( tbl_name ) before .consistent_read()'} 154 | return this; 155 | } 156 | 157 | if ( value === undefined ) { 158 | value = true 159 | } else { 160 | var value = JSON.parse(JSON.stringify($value)) 161 | } 162 | 163 | if (!this.payload.RequestItems.hasOwnProperty( this.current_table )) { 164 | this.payload.RequestItems[ this.current_table ] = { 165 | Keys: [], 166 | // ExpressionAttributeNames 167 | // ProjectionExpression: "" 168 | // AttributesToGet: [] 169 | ConsistentRead: value, 170 | } 171 | } else { 172 | this.payload.RequestItems[ this.current_table ].ConsistentRead = value 173 | } 174 | 175 | return this 176 | } 177 | 178 | 179 | 180 | 181 | 182 | Batch.prototype.read = function( cb ) { 183 | if (this.err) 184 | return cb(this.err); 185 | 186 | var $this = this 187 | 188 | var $thisQuery = this.payload; 189 | 190 | if (typeof this.local_events['beforeRequest'] === "function" ) 191 | this.local_events['beforeRequest']('batchWriteItem', $thisQuery) 192 | 193 | // @todo: implement promise 194 | 195 | this.routeCall('batchGetItem', $thisQuery , true , function(err,data) { 196 | if (err) 197 | return typeof cb !== "function" ? null : cb.apply( this, [ err, false ] ) 198 | 199 | var ret = {} 200 | 201 | Object.keys(data.Responses).map(function( tbl_name ) { 202 | if (!ret.hasOwnProperty(tbl_name)) 203 | ret[tbl_name] = [] 204 | 205 | ret[tbl_name] = util.parse({ L : 206 | (data.Responses[tbl_name] || []).map(function(item) { return {'M': item } }) 207 | }) 208 | 209 | }) 210 | 211 | this.rawUnprocessedKeys = data.UnprocessedKeys; 212 | 213 | typeof cb !== "function" ? null : cb.apply( this, [ err, ret, data ]) 214 | 215 | }) 216 | 217 | } 218 | Batch.prototype.write = function( cb ) { 219 | 220 | if (this.err) 221 | return cb(this.err); 222 | 223 | var $this = this 224 | 225 | var $thisQuery = this.payload; 226 | 227 | if (typeof this.local_events['beforeRequest'] === "function" ) 228 | this.local_events['beforeRequest']('batchWriteItem', $thisQuery) 229 | 230 | // @todo: implement promise 231 | 232 | //console.log(JSON.stringify($thisQuery, null, "\t")) 233 | 234 | this.routeCall('batchWriteItem', $thisQuery , true , function(err,data) { 235 | if (err) 236 | return typeof cb !== "function" ? null : cb.apply( this, [ err, false ] ) 237 | 238 | typeof cb !== "function" ? null : cb.apply( this, [ err, data, data ]) 239 | 240 | }) 241 | 242 | 243 | } 244 | 245 | 246 | 247 | module.exports = Batch; 248 | -------------------------------------------------------------------------------- /lib/transact.js: -------------------------------------------------------------------------------- 1 | 2 | var keywords = [ 3 | "abort","absolute","action","add","after","agent","aggregate","all","allocate","alter","analyze","and","any","archive","are","array", 4 | "as","asc","ascii","asensitive","assertion","asymmetric","at","atomic","attach","attribute","auth","authorization","authorize","auto", 5 | "avg","back","backup","base","batch","before","begin","between","bigint","binary","bit","blob","block","boolean","both","breadth", 6 | "bucket","bulk","by","byte","call","called","calling","capacity","cascade","cascaded","case","cast","catalog","char","character", 7 | "check","class","clob","close","cluster","clustered","clustering","clusters","coalesce","collate","collation","collection","column", 8 | "columns","combine","comment","commit","compact","compile","compress","condition","conflict","connect","connection","consistency", 9 | "consistent","constraint","constraints","constructor","consumed","continue","convert","copy","corresponding","count","counter", 10 | "create","cross","cube","current","cursor","cycle","data","database","date","datetime","day","deallocate","dec","decimal","declare", 11 | "default","deferrable","deferred","define","defined","definition","delete","delimited","depth","deref","desc","describe","descriptor", 12 | "detach","deterministic","diagnostics","directories","disable","disconnect","distinct","distribute","do","domain","double","drop","dump", 13 | "duration","dynamic","each","element","else","elseif","empty","enable","end","equal","equals","error","escape","escaped","eval","evaluate", 14 | "exceeded","except","exception","exceptions","exclusive","exec","execute","exists","exit","explain","explode","export","expression","extended", 15 | "external","extract","fail","false","family","fetch","fields","file","filter","filtering","final","finish","first","fixed","flattern","float", 16 | "for","force","foreign","format","forward","found","free","from","full","function","functions","general","generate","get","glob","global","go", 17 | "goto","grant","greater","group","grouping","handler","hash","have","having","heap","hidden","hold","hour","identified","identity","if","ignore", 18 | "immediate","import","in","including","inclusive","increment","incremental","index","indexed","indexes","indicator","infinite","initially","inline", 19 | "inner","innter","inout","input","insensitive","insert","instead","int","integer","intersect","interval","into","invalidate","is","isolation", 20 | "item","items","iterate","join","key","keys","lag","language","large","last","lateral","lead","leading","leave","left","length","less","level", 21 | "like","limit","limited","lines","list","load","local","localtime","localtimestamp","location","locator","lock","locks","log","loged","long", 22 | "loop","lower","map","match","materialized","max","maxlen","member","merge","method","metrics","min","minus","minute","missing","mod","mode", 23 | "modifies","modify","module","month","multi","multiset","name","names","national","natural","nchar","nclob","new","next","no","none","not", 24 | "null","nullif","number","numeric","object","of","offline","offset","old","on","online","only","opaque","open","operator","option","or","order", 25 | "ordinality","other","others","out","outer","output","over","overlaps","override","owner","pad","parallel","parameter","parameters","partial", 26 | "partition","partitioned","partitions","path","percent","percentile","permission","permissions","pipe","pipelined","plan","pool","position", 27 | "precision","prepare","preserve","primary","prior","private","privileges","procedure","processed","project","projection","property","provisioning", 28 | "public","put","query","quit","quorum","raise","random","range","rank","raw","read","reads","real","rebuild","record","recursive","reduce","ref", 29 | "reference","references","referencing","regexp","region","reindex","relative","release","remainder","rename","repeat","replace","request","reset", 30 | "resignal","resource","response","restore","restrict","result","return","returning","returns","reverse","revoke","right","role","roles","rollback", 31 | "rollup","routine","row","rows","rule","rules","sample","satisfies","save","savepoint","scan","schema","scope","scroll","search","second","section", 32 | "segment","segments","select","self","semi","sensitive","separate","sequence","serializable","session","set","sets","shard","share","shared","short", 33 | "show","signal","similar","size","skewed","smallint","snapshot","some","source","space","spaces","sparse","specific","specifictype","split","sql", 34 | "sqlcode","sqlerror","sqlexception","sqlstate","sqlwarning","start","state","static","status","storage","store","stored","stream","string","struct", 35 | "style","sub","submultiset","subpartition","substring","subtype","sum","super","symmetric","synonym","system","table","tablesample","temp","temporary", 36 | "terminated","text","than","then","throughput","time","timestamp","timezone","tinyint","to","token","total","touch","trailing","transaction","transform", 37 | "translate","translation","treat","trigger","trim","true","truncate","ttl","tuple","type","under","undo","union","unique","unit","unknown","unlogged", 38 | "unnest","unprocessed","unsigned","until","update","upper","url","usage","use","user","users","using","uuid","vacuum","value","valued","values","varchar", 39 | "variable","variance","varint","varying","view","views","virtual","void","wait","when","whenever","where","while","window","with","within","without","work", 40 | "wrapped","write","year","zone" 41 | ] 42 | 43 | 44 | 45 | var _expr_attribute = function( attr ) { 46 | return attr 47 | .split('.').join("_dot_") 48 | .split('-').join("_minus_") 49 | } 50 | 51 | var util = require('@awspilot/dynamodb-util') 52 | 53 | //util.config.empty_string_replace_as = o.empty_string_replace_as; 54 | 55 | util.config.stringset_parse_as_set = true; 56 | util.config.numberset_parse_as_set = true; 57 | util.config.binaryset_parse_as_set = true; 58 | 59 | function Transact( $client, config ) { 60 | 61 | this.err = null; 62 | this.reset() 63 | this.TransactItems = [] 64 | 65 | 66 | 67 | this.events = config.events // global events 68 | this.describeTables = config.describeTables 69 | this.return_explain = config.return_explain 70 | this.local_events = {} 71 | this.client = $client 72 | 73 | 74 | } 75 | Transact.prototype.reset = function() { 76 | this.pending = { 77 | if: null, 78 | ExpressionAttributeNames: {}, 79 | ExpressionAttributeValues: {}, 80 | ConditionExpression: [], 81 | } 82 | } 83 | Transact.prototype.table = function($tableName) { 84 | this.reset() 85 | this.pending.tableName = $tableName; 86 | return this; 87 | } 88 | Transact.prototype.debug = function() { 89 | console.log("pending=",JSON.stringify(this.pending,null,"\t")) 90 | return this; 91 | } 92 | Transact.prototype.where = function( key ) { 93 | this.pending.where = key; 94 | return this; 95 | } 96 | Transact.prototype.eq = function( value ) { 97 | if (this.pending.where) { 98 | if (!this.pending.hasOwnProperty('wheres')) 99 | this.pending.wheres = {} 100 | 101 | this.pending.wheres[this.pending.where] = value 102 | delete this.pending.where; 103 | } 104 | 105 | if (this.pending.if !== null) { 106 | 107 | var _name = this._namefy( this.pending.if.attr, '#ifeq_' ) 108 | var _vname = this._valuefy( this.pending.if.attr, ':ifeq_', value ) 109 | this.pending.ConditionExpression.push( 110 | "( " + _name + " = " + _vname + " )" 111 | ) 112 | this.pending.if = null; 113 | return this 114 | } 115 | 116 | return this; 117 | } 118 | 119 | Transact.prototype._namefy = function( name, prefix ) { 120 | var $this=this; 121 | if ( 122 | ( keywords.indexOf(name.toLowerCase()) === -1 ) && 123 | ( name.match(/^[a-zA-Z]+$/) !== null ) 124 | ) 125 | return name; 126 | 127 | 128 | var _att_names = [] 129 | name.split('.').map(function( n, idx ) { 130 | 131 | // if its an array path, remove it and add it later 132 | // for (const match of "hello[1][12][14]".matchAll(/([^\[]*)\[([0-9]+)\]/gs)) { console.log(match) } 133 | if ( n.indexOf('[') === -1 ) { 134 | var _att_name = prefix +idx+ _expr_attribute(n) 135 | $this.pending.ExpressionAttributeNames [ _att_name ] = n; 136 | _att_names.push(_att_name) 137 | } else { 138 | var _arr_path = n.slice(n.indexOf('[')) 139 | n = n.slice(0,n.indexOf('[')) 140 | var _att_name = prefix +idx+ _expr_attribute(n) 141 | $this.pending.ExpressionAttributeNames [ _att_name ] = n; 142 | _att_names.push(_att_name + _arr_path ) 143 | } 144 | 145 | }) 146 | 147 | return _att_names.join('.'); 148 | } 149 | Transact.prototype._valuefy = function( name, prefix, value ) { 150 | var _att_name = prefix + name 151 | .split('-').join('_minus_') 152 | .split('.').join("_dot_") 153 | .split('[').join("_sqp1_") 154 | .split(']').join("_sqp1_") 155 | 156 | this.pending.ExpressionAttributeValues[ _att_name ] = util.stringify(value) 157 | return _att_name; 158 | } 159 | 160 | 161 | Transact.prototype.if = function( attr ) { 162 | this.pending.if = { 163 | attr: attr, 164 | }; 165 | return this; 166 | } 167 | Transact.prototype.not = function( ) { 168 | if (this.pending.if !== null) 169 | this.pending.if.not = true; 170 | 171 | return this; 172 | } 173 | Transact.prototype.exists = function( value ) { 174 | if (this.pending.if !== null) { 175 | 176 | if (this.pending.if.not === true) { 177 | var _name = this._namefy( this.pending.if.attr, '#ifnotexists_' ) 178 | this.pending.ConditionExpression.push( 179 | "attribute_not_exists( " + _name + " )" 180 | ) 181 | } else { 182 | var _name = this._namefy( this.pending.if.attr, '#ifexists_' ) 183 | this.pending.ConditionExpression.push( 184 | "attribute_exists( " + _name + " )" 185 | ) 186 | } 187 | this.pending.if = null; 188 | 189 | return this 190 | } 191 | return this; 192 | } 193 | Transact.prototype.ne = function( value ) { 194 | if (this.pending.if !== null) { 195 | 196 | var _name = this._namefy( this.pending.if.attr, '#ifne_' ) 197 | var _vname = this._valuefy( this.pending.if.attr, ':ifne_', value ) 198 | this.pending.ConditionExpression.push( 199 | "( " + _name + " <> " + _vname + " )" 200 | ) 201 | this.pending.if = null; 202 | return this 203 | } 204 | return this; 205 | } 206 | Transact.prototype.ge = function( value ) { 207 | if (this.pending.if !== null) { 208 | 209 | var _name = this._namefy( this.pending.if.attr, '#ifge_' ) 210 | var _vname = this._valuefy( this.pending.if.attr, ':ifge_', value ) 211 | this.pending.ConditionExpression.push( 212 | "( " + _name + " >= " + _vname + " )" 213 | ) 214 | this.pending.if = null; 215 | return this 216 | } 217 | return this; 218 | } 219 | Transact.prototype.gt = function( value ) { 220 | if (this.pending.if !== null) { 221 | 222 | var _name = this._namefy( this.pending.if.attr, '#ifgt_' ) 223 | var _vname = this._valuefy( this.pending.if.attr, ':ifgt_', value ) 224 | this.pending.ConditionExpression.push( 225 | "( " + _name + " > " + _vname + " )" 226 | ) 227 | this.pending.if = null; 228 | return this 229 | } 230 | return this; 231 | } 232 | Transact.prototype.le = function( value ) { 233 | if (this.pending.if !== null) { 234 | 235 | var _name = this._namefy( this.pending.if.attr, '#ifle_' ) 236 | var _vname = this._valuefy( this.pending.if.attr, ':ifle_', value ) 237 | this.pending.ConditionExpression.push( 238 | "( " + _name + " <= " + _vname + " )" 239 | ) 240 | this.pending.if = null; 241 | return this 242 | } 243 | return this; 244 | } 245 | Transact.prototype.lt = function( value ) { 246 | if (this.pending.if !== null) { 247 | 248 | var _name = this._namefy( this.pending.if.attr, '#iflt_' ) 249 | var _vname = this._valuefy( this.pending.if.attr, ':iflt_', value ) 250 | this.pending.ConditionExpression.push( 251 | "( " + _name + " < " + _vname + " )" 252 | ) 253 | this.pending.if = null; 254 | return this 255 | } 256 | return this; 257 | } 258 | Transact.prototype.between = function( v1,v2 ) { 259 | if (this.pending.if !== null) { 260 | 261 | if (this.pending.if.not === true) { 262 | var _name = this._namefy( this.pending.if.attr, '#ifnotbtw_' ) 263 | var _v1 = this._valuefy( this.pending.if.attr, ':ifnotbtwlo_', v1 ) 264 | var _v2 = this._valuefy( this.pending.if.attr, ':ifnotbtwhi_', v2 ) 265 | this.pending.ConditionExpression.push( 266 | "(NOT ( " + _name + " BETWEEN " + _v1 + " AND " + _v2 + " ))" 267 | ) 268 | } else { 269 | var _name = this._namefy( this.pending.if.attr, '#ifbtw_' ) 270 | var _v1 = this._valuefy( this.pending.if.attr, ':ifbtwlo_', v1 ) 271 | var _v2 = this._valuefy( this.pending.if.attr, ':ifbtwhi_', v2 ) 272 | this.pending.ConditionExpression.push( 273 | "( " + _name + " BETWEEN " + _v1 + " AND " + _v2 + " )" 274 | ) 275 | } 276 | this.pending.if = null; 277 | 278 | return this 279 | } 280 | return this; 281 | } 282 | Transact.prototype.in = function( value ) { 283 | 284 | var $this=this; 285 | 286 | if (this.pending.if !== null) { 287 | 288 | if (this.pending.if.not === true) { 289 | var _name = this._namefy( this.pending.if.attr, '#ifnotin_' ) 290 | 291 | var ins = [] 292 | value.map(function(v,idx) { 293 | var _vname = $this._valuefy( $this.pending.if.attr, ':ifnotin' + idx + '_', v ) 294 | ins.push( _vname ) 295 | }) 296 | 297 | this.pending.ConditionExpression.push( 298 | "(NOT ( " + _name + " IN (" + ins.join(' , ') + ") ))" 299 | ) 300 | } else { 301 | var _name = this._namefy( this.pending.if.attr, '#ifin_' ) 302 | 303 | var ins = [] 304 | value.map(function(v,idx) { 305 | var _vname = $this._valuefy( $this.pending.if.attr, ':ifin' + idx + '_', v ) 306 | ins.push( _vname ) 307 | }) 308 | 309 | this.pending.ConditionExpression.push( 310 | "( " + _name + " IN (" + ins.join(' , ') + ") )" 311 | ) 312 | } 313 | 314 | this.pending.if = null; 315 | 316 | return this 317 | } 318 | return this; 319 | } 320 | 321 | 322 | 323 | Transact.prototype.contains = function( value ) { 324 | if (this.pending.if !== null) { 325 | 326 | if (this.pending.if.not === true) { 327 | var _name = this._namefy( this.pending.if.attr, '#ifnotcontains_' ) 328 | var _vname = this._valuefy( this.pending.if.attr, ':ifnotcontains_', value ) 329 | this.pending.ConditionExpression.push( 330 | "( NOT contains( " + _name + " , " + _vname + ") )" 331 | ) 332 | } else { 333 | var _name = this._namefy( this.pending.if.attr, '#ifcontains_' ) 334 | var _vname = this._valuefy( this.pending.if.attr, ':ifcontains_', value ) 335 | this.pending.ConditionExpression.push( 336 | "contains( " + _name + " , " + _vname + " )" 337 | ) 338 | } 339 | this.pending.if = null; 340 | 341 | return this 342 | } 343 | return this; 344 | } 345 | 346 | Transact.prototype.begins_with = function( value ) { 347 | if (this.pending.if !== null) { 348 | 349 | if (this.pending.if.not === true) { 350 | var _name = this._namefy( this.pending.if.attr, '#ifnotbw_' ) 351 | var _vname = this._valuefy( this.pending.if.attr, ':ifnotbw_', value ) 352 | this.pending.ConditionExpression.push( 353 | "(NOT begins_with( " + _name + " , " + _vname + " ))" 354 | ) 355 | } else { 356 | var _name = this._namefy( this.pending.if.attr, '#ifbw_' ) 357 | var _vname = this._valuefy( this.pending.if.attr, ':ifbw_', value ) 358 | this.pending.ConditionExpression.push( 359 | "begins_with( " + _name + " , " + _vname + " )" 360 | ) 361 | } 362 | this.pending.if = null; 363 | 364 | return this 365 | } 366 | return this; 367 | } 368 | 369 | 370 | 371 | 372 | Transact.prototype.insert = function(item) { 373 | if (this.err) 374 | return this; 375 | 376 | var $this = this 377 | 378 | if (!this.describeTables.hasOwnProperty(this.pending.tableName)) { 379 | this.err = { errorMessage: "transact() needs to know table schema, please use .schema() to define it"} 380 | return this; 381 | } 382 | 383 | var describeTable = this.describeTables[this.pending.tableName] 384 | 385 | for (var i in describeTable.KeySchema ) { 386 | this.pending.ExpressionAttributeNames[ '#'+describeTable.KeySchema[i].AttributeName ] = describeTable.KeySchema[i].AttributeName 387 | this.pending.ConditionExpression.push( 388 | "attribute_not_exists( #" + describeTable.KeySchema[i].AttributeName + ")" 389 | ) 390 | } 391 | 392 | var $thisQuery = { 393 | Put: { 394 | TableName: this.pending.tableName, 395 | Item: util.anormalizeItem(item), 396 | ConditionExpression: this.pending.ConditionExpression.join(' AND '), 397 | ExpressionAttributeNames: this.pending.ExpressionAttributeNames, 398 | }, 399 | } 400 | 401 | //console.log("insert()", JSON.stringify($thisQuery, null, "\t")) 402 | 403 | this.TransactItems.push($thisQuery) 404 | this.reset() 405 | 406 | 407 | return this; 408 | } 409 | 410 | Transact.prototype.insert_or_replace = function( item ) { 411 | if (this.err) 412 | return this; 413 | 414 | var $this = this 415 | 416 | var $thisQuery = { 417 | Put: { 418 | TableName: this.pending.tableName, 419 | Item: util.anormalizeItem(item), 420 | // ReturnValuesOnConditionCheckFailure: ALL_OLD | NONE 421 | }, 422 | // ReturnValuesOnConditionCheckFailure: ALL_OLD | NONE 423 | } 424 | if (Object.keys(this.pending.ExpressionAttributeNames).length) 425 | $thisQuery.Put.ExpressionAttributeNames = this.pending.ExpressionAttributeNames; 426 | if (Object.keys(this.pending.ExpressionAttributeValues).length) 427 | $thisQuery.Put.ExpressionAttributeValues = this.pending.ExpressionAttributeValues; 428 | if (this.pending.ConditionExpression.length) 429 | $thisQuery.Put.ConditionExpression = this.pending.ConditionExpression.join(' AND '); 430 | 431 | this.TransactItems.push($thisQuery) 432 | //console.log("pending=", this.pending ) 433 | this.reset() 434 | //console.log("insert_or_replace", JSON.stringify($thisQuery, null ,"\t") ) 435 | return this; 436 | } 437 | 438 | Transact.prototype.replace = function( item ) { 439 | if (this.err) 440 | return this; 441 | 442 | var $this = this 443 | 444 | if (!this.describeTables.hasOwnProperty(this.pending.tableName)) { 445 | this.err = { errorMessage: "transact() needs to know table schema, please use .schema() to define it"} 446 | return this; 447 | } 448 | 449 | var describeTable = this.describeTables[this.pending.tableName] 450 | 451 | for (var i in describeTable.KeySchema ) { 452 | this.pending.ExpressionAttributeNames[ '#'+describeTable.KeySchema[i].AttributeName ] = describeTable.KeySchema[i].AttributeName 453 | this.pending.ExpressionAttributeValues[ ':'+describeTable.KeySchema[i].AttributeName ] = util.anormalizeItem(item)[[describeTable.KeySchema[i].AttributeName]] 454 | this.pending.ConditionExpression.push( 455 | "( #" + describeTable.KeySchema[i].AttributeName + " = :" + describeTable.KeySchema[i].AttributeName + ")" 456 | ) 457 | } 458 | 459 | var $thisQuery = { 460 | Put: { 461 | TableName: this.pending.tableName, 462 | Item: util.anormalizeItem(item), 463 | ConditionExpression: this.pending.ConditionExpression.join(' AND '), 464 | ExpressionAttributeNames: this.pending.ExpressionAttributeNames, 465 | ExpressionAttributeValues: this.pending.ExpressionAttributeValues, 466 | }, 467 | } 468 | 469 | //console.log("insert()", JSON.stringify($thisQuery, null, "\t")) 470 | 471 | this.TransactItems.push($thisQuery) 472 | this.reset() 473 | return this; 474 | } 475 | 476 | 477 | Transact.prototype.insert_or_update = function( item ) { 478 | if (this.err) 479 | return this; 480 | 481 | var $this = this 482 | 483 | if (!this.describeTables.hasOwnProperty(this.pending.tableName)) { 484 | this.err = { errorMessage: "transact() needs to know table schema, please use .schema() to define it"} 485 | return this; 486 | } 487 | 488 | var describeTable = this.describeTables[this.pending.tableName] 489 | 490 | var item_copy = util.clone( item ) 491 | 492 | var Key = {} 493 | var SET_UpdateExpression = [] 494 | var ADD_UpdateExpression = [] 495 | var REMOVE_UpdateExpression = [] 496 | 497 | 498 | for (var i in describeTable.KeySchema ) { 499 | Key[describeTable.KeySchema[i].AttributeName] = util.anormalizeItem(item)[[describeTable.KeySchema[i].AttributeName]]; 500 | delete item_copy[describeTable.KeySchema[i].AttributeName] 501 | } 502 | 503 | Object.keys(item_copy).map(function(k) { 504 | $this.pending.ExpressionAttributeNames[ '#'+ k ] = k 505 | 506 | if ( item[k] === undefined ) { 507 | REMOVE_UpdateExpression.push( 508 | "#" + k 509 | ) 510 | return; 511 | } 512 | 513 | $this.pending.ExpressionAttributeValues[ ':'+k ] = util.anormalizeItem(item)[k] 514 | SET_UpdateExpression.push( 515 | "#" + k + " = :" + k 516 | ) 517 | 518 | }) 519 | 520 | var $thisQuery = { 521 | Update: { 522 | TableName: this.pending.tableName, 523 | Key: Key, 524 | // Item: util.anormalizeItem(item), 525 | ExpressionAttributeNames: this.pending.ExpressionAttributeNames, 526 | // insert_or_update() can not have the "ExpressionAttributeValues can not be empty" error, theres always values for the key 527 | ExpressionAttributeValues: this.pending.ExpressionAttributeValues, 528 | }, 529 | } 530 | 531 | 532 | 533 | if ( SET_UpdateExpression.length || REMOVE_UpdateExpression.length ) { 534 | $thisQuery.Update.UpdateExpression = ''; 535 | 536 | if ( SET_UpdateExpression.length ) 537 | $thisQuery.Update.UpdateExpression += "SET " + SET_UpdateExpression.join(' , ') + ' '; 538 | 539 | if ( REMOVE_UpdateExpression.length ) 540 | $thisQuery.Update.UpdateExpression += "REMOVE " + REMOVE_UpdateExpression.join(' , ') + ' '; 541 | 542 | 543 | } 544 | 545 | if (this.pending.ConditionExpression.length) 546 | $thisQuery.Update.ConditionExpression = this.pending.ConditionExpression.join(' AND '); 547 | 548 | //console.log("insert_or_update()", JSON.stringify($thisQuery, null, "\t")) 549 | 550 | this.TransactItems.push($thisQuery) 551 | this.reset() 552 | return this; 553 | } 554 | 555 | 556 | Transact.prototype.update = function( item ) { 557 | if (this.err) 558 | return this; 559 | 560 | var $this = this 561 | 562 | var item_copy = util.clone( item ) 563 | 564 | var Key = {} 565 | var SET_UpdateExpression = [] 566 | var ADD_UpdateExpression = [] 567 | var REMOVE_UpdateExpression = [] 568 | 569 | Object.keys(this.pending.wheres || {}).map(function(k) { 570 | Key[k] = util.stringify($this.pending.wheres[k]) 571 | $this.pending.ExpressionAttributeNames[ '#'+k ] = k 572 | $this.pending.ConditionExpression.push( 573 | "attribute_exists( #" + k + ")" 574 | ) 575 | 576 | }) 577 | 578 | Object.keys(item_copy).map(function(k) { 579 | $this.pending.ExpressionAttributeNames[ '#update_'+ k ] = k 580 | 581 | if ( item[k] === undefined ) { 582 | REMOVE_UpdateExpression.push( 583 | "#update_" + k 584 | ) 585 | return; 586 | } 587 | 588 | 589 | $this.pending.ExpressionAttributeValues[ ':update_'+k ] = util.anormalizeItem(item)[k] 590 | SET_UpdateExpression.push( 591 | "#update_" + k + " = :update_" + k 592 | ) 593 | 594 | }) 595 | 596 | var $thisQuery = { 597 | Update: { 598 | TableName: this.pending.tableName, 599 | Key: Key, 600 | // Item: util.anormalizeItem(item), 601 | ConditionExpression: this.pending.ConditionExpression.join(' AND '), 602 | ExpressionAttributeNames: this.pending.ExpressionAttributeNames, 603 | }, 604 | } 605 | 606 | // ExpressionAttributeValues can not be empty 607 | if (Object.keys(this.pending.ExpressionAttributeValues).length) 608 | $thisQuery.Update.ExpressionAttributeValues = this.pending.ExpressionAttributeValues; 609 | 610 | if ( SET_UpdateExpression.length || REMOVE_UpdateExpression.length ) { 611 | $thisQuery.Update.UpdateExpression = ''; 612 | 613 | if ( SET_UpdateExpression.length ) 614 | $thisQuery.Update.UpdateExpression += "SET " + SET_UpdateExpression.join(' , ') + ' '; 615 | 616 | if ( REMOVE_UpdateExpression.length ) 617 | $thisQuery.Update.UpdateExpression += "REMOVE " + REMOVE_UpdateExpression.join(' , ') + ' '; 618 | 619 | 620 | } 621 | 622 | // console.log("update()", JSON.stringify($thisQuery, null, "\t")) 623 | 624 | this.TransactItems.push($thisQuery) 625 | this.reset() 626 | return this; 627 | } 628 | 629 | 630 | Transact.prototype.delete = function(callback ) { 631 | var $this = this 632 | 633 | var Key = {} 634 | Object.keys(this.pending.wheres || {}).map(function(k) { 635 | Key[k] = util.stringify($this.pending.wheres[k]) 636 | }) 637 | 638 | var $thisQuery = { 639 | Delete: { 640 | TableName: this.pending.tableName, 641 | Key: Key, 642 | }, 643 | } 644 | 645 | if (Object.keys(this.pending.ExpressionAttributeNames).length) 646 | $thisQuery.Delete.ExpressionAttributeNames = this.pending.ExpressionAttributeNames; 647 | if (Object.keys(this.pending.ExpressionAttributeValues).length) 648 | $thisQuery.Delete.ExpressionAttributeValues = this.pending.ExpressionAttributeValues; 649 | if (this.pending.ConditionExpression.length) 650 | $thisQuery.Delete.ConditionExpression = this.pending.ConditionExpression.join(' AND '); 651 | 652 | // console.log("update()", JSON.stringify($thisQuery, null, "\t")) 653 | 654 | this.TransactItems.push($thisQuery) 655 | this.reset() 656 | return this; 657 | } 658 | 659 | Transact.prototype.write = function( callback ) { 660 | var $this=this; 661 | if (this.err) { 662 | if (typeof callback !== "function") { 663 | return new Promise(function(fullfill, reject) { 664 | return reject(this.err) 665 | }) 666 | } 667 | return callback.apply( $this, [ this.err ]) 668 | } 669 | 670 | 671 | var $this = this 672 | 673 | var $thisQuery = { 674 | TransactItems: this.TransactItems, 675 | // ClientRequestToken: 'STRING_VALUE', 676 | ReturnConsumedCapacity: 'TOTAL', // INDEXES | TOTAL | NONE, 677 | ReturnItemCollectionMetrics: 'SIZE', // SIZE | NONE 678 | } 679 | 680 | //console.log(".transactWriteItems()", JSON.stringify($thisQuery, null ,"\t") ) 681 | 682 | 683 | if (typeof this.local_events['beforeRequest'] === "function" ) 684 | this.local_events['beforeRequest']('transactWriteItems', $thisQuery) 685 | 686 | if (typeof callback !== "function") { 687 | return new Promise(function(fullfill, reject) { 688 | $this.routeCall('transactWriteItems', $thisQuery , true, function(err,data) { 689 | if (err) 690 | return reject(err) 691 | 692 | fullfill(data.Attributes) 693 | }) 694 | }) 695 | } 696 | 697 | this.routeCall('transactWriteItems', $thisQuery , true , function(err,data) { 698 | 699 | if (err) 700 | return typeof callback !== "function" ? null : callback.apply( this, [ err, false ] ) 701 | 702 | typeof callback !== "function" ? null : callback.apply( this, [ err, data, data ]) 703 | }) 704 | } 705 | 706 | 707 | 708 | Transact.prototype.routeCall = function(method, params, reset ,callback ) { 709 | var $this = this 710 | this.events.beforeRequest.apply( this, [ method, params ]) 711 | 712 | if ( this.return_explain ) { 713 | if ( reset === true ) 714 | $this.reset() 715 | 716 | var explain; 717 | switch (method) { 718 | 719 | } 720 | 721 | 722 | callback.apply( $this, [ null, explain ] ) 723 | return 724 | } 725 | 726 | 727 | this.client[method]( params, function( err, data ) { 728 | 729 | if (err) 730 | $this.events.error.apply( $this, [ method, err , params ] ) 731 | 732 | if ((data || {}).hasOwnProperty('ConsumedCapacity') ) 733 | $this.ConsumedCapacity = data.ConsumedCapacity 734 | 735 | if ( reset === true ) 736 | $this.reset() 737 | 738 | callback.apply( $this, [ err, data ] ) 739 | }) 740 | } 741 | 742 | module.exports = Transact; 743 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.2", 3 | "author": { 4 | "name": "Adrian Praja", 5 | "email": "adrian@databank.ro", 6 | "url": "https://awspilot.dev" 7 | }, 8 | "name": "@awspilot/dynamodb", 9 | "description": "Speak fluent DynamoDB, write code with fashion, I Promise() 😃 ", 10 | "keywords": [ 11 | "aws", 12 | "dynamodb", 13 | "sql", 14 | "promise", 15 | "nosql" 16 | ], 17 | "homepage": "https://awspilot.dev/", 18 | "bugs": "https://github.com/awspilot/dynamodb-oop/issues", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/awspilot/dynamodb-oop" 22 | }, 23 | "main": "./lib/dynamodb", 24 | "browser": "./dist/dynamodbjs", 25 | "license": "MIT", 26 | "dependencies": { 27 | "@awspilot/dynamodb-util": "1.1.4", 28 | "@awspilot/dynamodb-sql-util": "1.0.1" 29 | }, 30 | "peerDependencies": { 31 | "aws-sdk": "^2.432.0" 32 | }, 33 | "devDependencies": { 34 | "assert": "2.0.0", 35 | "async": "^3.2.2", 36 | "jison": "^0.4.18", 37 | "js-yaml": "^3.13.1", 38 | "mocha": "^9.1.3", 39 | 40 | "babel-core": "^6.26.3", 41 | "babel-loader": "^7.1.5", 42 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 43 | "babel-preset-env": "^1.7.0", 44 | "babel-preset-es2015": "^6.24.1", 45 | 46 | "webpack": "^5.62.1", 47 | "webpack-cli": "^4.9.1", 48 | "ifdef-loader": "^2.1.4" 49 | 50 | }, 51 | "scripts": { 52 | "test": "mocha --timeout 300000 -S -R spec --exit", 53 | 54 | "build:browser": "webpack --config webpack.browser.js" 55 | }, 56 | "engines": { 57 | "node": "*" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/000-index.js: -------------------------------------------------------------------------------- 1 | require('./lib/common') 2 | 3 | require("./tests/000-createTable.js") 4 | run_test('[SQL] CREATE TABLE', 'test/res/000-create_table.yaml' ) 5 | require("./tests/010-describe.js") 6 | run_test('[SQL] DECRIBE TABLE', 'test/res/010-describe.yaml' ) 7 | 8 | require("./tests/020-specialSigns.js") 9 | 10 | require("./tests/030-insert.js") 11 | run_test('[SQL] INSERT', 'test/res/030-insert.yaml' ) 12 | 13 | require("./tests/031-insert_or_update.js") 14 | require("./tests/032-insert_or_replace.js") 15 | require("./tests/040-update.js") 16 | run_test('[SQL] UPDATE', 'test/res/040-update.yaml' ) 17 | require("./tests/050-replace.js") 18 | run_test('[SQL] REPLACE', 'test/res/050-replace.yaml' ) 19 | require("./tests/060-delete.js") 20 | run_test('[SQL] DELETE', 'test/res/060-delete.yaml' ) 21 | require("./tests/070-get.js") 22 | require("./tests/080-query.js") 23 | run_test('[SQL] QUERY', 'test/res/070-query.yaml' ) 24 | require("./tests/090-scan.js") 25 | run_test('[SQL] SCAN', 'test/res/090-scan.yaml' ) 26 | require("./tests/0A0-batch_write.js") 27 | require("./tests/0B0-batch_read.js") 28 | require("./tests/0C0-transact.js") 29 | run_test('[SQL] DROP INDEX', 'test/res/991-drop_index.yaml' ) 30 | require("./tests/999-deleteTable.js") 31 | // Cannot delete table while indexes are being created, updated, or deleted. 32 | run_test('[SQL] DROP TABLE', 'test/res/999-drop_table.yaml' ) 33 | -------------------------------------------------------------------------------- /test/credentials: -------------------------------------------------------------------------------- 1 | export AWS_ACCESS_KEY_ID= 2 | export AWS_SECRET_ACCESS_KEY= 3 | export AWS_REGION=us-east-1 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/lib/common.js: -------------------------------------------------------------------------------- 1 | fs = require('fs') 2 | async = require('async') 3 | assert = require('assert') 4 | yaml = require('js-yaml') 5 | $tableName = 'test_hash_range' 6 | 7 | 8 | /* try with dynamodb-local 9 | var dynalite = require('dynalite'), 10 | dynaliteServer = dynalite({ createTableMs: 50, db: require('memdown')}) 11 | dynaliteServer.listen(8000, function(err) { 12 | if (err) throw err 13 | }) 14 | */ 15 | 16 | var AWS = require('aws-sdk') 17 | //AWS.config.logger = console; 18 | AWS.config.maxRetries = 20; 19 | 20 | 21 | const DynamodbFactory = require('../../lib/dynamodb') 22 | //const DynamodbFactory = require('../../dist/dynamodb-node.js') 23 | //import { DynamodbFactory } from '../../dist/dynamodb-node.min.js' 24 | 25 | DynamodbFactory.config( { 26 | stringset_parse_as_set: false, 27 | numberset_parse_as_set: false, 28 | empty_string_replace_as: "" 29 | } ); 30 | 31 | 32 | 33 | // test against local version 34 | //DynamoDB = new DynamodbFactory( new AWS.DynamoDB({endpoint: 'http://localhost:8000', "accessKeyId": "myKeyId", "secretAccessKey": "secret", "region": "us-east-1" })) 35 | 36 | // test against online demo version 37 | // DynamoDB = new DynamodbFactory( new AWS.DynamoDB({endpoint: 'https://djaorxfotj9hr.cloudfront.net/v1/dynamodb', "accessKeyId": "myKeyId", "secretAccessKey": "secret", "region": "eu-central-1" })) 38 | // DynamoDB = new DynamodbFactory({ endpoint: 'https://djaorxfotj9hr.cloudfront.net/v1/dynamodb', accessKeyId: "myKeyId", secretAccessKey: "secret", region: "eu-central-1" }) 39 | 40 | // test against AWS DynamoDB 41 | DynamoDB = new DynamodbFactory({ 42 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 43 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 44 | region: process.env.AWS_REGION, 45 | }) 46 | 47 | 48 | 49 | 50 | 51 | 52 | DynamoDB.schema({ 53 | TableName: 'test_hash', 54 | KeySchema: [ 55 | { 56 | AttributeName: "hash", 57 | KeyType: "HASH" 58 | } 59 | ] 60 | }) 61 | DynamoDB.schema([{ 62 | TableName: 'test_hash_range', 63 | KeySchema: [ 64 | { 65 | AttributeName: "hash", 66 | KeyType: "HASH" 67 | }, 68 | { 69 | AttributeName: "range", 70 | KeyType: "RANGE" 71 | } 72 | ] 73 | }]) 74 | 75 | 76 | DynamoDB.on('error', function(op, error, payload ) { 77 | //console.log(op,error,JSON.stringify(payload)) 78 | }) 79 | DynamoDB.on('beforeRequest', function(op, payload ) { 80 | //console.log("--------------------------------") 81 | //console.log(op,payload) 82 | }) 83 | 84 | query_handler = function( idx, yml ) { 85 | return function(done) { 86 | if (yml.Tests.query[idx].log === true) 87 | global.DDBSQL = true 88 | else 89 | global.DDBSQL = false 90 | 91 | var ddb = DynamoDB 92 | if (yml.Tests.query[idx].explain === true) 93 | ddb = ddb.explain() 94 | 95 | 96 | var err; 97 | var data; 98 | var dataItem; 99 | 100 | 101 | 102 | async.waterfall([ 103 | 104 | 105 | // beforequery 106 | function( cb ) { 107 | if (!yml.Tests.query[idx].hasOwnProperty('beforeQuery')) 108 | return cb() 109 | 110 | ddb.query( yml.Tests.query[idx].beforeQuery, function( ) { 111 | cb() 112 | }) 113 | }, 114 | // before sleep 115 | function( cb ) { 116 | if (!yml.Tests.query[idx].hasOwnProperty('beforeSleep')) 117 | return cb() 118 | 119 | setTimeout(cb, yml.Tests.query[idx].beforeSleep ) 120 | }, 121 | 122 | 123 | // actual query 124 | function( cb ) { 125 | ddb.query( yml.Tests.query[idx].query, function(sqlerr, sqldata ) { 126 | err = sqlerr; 127 | data = sqldata; 128 | cb() 129 | }) 130 | }, 131 | 132 | // sleep 133 | function( cb ) { 134 | if (!yml.Tests.query[idx].hasOwnProperty('sleep')) 135 | return cb() 136 | 137 | setTimeout(cb, yml.Tests.query[idx].sleep ) 138 | }, 139 | 140 | 141 | function(cb) { 142 | if (! yml.Tests.query[idx].hasOwnProperty('dataItem')) 143 | return cb() 144 | 145 | var dt = DynamoDB.table(yml.Tests.query[idx].dataItem.table) 146 | Object.keys(yml.Tests.query[idx].dataItem.item).map(function(k) { 147 | dt.where(k).eq(yml.Tests.query[idx].dataItem.item[k]) 148 | }) 149 | dt.get(function(err, new_data ) { 150 | if (err) 151 | throw err 152 | 153 | dataItem = new_data 154 | cb() 155 | }) 156 | 157 | }, 158 | ], function() { 159 | 160 | 161 | 162 | 163 | if (yml.Tests.query[idx].shouldFail) { 164 | if (err) { 165 | if (!(yml.Tests.query[idx].validations || []).length) 166 | return done() 167 | 168 | yml.Tests.query[idx].validations.forEach(function(el) { 169 | var left = eval( el.key ); 170 | if ( el.sortKeyAsArray === true ) { 171 | left = left.sort(function(a,b) { 172 | return a > b ? 1 : -1; 173 | }) 174 | } 175 | assert.deepEqual( left , eval( el.value )) 176 | }) 177 | done() 178 | } 179 | } else { 180 | if (err) 181 | throw err 182 | 183 | if (yml.Tests.query[idx].log === true) { 184 | console.log("data=", JSON.stringify(data, null, "\t")) 185 | if (dataItem) console.log("dataItem=", JSON.stringify(dataItem, null, "\t")) 186 | } 187 | 188 | if (yml.Tests.query[idx].results) 189 | assert.equal(data.length, yml.Tests.query[idx].results) 190 | 191 | if (yml.Tests.query[idx].validations) { 192 | yml.Tests.query[idx].validations.forEach(function(el) { 193 | var left = eval( el.key ); 194 | if ( el.sortKeyAsArray === true ) { 195 | left = left.sort(function(a,b) { 196 | return a > b ? 1 : -1; 197 | }) 198 | } 199 | assert.deepEqual( left , eval( el.value )) 200 | }) 201 | } 202 | done() 203 | } 204 | }) 205 | 206 | 207 | 208 | 209 | } 210 | } 211 | 212 | before_test = function(data) { 213 | return function(done) { 214 | async.each(data, function(q, cb ) { 215 | DynamoDB.query(q, cb ) 216 | }, function(err) { 217 | if (err) 218 | throw err 219 | 220 | done() 221 | }) 222 | } 223 | } 224 | 225 | run_test = function(test_name, yml_file ) { 226 | 227 | describe(test_name, function () { 228 | var yml = yaml.safeLoad(fs.readFileSync(yml_file, 'utf8')) 229 | before(before_test(yml.Prepare.Data)) 230 | // beforeEach 231 | 232 | yml.Tests.query.forEach(function(v,k) { 233 | it(yml.Tests.query[k].title || yml.Tests.query[k].query, query_handler(k, yml ) ) 234 | 235 | }) 236 | }) 237 | } 238 | -------------------------------------------------------------------------------- /test/res/000-create_table.yaml: -------------------------------------------------------------------------------- 1 | Prepare: 2 | Data: [] 3 | Tests: 4 | query: 5 | - 6 | beforeQeuery: DROP TABLE ppr_table 7 | beforeSleep: 10000 8 | title: CREATE PAY_PER_REQUEST TABLE table 9 | query: 10 | | 11 | CREATE PAY_PER_REQUEST TABLE ppr_table ( 12 | pKey STRING, 13 | PRIMARY KEY( pKey ) 14 | ) 15 | sleep: 10000 16 | - 17 | query: DESCRIBE TABLE ppr_table 18 | log: false 19 | shouldFail: false 20 | validations: 21 | - key: data.BillingModeSummary.BillingMode 22 | value: '"PAY_PER_REQUEST"' 23 | - 24 | query: DROP TABLE ppr_table 25 | log: false 26 | shouldFail: false 27 | 28 | - 29 | beforeQuery: DROP TABLE provisioned_table 30 | beforeSleep: 10000 31 | title: CREATE PROVISIONED TABLE provisioned_table 32 | query: 33 | | 34 | CREATE PROVISIONED TABLE provisioned_table ( 35 | pKey STRING, 36 | PRIMARY KEY( pKey ) 37 | ) 38 | sleep: 10000 39 | - 40 | query: DESCRIBE TABLE provisioned_table 41 | log: false 42 | shouldFail: false 43 | validations: 44 | - key: data.BillingModeSummary.BillingMode 45 | value: '"PROVISIONED"' 46 | 47 | - 48 | query: DROP TABLE provisioned_table 49 | log: false 50 | shouldFail: false 51 | -------------------------------------------------------------------------------- /test/res/010-describe.yaml: -------------------------------------------------------------------------------- 1 | Prepare: 2 | Data: [] 3 | Tests: 4 | query: 5 | - 6 | query: DESCRIBE TABLE tbl_name 7 | log: false 8 | shouldFail: true 9 | validations: 10 | - key: err.code 11 | value: "'ResourceNotFoundException'" 12 | 13 | - 14 | query: DESCRIBE TABLE tablE 15 | log: false 16 | shouldFail: true 17 | validations: 18 | - key: err.code 19 | value: "'ResourceNotFoundException'" 20 | 21 | - 22 | query: DESCRIBE TABLE test_hash_range 23 | log: false 24 | # validate! 25 | 26 | - 27 | title: EXPLAIN DESCRIBE TABLE test_hash_range 28 | query: DESCRIBE TABLE test_hash_range 29 | explain: true 30 | log: false 31 | # validate! 32 | 33 | - 34 | query: SHOW TABLES 35 | log: false 36 | 37 | - title: EXPLAIN SHOW TABLES 38 | query: SHOW TABLES 39 | explain: true 40 | log: false 41 | # validate! 42 | -------------------------------------------------------------------------------- /test/res/030-insert.yaml: -------------------------------------------------------------------------------- 1 | Prepare: 2 | Data: [] 3 | Tests: 4 | query: 5 | - 6 | title: INSERT INTO tbl_name SET `hash` = , `range` = 7 | query: INSERT INTO test_hash_range SET `hash` = 'sql-test', `range` = 3 - 2 8 | log: false 9 | - 10 | title: INSERT INTO tbl_name SET ... existing item ( should fail )... 11 | query: INSERT INTO test_hash_range SET `hash` = 'sql-test', `range` = 1 12 | shouldFail: true 13 | 14 | - 15 | title: INSERT INTO tbl_name SET `hash` = , `range` = , bool = true 16 | query: INSERT INTO test_hash_range SET `hash` = 'sql-test-bool', `range` = 2/2 , bool = true 17 | dataItem: 18 | table: test_hash_range 19 | item: { hash: 'sql-test-bool', range: 1 } 20 | validations: 21 | - key: dataItem.bool 22 | value: true 23 | 24 | - 25 | title: INSERT INTO tbl_name SET `hash` = , `range` = , `null` = null, 26 | query: INSERT INTO test_hash_range SET `hash` = 'sql-test-null', `range` = 3 * 1 - 2, `null` = null 27 | dataItem: 28 | table: test_hash_range 29 | item: { hash: 'sql-test-null', range: 1 } 30 | validations: 31 | - key: dataItem.null 32 | value: null 33 | 34 | - 35 | title: INSERT INTO tbl_name SET `hash` = , `range` = , `list` = [ true , false, 1, 'two', null, {} ] 36 | query: INSERT INTO test_hash_range SET `hash` = 'sql-test-list', `range` = 1, `list` = [ true , false, 1, 'two', null, {} ] 37 | dataItem: 38 | table: test_hash_range 39 | item: { hash: 'sql-test-list', range: 1 } 40 | validations: 41 | - key: dataItem.list 42 | value: [ true , false, 1, 'two', null, {} ] 43 | 44 | - 45 | title: "INSERT INTO tbl_name SET `hash` = , `range` = , `map` = { bool: true , 'number': 1, 'string': 'two', \"null\": null, 'nested-list': [] }" 46 | query: "INSERT INTO test_hash_range SET `hash` = 'sql-test-map', `range` = 1, `map` = { bool: true , 'number': 1, 'string': 'two', \"null\": null, 'nested-list': [] }" 47 | dataItem: 48 | table: test_hash_range 49 | item: { hash: 'sql-test-map', range: 1 } 50 | validations: 51 | - key: dataItem.map 52 | value: { bool: true , number: 1, 'string': 'two', "null": null, 'nested-list': [] } 53 | 54 | 55 | 56 | - title: INSERT INTO tbl_name SET `hash` = , `range` = , ss = new StringSet(...), ns = new NumberSet(...) 57 | query: INSERT INTO `test_hash_range` SET `hash` = 'test-ss-ns', `range` = 1, ss = new StringSet(['asd','fgh','jkl']), ns = new NumberSet([99,66,33]) 58 | dataItem: 59 | table: test_hash_range 60 | item: { hash: 'test-ss-ns', range: 1 } 61 | validations: 62 | - key: dataItem.ss[2] 63 | value: "'jkl'" 64 | - key: "dataItem.ns.sort(function(a,b) { return a > b ? 1 : -1 })[2]" 65 | value: 99 66 | 67 | 68 | - title: INSERT INTO tbl_name VALUES ( ) 69 | query: 70 | | 71 | INSERT INTO 72 | `test_hash_range` 73 | VALUES ({ 74 | "hash": 'test-insert-sql', 75 | 'range': 1, 76 | 'number': 1, 77 | 'boolean': true, 78 | 'null': null, 79 | 'array': [1,2, 3 ], 80 | 'object': { 81 | "substring": "aaa", 82 | subnumber: 1, 83 | subobject: {}, 84 | subarray: [] 85 | }, 86 | }) 87 | dataItem: 88 | table: test_hash_range 89 | item: { hash: 'test-insert-sql', range: 1 } 90 | validations: 91 | - key: dataItem.number 92 | value: 1 93 | - key: dataItem.boolean 94 | value: true 95 | - key: dataItem.null 96 | value: null 97 | - key: dataItem.array 98 | value: [1,2, 3 ] 99 | - key: dataItem.object 100 | value: { substring: "aaa", subnumber: 1, subobject: {}, subarray: [] } 101 | 102 | - title: INSERT INTO tbl_name SET `hash` = , `range` = , d = new Date('2018-07-05T07:50:15.416Z').getTime() 103 | query: 104 | | 105 | INSERT INTO 106 | `test_hash_range` 107 | SET 108 | `hash` = 'test-insert-date', 109 | `range` = 1, 110 | d = new Date('2018-07-05T07:50:15.416Z').getTime(), 111 | m = Math.round( 7.4 + 2.7 ) 112 | dataItem: 113 | table: test_hash_range 114 | item: { hash: 'test-insert-date', range: 1 } 115 | validations: 116 | - key: dataItem.d 117 | value: 1530777015416 118 | - key: dataItem.m 119 | value: 10 120 | 121 | - 122 | title: "INSERT INTO tbl_name SET `hash` = , `range` = , uniqid = uuid('acct-###########')" 123 | query: "INSERT INTO test_hash_range SET `hash` = 'sql-test-uuid', `range` = 1, uniqid = uuid('acct-###########')" 124 | dataItem: 125 | table: test_hash_range 126 | item: { hash: 'sql-test-uuid', range: 1 } 127 | validations: 128 | - key: dataItem.uniqid.length 129 | value: 16 130 | - key: "dataItem.uniqid.substr(0,5)" 131 | value: "'acct-'" 132 | 133 | 134 | - title: INSERT INTO tbl_name SET `hash` = , `range` = , buffer = Buffer.from( ,'base64') 135 | query: 136 | | 137 | INSERT INTO 138 | test_hash_range 139 | SET 140 | hash = 'sql-test-insert-buffer', 141 | range = 1, 142 | buffer = Buffer.from( "4oyb77iPIGhvdXJnbGFzcy4g8J+VkCBjbG9jay4g4oyaIHdhdGNoLg==", 'base64' ) 143 | dataItem: 144 | table: test_hash_range 145 | item: { hash: 'sql-test-insert-buffer', range: 1 } 146 | validations: 147 | - key: "dataItem.buffer.toString('utf-8')" 148 | value: "'⌛️ hourglass. 🕐 clock. ⌚ watch.'" 149 | log: false 150 | 151 | # @todo: test new BinarySet 152 | # @todo: test ss,ns,bs,b nested in LIST 153 | # @todo: test ss,ns,bs,b nested in MAP 154 | # @todo: test multiline string in values 155 | # @todo: test multiline string json key names 156 | 157 | # - 158 | # title: INSERT INTO tbl_name SET `hash` = , `range` = , empty_string = '' 159 | # query: INSERT INTO test_hash_range SET `hash` = 'sql-test-empty-string', `range` = 1, empty_string = '' 160 | # dataItem: 161 | # table: test_hash_range 162 | # item: { hash: 'sql-test-empty-stringl', range: 1 } 163 | # validations: 164 | # - key: dataItem.bool 165 | # value: true 166 | # log: true 167 | -------------------------------------------------------------------------------- /test/res/033-batch_insert.yaml: -------------------------------------------------------------------------------- 1 | Prepare: 2 | Data: [] 3 | Tests: 4 | query: 5 | - 6 | title: INSERT INTO test_hash_range VALUES ( ), ( ) 7 | query: INSERT INTO test_hash_range VALUES ({'hash': 'sql-batchinsert', 'range': 1}), ({'hash': 'sql-batchinsert', 'range': 2}) 8 | 9 | 10 | - title: INSERT INTO tbl_name VALUES ( ), ( ) 11 | log: true 12 | query: 13 | | 14 | INSERT INTO 15 | `test_hash_range` 16 | VALUES 17 | ( { "hash": 'test-batchinsert-sql', 'range': 1 } ), 18 | ( { "hash": 'test-batchinsert-sql', 'range': 2 } ) 19 | validations: [] 20 | 21 | 22 | - title: INSERT INTO tbl_name VALUES ( ), ( ) 23 | log: false 24 | query: 25 | | 26 | INSERT INTO 27 | `table_hash_string_range_number` 28 | VALUES 29 | ( { "hash": 'test-batchinsert-sql', 'range': 1 } ), 30 | ( { "hash": 'test-batchinsert-sql', 'range': 2 } ), 31 | ( { "hash": 'test-batchinsert-sql', 'range': 3 } ), 32 | ( { "hash": 'test-batchinsert-sql', 'range': 4 } ), 33 | ( { "hash": 'test-batchinsert-sql', 'range': 5 } ), 34 | ( { "hash": 'test-batchinsert-sql', 'range': 6 } ), 35 | ( { "hash": 'test-batchinsert-sql', 'range': 7 } ), 36 | ( { "hash": 'test-batchinsert-sql', 'range': 8 } ), 37 | ( { "hash": 'test-batchinsert-sql', 'range': 9 } ), 38 | ( { "hash": 'test-batchinsert-sql', 'range': 10 } ), 39 | ( { "hash": 'test-batchinsert-sql', 'range': 11 } ), 40 | ( { "hash": 'test-batchinsert-sql', 'range': 12 } ), 41 | ( { "hash": 'test-batchinsert-sql', 'range': 13 } ), 42 | ( { "hash": 'test-batchinsert-sql', 'range': 14 } ), 43 | ( { "hash": 'test-batchinsert-sql', 'range': 15 } ), 44 | ( { "hash": 'test-batchinsert-sql', 'range': 16 } ), 45 | ( { "hash": 'test-batchinsert-sql', 'range': 17 } ), 46 | ( { "hash": 'test-batchinsert-sql', 'range': 18 } ), 47 | ( { "hash": 'test-batchinsert-sql', 'range': 19 } ), 48 | ( { "hash": 'test-batchinsert-sql', 'range': 20 } ), 49 | ( { "hash": 'test-batchinsert-sql', 'range': 21 } ), 50 | ( { "hash": 'test-batchinsert-sql', 'range': 22 } ), 51 | ( { "hash": 'test-batchinsert-sql', 'range': 23 } ), 52 | ( { "hash": 'test-batchinsert-sql', 'range': 24 } ), 53 | ( { "hash": 'test-batchinsert-sql', 'range': 25 } ), 54 | ( { "hash": 'test-batchinsert-sql', 'range': 26 } ) 55 | 56 | validations: [] 57 | shouldFail: true 58 | 59 | # @todo test insert buffer, it works its just not covered in the tests 60 | # @todo: test new BinarySet 61 | # @todo: test ss,ns,bs,b nested in LIST 62 | # @todo: test ss,ns,bs,b nested in MAP 63 | # @todo: test multiline string in values 64 | # @todo: test multiline string json key names 65 | -------------------------------------------------------------------------------- /test/res/040-update.yaml: -------------------------------------------------------------------------------- 1 | Prepare: 2 | Data: [] 3 | Tests: 4 | query: 5 | # test update inexistent 6 | - title: UPDATE inexistent item should throw 'ConditionalCheckFailedException' 7 | query: UPDATE `test_hash_range` SET x = 1234 WHERE `hash` = 'inexistent' and `range` = 1 8 | shouldFail: true 9 | validations: 10 | - key: err.code 11 | value: "'ConditionalCheckFailedException'" 12 | 13 | 14 | 15 | 16 | - 17 | title: "" 18 | query: INSERT INTO test_hash_range SET `hash` = 'sql-test-update', `range` = 1 19 | 20 | # test update string, number , boolean, array, object, null 21 | - title: "UPDATE tbl_name SET `string` = , `number` = , `boolean` = , `null` = null, `array` = , `object` = " 22 | query: 23 | | 24 | UPDATE 25 | `test_hash_range` 26 | SET 27 | `string` = 'text', 28 | `number` = 1, 29 | `boolean` = true, 30 | `null` = null, 31 | `array` = ['a',1,true,null], 32 | `object` = { 33 | 'string': 'text', 34 | 'number': 1, 35 | 'bool' : true, 36 | 'null' : null, 37 | } 38 | 39 | WHERE 40 | `hash` = 'sql-test-update' AND 41 | `range` = 1 42 | dataItem: 43 | table: test_hash_range 44 | item: { hash: 'sql-test-update', range: 1 } 45 | validations: 46 | - key: dataItem.string 47 | value: "'text'" 48 | - key: dataItem.number 49 | value: 1 50 | - key: dataItem.boolean 51 | value: true 52 | - key: dataItem.null 53 | value: "null" 54 | - key: dataItem.array 55 | value: ['a',1,true,null] 56 | - key: dataItem.object 57 | value: { string: 'text', number: 1, bool : true, null : null, } 58 | 59 | 60 | # test increment number 61 | - query: "UPDATE test_hash_range SET `number` += 10 WHERE `hash` = 'sql-test-update' AND `range` = 1 " 62 | dataItem: 63 | table: test_hash_range 64 | item: { hash: 'sql-test-update', range: 1 } 65 | validations: 66 | - key: dataItem.number 67 | value: 11 68 | 69 | - query: "UPDATE test_hash_range SET `number` += -10 WHERE `hash` = 'sql-test-update' AND `range` = 1 " 70 | dataItem: 71 | table: test_hash_range 72 | item: { hash: 'sql-test-update', range: 1 } 73 | validations: 74 | - key: dataItem.number 75 | value: 1 76 | 77 | # test delete item 78 | - query: "UPDATE test_hash_range SET `number` = undefined WHERE `hash` = 'sql-test-update' AND `range` = 1 " 79 | dataItem: 80 | table: test_hash_range 81 | item: { hash: 'sql-test-update', range: 1 } 82 | validations: 83 | - key: Object.keys(dataItem).length 84 | value: 7 85 | 86 | # test stringset, numberset 87 | - query: "UPDATE test_hash_range SET ss = new StringSet(['a','b','c']), ns = new NumberSet([1,2,3]) WHERE `hash` = 'sql-test-update' AND `range` = 1 " 88 | dataItem: 89 | table: test_hash_range 90 | item: { hash: 'sql-test-update', range: 1 } 91 | 92 | validations: 93 | - key: dataItem.ss 94 | value: ['a','b','c'] 95 | - key: dataItem.ns 96 | sortKeyAsArray: true 97 | value: [1,2,3] 98 | 99 | # test date 100 | - title: "UPDATE test_hash_range SET d = new Date('2018-07-05T07:50:15.416Z').getTime() WHERE `hash` = AND `range` = " 101 | query: 102 | | 103 | UPDATE 104 | test_hash_range 105 | SET 106 | d = new Date('2018-07-05T07:50:15.416Z').getTime(), 107 | m = Math.round( 7.4 + 2.7 ) 108 | WHERE 109 | `hash` = 'sql-test-update' AND 110 | `range` = Math.round( 3.4 - 2 ) 111 | dataItem: 112 | table: test_hash_range 113 | item: { hash: 'sql-test-update', range: 1 } 114 | validations: 115 | - key: dataItem.d 116 | value: 1530777015416 117 | - key: dataItem.m 118 | value: 10 119 | log: false 120 | 121 | # test uuid() 122 | - title: "UPDATE test_hash_range SET uniqid = uuid('acct-###########') WHERE `hash` = AND `range` = " 123 | query: 124 | | 125 | UPDATE 126 | test_hash_range 127 | SET 128 | uniqid = uuid('acct-###########') 129 | WHERE 130 | `hash` = 'sql-test-update' AND 131 | `range` = 3 - 2 132 | dataItem: 133 | table: test_hash_range 134 | item: { hash: 'sql-test-update', range: 1 } 135 | validations: 136 | - key: dataItem.uniqid.length 137 | value: 16 138 | - key: "dataItem.uniqid.substr(0,5)" 139 | value: "'acct-'" 140 | log: false 141 | 142 | # test buffer 143 | - title: "UPDATE test_hash_range SET uniqid = uuid('acct-###########') WHERE `hash` = AND `range` = " 144 | query: 145 | | 146 | UPDATE 147 | test_hash_range 148 | SET 149 | buffer = Buffer.from( "4oyb77iPIGhvdXJnbGFzcy4g8J+VkCBjbG9jay4g4oyaIHdhdGNoLg==", 'base64' ) 150 | WHERE 151 | `hash` = 'sql-test-update' AND 152 | `range` = 3 - 2 153 | dataItem: 154 | table: test_hash_range 155 | item: { hash: 'sql-test-update', range: 1 } 156 | validations: 157 | - key: "dataItem.buffer.toString('utf-8')" 158 | value: "'⌛️ hourglass. 🕐 clock. ⌚ watch.'" 159 | log: false 160 | 161 | # @todo: test new BinarySet 162 | # @todo: test ss,ns,bs,b nested in LIST 163 | # @todo: test ss,ns,bs,b nested in MAP 164 | # @todo: test multiline string in values 165 | # @todo: test multiline string json key names 166 | 167 | 168 | # test add to stringset 169 | 170 | # test remove from stringset 171 | 172 | # test add to numberset 173 | 174 | # test remove from numberset 175 | -------------------------------------------------------------------------------- /test/res/050-replace.yaml: -------------------------------------------------------------------------------- 1 | Prepare: 2 | Data: [] 3 | Tests: 4 | query: 5 | - title: REPLACE INTO tbl_name SET `hash` = ...inexistent hash... 6 | query: 7 | | 8 | REPLACE INTO 9 | `test_hash_range` 10 | SET 11 | `hash` = 'replace-inexistent-hash', 12 | `range` = 1, 13 | 14 | `string` = 'text', 15 | `number` = 1, 16 | `boolean` = true, 17 | `array` = ['a',1,true,null], 18 | `object` = { 19 | 'string': 'text', 20 | 'number': 1, 21 | 'bool' : true, 22 | 'null' : null, 23 | }, 24 | `null` = null 25 | dataItem: 26 | table: test_hash_range 27 | item: { hash: 'replace-inexistent-hash', range: 1 } 28 | validations: 29 | - key: dataItem.string 30 | value: "'text'" 31 | - key: dataItem.number 32 | value: 1 33 | - key: dataItem.boolean 34 | value: true 35 | - key: dataItem.null 36 | value: "null" 37 | - key: dataItem.array 38 | value: ['a',1,true,null] 39 | - key: dataItem.object 40 | value: { string: 'text', number: 1, bool : true, null : null, } 41 | 42 | # test stringset, numberset 43 | - title: "REPLACE INTO tbl_name SET `hash` = , ss = new StringSet(..), ns = new NumberSet(...) " 44 | query: 45 | | 46 | REPLACE INTO 47 | test_hash_range 48 | SET 49 | `hash` = 'replace-inexistent-hash', 50 | `range` = 1, 51 | ss = new StringSet(['a','b','c']), 52 | ns = new NumberSet([1,2,3]) 53 | 54 | dataItem: 55 | table: test_hash_range 56 | item: { hash: 'replace-inexistent-hash', range: 1 } 57 | validations: 58 | - key: Object.keys(dataItem).length 59 | value: 4 60 | 61 | - key: dataItem.ss 62 | value: ['a','b','c'] 63 | - key: dataItem.ns 64 | sortKeyAsArray: true 65 | value: [1,2,3] 66 | log: false 67 | 68 | 69 | 70 | 71 | 72 | - title: "REPLACE INTO tbl_name SET `hash` = , d = new Date('2018-07-05T07:50:15.416Z').getTime() " 73 | query: 74 | | 75 | REPLACE INTO 76 | test_hash_range 77 | SET 78 | `hash` = 'replace-inexistent-hash', 79 | `range` = 1, 80 | d = new Date('2018-07-05T07:50:15.416Z').getTime(), 81 | m = Math.round( 7.4 + 2.7 ) 82 | 83 | dataItem: 84 | table: test_hash_range 85 | item: { hash: 'replace-inexistent-hash', range: 1 } 86 | validations: 87 | - key: Object.keys(dataItem).length 88 | value: 4 89 | - key: dataItem.d 90 | value: 1530777015416 91 | - key: dataItem.m 92 | value: 10 93 | log: false 94 | 95 | 96 | - title: "REPLACE INTO tbl_name SET `hash` = , uniqid = uuid('acct-###########') " 97 | query: 98 | | 99 | REPLACE INTO 100 | test_hash_range 101 | SET 102 | `hash` = 'replace-inexistent-hash', 103 | `range` = 3 - 2, 104 | uniqid = uuid('acct-###########') 105 | 106 | 107 | dataItem: 108 | table: test_hash_range 109 | item: { hash: 'replace-inexistent-hash', range: 1 } 110 | validations: 111 | - key: dataItem.uniqid.length 112 | value: 16 113 | - key: "dataItem.uniqid.substr(0,5)" 114 | value: "'acct-'" 115 | log: false 116 | 117 | 118 | 119 | - title: REPLACE INTO tbl_name SET buffer = Buffer.from( ,'base64') 120 | query: 121 | | 122 | REPLACE INTO 123 | `test_hash_range` 124 | SET 125 | hash = 'replace-buffer', 126 | range = 1, 127 | buffer = Buffer.from( "4oyb77iPIGhvdXJnbGFzcy4g8J+VkCBjbG9jay4g4oyaIHdhdGNoLg==", 'base64' ) 128 | dataItem: 129 | table: test_hash_range 130 | item: { hash: 'replace-buffer', range: 1 } 131 | validations: 132 | - key: "dataItem.buffer.toString('utf-8')" 133 | value: "'⌛️ hourglass. 🕐 clock. ⌚ watch.'" 134 | log: false 135 | 136 | # @todo: test new BinarySet 137 | # @todo: test ss,ns,bs,b nested in LIST 138 | # @todo: test ss,ns,bs,b nested in MAP 139 | # @todo: test multiline string in values 140 | # @todo: test multiline string json key names 141 | -------------------------------------------------------------------------------- /test/res/060-delete.yaml: -------------------------------------------------------------------------------- 1 | Prepare: 2 | Data: 3 | - INSERT INTO test_hash_range SET `hash` = 'sql-test-delete', `range` = 3 - 2 4 | Tests: 5 | query: 6 | - title: delete from `test_hash_range` where `hash` = 'sql-test-delete' and `range` = 1 7 | query: 8 | | 9 | DELETE FROM 10 | `test_hash_range` 11 | WHERE 12 | `hash` = 'sql-test-delete' AND 13 | `range` = 3 - 2 14 | dataItem: 15 | table: test_hash_range 16 | item: { hash: 'sql-test-delete', range: 1 } 17 | validations: 18 | - key: Object.keys(dataItem).length 19 | value: 0 20 | log: false 21 | 22 | - title: DELETE FROM table WHERE `hash` = 'sql-test-delete' and `range` = 1 23 | query: 24 | | 25 | DELETE FROM 26 | test_hash_range 27 | WHERE 28 | `hash` = 'sql-test-delete' AND 29 | `range` = 3 - 2 30 | dataItem: 31 | table: test_hash_range 32 | item: { hash: 'sql-test-delete', range: 1 } 33 | validations: 34 | - key: Object.keys(dataItem).length 35 | value: 0 36 | log: false 37 | -------------------------------------------------------------------------------- /test/res/070-query.yaml: -------------------------------------------------------------------------------- 1 | Prepare: 2 | Data: 3 | - INSERT INTO test_hash_range SET `hash` = 'test-sql-query', `range` = 1, `number` = 1, `array` = ['string'], gsi_range = 'findme' 4 | - INSERT INTO test_hash_range SET `hash` = 'test-sql-query', `range` = 2, `number` = 2, `boolean` = true, `array` = [ 777 ], gsi_range = 'aaa' 5 | - INSERT INTO test_hash_range SET `hash` = 'test-sql-query', `range` = 99, `number` = 99, `boolean` = false, `array` = [ true ], rand = 77 6 | #- INSERT INTO test_hash_range SET `hash` = 'test-sql-query', `range` = 'aaa' 7 | # - INSERT INTO test_hash_range SET `hash` = 'test-sql-query', `range` = 'aaz' 8 | # - INSERT INTO test_hash_range SET `hash` = 'test-sql-query', `range` = 'bbb' 9 | # - INSERT INTO test_hash_range SET `hash` = 5, `range` = 5 10 | Tests: 11 | query: 12 | - query: SELECT * FROM test_hash_range 13 | shouldFail: true 14 | 15 | - query: SELECT * FROM test_hash_range WHERE `hash` = "test-sql-query" 16 | results: 3 17 | 18 | # - query: SELECT * FROM test_hash_range WHERE `hash` = 5 19 | # results: 1 20 | # 21 | - query: SELECT * FROM test_hash_range WHERE `hash` = 'test-sql-query' and `range` > 1 22 | results: 2 23 | 24 | - query: SELECT * FROM test_hash_range WHERE `hash` = 'test-sql-query' and `range` = 99 25 | results: 1 26 | 27 | - query: SELECT * FROM test_hash_range WHERE `hash` = 'test-sql-query' and `range` BETWEEN 2 AND 4 28 | results: 1 29 | 30 | - query: SELECT * FROM test_hash_range USE INDEX gsi_index WHERE `hash` = 'test-sql-query' 31 | results: 2 32 | 33 | 34 | - query: SELECT * FROM test_hash_range USE INDEX gsi_index WHERE `hash` = 'test-sql-query' AND gsi_range LIKE 'find%' 35 | results: 1 36 | 37 | 38 | - query: SELECT * FROM test_hash_range USE INDEX gsi_index WHERE `hash` = 'test-sql-query' AND gsi_range BETWEEN 'aab' AND 'z' 39 | results: 1 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | - query: SELECT * FROM test_hash_range WHERE `hash` = 'test-sql-query' LIMIT 2 49 | results: 2 50 | 51 | 52 | 53 | - query: SELECT * FROM test_hash_range WHERE `hash` = 'test-sql-query' DESC LIMIT 1 54 | results: 1 55 | validations: 56 | - key: data[0].range 57 | value: 99 58 | 59 | 60 | 61 | 62 | - query: SELECT rand FROM test_hash_range WHERE `hash` = 'test-sql-query' DESC LIMIT 1 63 | results: 1 64 | validations: 65 | - key: data[0].rand 66 | value: 77 67 | - key: Object.keys(data).length 68 | value: 1 69 | 70 | - query: SELECT `range`, inexistent_field FROM test_hash_range WHERE `hash` = 'test-sql-query' DESC LIMIT 1 71 | results: 1 72 | validations: 73 | - key: data[0].range 74 | value: 99 75 | - key: Object.keys(data[0]).length 76 | value: 1 77 | 78 | - query: SELECT `hash`, `range` FROM test_hash_range WHERE `hash` = 'test-sql-query' DESC LIMIT 1 79 | results: 1 80 | validations: 81 | - key: data[0].range 82 | value: 99 83 | - key: Object.keys(data[0]).length 84 | value: 2 85 | 86 | - query: SELECT `hash`, * FROM test_hash_range WHERE `hash` = 'test-sql-query' 87 | results: 3 88 | shouldFail: false 89 | 90 | 91 | # - query: SELECT `hash` AS h FROM test_hash_range WHERE `hash` = 'test-sql-query' 92 | # shouldFail: false 93 | # log: true 94 | 95 | - query: SELECT * FROM test_hash_range WHERE `hash` = "test-sql-query" CONSISTENT_READ 96 | results: 3 97 | 98 | 99 | # - query: SELECT CONSISTENT_READ * FROM test_hash_range WHERE `hash` = "test-sql-query" 100 | # results: 3 101 | # log: true 102 | 103 | - query: SELECT * FROM test_hash_range WHERE `hash` = 'test-sql-query' AND `range` > 3 - 2 104 | results: 2 105 | 106 | # HAVING not supported yet 107 | 108 | 109 | # - query: SELECT * FROM test_hash_range WHERE `hash` = "test-sql-query" HAVING gsi_string = "findme" 110 | # results: 1 111 | # validations: 112 | # - key: data[0].range 113 | # value: 1 114 | # 115 | # - query: SELECT * FROM test_hash_range WHERE `hash` = "test-sql-query" HAVING `number` BETWEEN 50 AND 150 116 | # results: 1 117 | # validations: 118 | # - key: data[0].range 119 | # value: 99 120 | # 121 | # - query: SELECT * FROM test_hash_range WHERE `hash` = "test-sql-query" HAVING gsi_string LIKE 'find%' 122 | # results: 1 123 | # validations: 124 | # - key: data[0].range 125 | # value: 1 126 | # 127 | # - query: SELECT * FROM test_hash_range WHERE `hash` = "test-sql-query" HAVING `boolean` = true 128 | # results: 1 129 | # validations: 130 | # - key: data[0].range 131 | # value: 2 132 | # 133 | # - query: SELECT * FROM test_hash_range WHERE `hash` = "test-sql-query" HAVING `boolean` = false 134 | # results: 1 135 | # validations: 136 | # - key: data[0].range 137 | # value: 99 138 | # #log: true 139 | # 140 | # - query: SELECT * FROM test_hash_range WHERE `hash` = "test-sql-query" HAVING gsi_string CONTAINS 'indm' 141 | # results: 1 142 | # validations: 143 | # - key: data[0].range 144 | # value: 1 145 | 146 | # - 147 | # query: SELECT * FROM test_hash_range WHERE `hash` = "test-sql-query" HAVING array CONTAINS 'string' 148 | # log: true 149 | # 150 | # - 151 | # query: SELECT * FROM test_hash_range WHERE `hash` = "test-sql-query" HAVING array CONTAINS 777 152 | # log: true 153 | # 154 | # - 155 | # query: SELECT * FROM test_hash_range WHERE `hash` = "test-sql-query" HAVING array CONTAINS true 156 | # log: true 157 | 158 | # HAVING xpatn OP | eg obj, obj.ccc array[1], 159 | # HAVING string between A and Z 160 | # HAVING field IN [ a,b,c] 161 | # HAVING field NOT NULL 162 | # HAVING field NULL 163 | # HAVING string NOT CONTAINS 'text' 164 | # HAVING stringset CONTAINS 'text' 165 | # HAVING stringset NOT CONTAINS 'text' 166 | -------------------------------------------------------------------------------- /test/res/090-scan.yaml: -------------------------------------------------------------------------------- 1 | Prepare: 2 | Data: [] 3 | Tests: 4 | query: 5 | - query: SCAN * FROM test_hash_range 6 | results: 9 7 | 8 | - query: SCAN * FROM test_hash_range CONSISTENT_READ 9 | results: 9 10 | 11 | - query: SCAN * FROM test_hash_range LIMIT 2 12 | results: 2 13 | 14 | - query: SCAN `hash`,`range` FROM test_hash_range 15 | results: 9 16 | validations: 17 | - key: data[0].range 18 | value: 1 19 | 20 | - query: SCAN * FROM test_hash_range USE INDEX gsi_index 21 | results: 4 22 | 23 | 24 | 25 | - title: EXPLAIN SCAN * FROM test_hash_range 26 | query: SCAN * FROM test_hash_range 27 | explain: true 28 | log: false 29 | -------------------------------------------------------------------------------- /test/res/991-drop_index.yaml: -------------------------------------------------------------------------------- 1 | Prepare: 2 | Data: [] 3 | Tests: 4 | query: 5 | - 6 | query: DROP INDEX gsi_index ON test_hash_range 7 | #explain: true 8 | log: false 9 | 10 | 11 | # @todo: describe table, check if index was deleted 12 | -------------------------------------------------------------------------------- /test/res/999-drop_table.yaml: -------------------------------------------------------------------------------- 1 | Prepare: 2 | Data: [] 3 | Tests: 4 | query: 5 | - 6 | beforeSleep: 10000 7 | beforeQuery: CREATE PAY_PER_REQUEST TABLE ppr_table ( pKey STRING, PRIMARY KEY( pKey ) ) 8 | query: DROP TABLE ppr_table 9 | #explain: true 10 | log: false 11 | shouldFail: false 12 | -------------------------------------------------------------------------------- /test/tests/000-createTable.js: -------------------------------------------------------------------------------- 1 | 2 | describe('client.createTable()', function () { 3 | it('deleting test table if exists', function(done) { 4 | 5 | DynamoDB 6 | .client 7 | .describeTable({ 8 | TableName: $tableName 9 | }, function(err, data) { 10 | if (err) { 11 | if (err.code === 'ResourceNotFoundException') 12 | done() 13 | else 14 | throw err 15 | } else { 16 | DynamoDB 17 | .client 18 | .deleteTable({ 19 | TableName: $tableName 20 | }, function(err, data) { 21 | if (err) 22 | throw 'delete failed' 23 | else 24 | done() 25 | }) 26 | } 27 | }) 28 | }); 29 | it('waiting for table to delete (within 25 seconds)', function(done) { 30 | var $existInterval = setInterval(function() { 31 | DynamoDB 32 | .client 33 | .describeTable({ 34 | TableName: $tableName 35 | }, function(err, data) { 36 | 37 | if (err && err.code === 'ResourceNotFoundException') { 38 | clearInterval($existInterval) 39 | return done() 40 | } 41 | if (err) { 42 | clearInterval($existInterval) 43 | throw err 44 | } 45 | 46 | if (data.TableStatus === 'DELETING') 47 | process.stdout.write('.') 48 | }) 49 | }, 1000) 50 | }) 51 | it('creating the table', function(done) { 52 | DynamoDB 53 | .client 54 | .createTable({ 55 | TableName: $tableName, 56 | BillingMode: 'PAY_PER_REQUEST', 57 | // ProvisionedThroughput: { 58 | // ReadCapacityUnits: 5, 59 | // WriteCapacityUnits: 5 60 | // }, 61 | KeySchema: [ 62 | { 63 | AttributeName: "hash", 64 | KeyType: "HASH" 65 | }, 66 | { 67 | AttributeName: "range", 68 | KeyType: "RANGE" 69 | } 70 | ], 71 | AttributeDefinitions: [ 72 | { 73 | AttributeName: "hash", 74 | AttributeType: "S" 75 | }, 76 | { 77 | AttributeName: "range", 78 | AttributeType: "N" 79 | }, 80 | { 81 | AttributeName: "gsi_range", 82 | AttributeType: "S" 83 | }, 84 | { 85 | AttributeName: "account-id", 86 | AttributeType: "S" 87 | }, 88 | { 89 | AttributeName: "account.id", 90 | AttributeType: "S" 91 | }, 92 | ], 93 | "GlobalSecondaryIndexes": [ 94 | { 95 | IndexName: "gsi_index", 96 | KeySchema: [ 97 | { 98 | AttributeName: "hash", 99 | KeyType: "HASH" 100 | }, 101 | { 102 | AttributeName: "gsi_range", 103 | KeyType: "RANGE" 104 | } 105 | ], 106 | 107 | Projection: { 108 | // "NonKeyAttributes": [ 109 | // "string" 110 | // ], 111 | ProjectionType: "ALL" 112 | }, 113 | // ProvisionedThroughput: { 114 | // ReadCapacityUnits: 5, 115 | // WriteCapacityUnits: 5 116 | // } 117 | }, 118 | { 119 | IndexName: "byAccount-Id", 120 | KeySchema: [ 121 | { 122 | AttributeName: "account-id", 123 | KeyType: "HASH" 124 | } 125 | ], 126 | 127 | Projection: { 128 | ProjectionType: "ALL" 129 | }, 130 | // ProvisionedThroughput: { 131 | // ReadCapacityUnits: 5, 132 | // WriteCapacityUnits: 5 133 | // } 134 | }, 135 | { 136 | IndexName: "byAccount.Id", 137 | KeySchema: [ 138 | { 139 | AttributeName: "account.id", 140 | KeyType: "HASH" 141 | } 142 | ], 143 | 144 | Projection: { 145 | ProjectionType: "ALL" 146 | }, 147 | // ProvisionedThroughput: { 148 | // ReadCapacityUnits: 5, 149 | // WriteCapacityUnits: 5 150 | // } 151 | } 152 | ], 153 | 154 | /* 155 | "LocalSecondaryIndexes": [ 156 | { 157 | "IndexName": "string", 158 | "KeySchema": [ 159 | { 160 | "AttributeName": "string", 161 | "KeyType": "string" 162 | } 163 | ], 164 | "Projection": { 165 | "NonKeyAttributes": [ 166 | "string" 167 | ], 168 | "ProjectionType": "string" 169 | } 170 | } 171 | ], 172 | */ 173 | 174 | 175 | }, function(err, data) { 176 | if (err) { 177 | throw err 178 | } else { 179 | if (data.TableDescription.TableStatus === 'CREATING' || data.TableDescription.TableStatus === 'ACTIVE' ) 180 | done() 181 | else 182 | throw 'unknown table status after create: ' + data.TableDescription.TableStatus 183 | } 184 | }) 185 | }) 186 | it('waiting for table to become ACTIVE', function(done) { 187 | var $existInterval = setInterval(function() { 188 | DynamoDB 189 | .client 190 | .describeTable({ 191 | TableName: $tableName 192 | }, function(err, data) { 193 | if (err) { 194 | throw err 195 | } else { 196 | //process.stdout.write("."); 197 | //console.log(data.Table) 198 | if (data.Table.TableStatus === 'ACTIVE') { 199 | clearInterval($existInterval) 200 | done() 201 | } 202 | } 203 | }) 204 | }, 3000) 205 | }) 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | it('deleting `table` table if exists', function(done) { 236 | DynamoDB 237 | .client 238 | .describeTable({ 239 | TableName: 'table' 240 | }, function(err, data) { 241 | if (err) { 242 | if (err.code === 'ResourceNotFoundException') 243 | done() 244 | else 245 | throw 'could not describe table' 246 | } else { 247 | DynamoDB 248 | .client 249 | .deleteTable({ 250 | TableName: 'table' 251 | }, function(err, data) { 252 | if (err) 253 | throw 'delete failed' 254 | else 255 | done() 256 | }) 257 | } 258 | }) 259 | }); 260 | it('waiting for `table` to delete (within 25 seconds)', function(done) { 261 | var $existInterval = setInterval(function() { 262 | DynamoDB 263 | .client 264 | .describeTable({ 265 | TableName: 'table' 266 | }, function(err, data) { 267 | 268 | if (err && err.code === 'ResourceNotFoundException') { 269 | clearInterval($existInterval) 270 | return done() 271 | } 272 | if (err) { 273 | clearInterval($existInterval) 274 | throw err 275 | } 276 | 277 | if (data.TableStatus === 'DELETING') 278 | process.stdout.write('.') 279 | }) 280 | }, 1000) 281 | }) 282 | 283 | 284 | }) 285 | -------------------------------------------------------------------------------- /test/tests/010-describe.js: -------------------------------------------------------------------------------- 1 | 2 | describe('.table().describe()', function () { 3 | it('should throw error on inexistent table', function(done) { 4 | DynamoDB 5 | .table('inexistent-table') 6 | .describe(function(err, data) { 7 | if (err) 8 | done() 9 | else 10 | throw err 11 | }) 12 | }) 13 | it('should return a valid Object for createTable', function(done) { 14 | DynamoDB 15 | .table($tableName) 16 | .describe( function(err, data) { 17 | if (err) 18 | throw err 19 | else { 20 | // create table based on 1st table 21 | data.TableName = 'copy-' + $tableName 22 | DynamoDB 23 | .client.createTable( data, function(err,data) { 24 | // failed to create an identic table 25 | if (err) 26 | throw err 27 | else 28 | done() 29 | } ) 30 | } 31 | }) 32 | }) 33 | it('waiting for temporary table to become active', function(done) { 34 | var $existInterval = setInterval(function() { 35 | DynamoDB 36 | .client 37 | .describeTable({ 38 | TableName: 'copy-' + $tableName 39 | }, function(err, data) { 40 | if (err) { 41 | clearInterval($existInterval) 42 | throw err 43 | } else { 44 | if (data.Table.TableStatus === 'ACTIVE') { 45 | clearInterval($existInterval) 46 | done() 47 | } 48 | } 49 | }) 50 | }, 3000) 51 | }) 52 | it('deleting copy of temporary table from previous test', function(done) { 53 | DynamoDB 54 | .client 55 | .describeTable({ 56 | TableName: 'copy-' + $tableName 57 | }, function(err, data) { 58 | if (err) { 59 | if (err.code === 'ResourceNotFoundException') 60 | done() 61 | else 62 | throw 'could not describe table' 63 | } else { 64 | DynamoDB 65 | .client 66 | .deleteTable({ 67 | TableName: 'copy-' + $tableName 68 | }, function(err, data) { 69 | if (err) 70 | throw 'delete failed' 71 | else 72 | done() 73 | }) 74 | } 75 | }) 76 | }); 77 | it('waiting for temporary table to delete', function(done) { 78 | var $existInterval = setInterval(function() { 79 | DynamoDB 80 | .client 81 | .describeTable({ 82 | TableName: 'copy-' + $tableName 83 | }, function(err, data) { 84 | 85 | if (err && err.code === 'ResourceNotFoundException') { 86 | clearInterval($existInterval) 87 | return done() 88 | } 89 | if (err) { 90 | throw err 91 | return 92 | } 93 | if (data.TableStatus === 'DELETING') 94 | process.stdout.write('.') 95 | }) 96 | }, 3000) 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /test/tests/020-specialSigns.js: -------------------------------------------------------------------------------- 1 | describe('special signs in attribute names', function () { 2 | it('insert attribute with "-" in name', function(done) { 3 | DynamoDB 4 | .table($tableName) 5 | .return( DynamoDB.ALL_NEW ) 6 | .insert_or_update({ 7 | hash: 'hash_with_minus', 8 | range: 1234, 9 | 'account-id': "aaa", 10 | 'non-key-attribute': 1, 11 | 'nested-object': { 'nested-attribute': 2 } 12 | }, function(err, data) { 13 | if (err) 14 | throw err 15 | 16 | assert.equal(data['account-id'],'aaa') 17 | done() 18 | }) 19 | 20 | }) 21 | 22 | it('query attributes with "-" sign in where()', function(done) { 23 | DynamoDB 24 | .table($tableName) 25 | .index('byAccount-Id') 26 | .descending() 27 | .where('account-id').eq( 'aaa' ) 28 | .on('beforeRequest', function(op, payload) { 29 | //console.log(op, JSON.stringify(payload,null,"\t")) 30 | }) 31 | .query(function(err, data ) { 32 | if (err) 33 | throw err 34 | 35 | assert.equal(data.length,1) 36 | assert.equal(data[0]['account-id'],'aaa') 37 | assert.equal(data[0]['nested-object']['nested-attribute'],2) 38 | done() 39 | }) 40 | }) 41 | 42 | it('query attributes with "-" sign in filter()', function(done) { 43 | DynamoDB 44 | .table($tableName) 45 | .index('byAccount-Id') 46 | .descending() 47 | .where('account-id').eq( 'aaa' ) 48 | .having('non-key-attribute').gt(0) 49 | .having('nested-object.nested-attribute').gt(0) 50 | .on('beforeRequest', function(op, payload) { 51 | //console.log(op, JSON.stringify(payload,null,"\t")) 52 | }) 53 | .query(function(err, data ) { 54 | if (err) 55 | throw err 56 | 57 | assert.equal(data.length,1) 58 | assert.equal(data[0]['non-key-attribute'],1) 59 | assert.equal(data[0]['nested-object']['nested-attribute'],2) 60 | done() 61 | }) 62 | }) 63 | it('query attributes with "-" sign in select()', function(done) { 64 | DynamoDB 65 | .table($tableName) 66 | .select("non-key-attribute","nested-object.nested-attribute") 67 | .index('byAccount-Id') 68 | .descending() 69 | .where('account-id').eq( 'aaa' ) 70 | .on('beforeRequest', function(op, payload) { 71 | //console.log(op, JSON.stringify(payload,null,"\t")) 72 | }) 73 | .query(function(err, data ) { 74 | if (err) 75 | throw err 76 | 77 | assert.equal(data.length,1) 78 | assert.equal(Object.keys(data[0]).length,2) 79 | assert.equal(data[0]['non-key-attribute'],1) 80 | assert.equal(data[0]['nested-object']['nested-attribute'],2) 81 | done() 82 | }) 83 | }) 84 | // also in scan 85 | 86 | 87 | it('insert attribute with "." in name', function(done) { 88 | DynamoDB 89 | .table($tableName) 90 | .return( DynamoDB.ALL_NEW ) 91 | .insert_or_update({ 92 | hash: 'hash_with_dot', 93 | range: 1234, 94 | 'account.id': "aaa", 95 | 'non.key.attribute': 1, 96 | 'nested.object': { 'nested.attribute': 2 }, 97 | nested: { 98 | object: { 99 | nested: { 100 | attribute: 3 101 | } 102 | } 103 | } 104 | }, function(err, data) { 105 | if (err) 106 | throw err 107 | 108 | assert.equal(data['account.id'],'aaa') 109 | done() 110 | }) 111 | }) 112 | 113 | it('query attributes with "." sign in where()', function(done) { 114 | DynamoDB 115 | .table($tableName) 116 | .index('byAccount.Id') 117 | .descending() 118 | .where('account.id').eq( 'aaa' ) 119 | .on('beforeRequest', function(op, payload) { 120 | //console.log(op, JSON.stringify(payload,null,"\t")) 121 | }) 122 | .query(function(err, data ) { 123 | if (err) 124 | throw err 125 | 126 | assert.equal(data.length,1) 127 | assert.equal(data[0]['account.id'],'aaa') 128 | assert.equal(data[0]['nested.object']['nested.attribute'],2) 129 | assert.equal(data[0].nested.object.nested.attribute, 3 ) 130 | done() 131 | }) 132 | }) 133 | 134 | // allow "." in filter by wrapping attribute name in "" 135 | it('should allow attributes with "." sign in filter()', function(done) { 136 | DynamoDB 137 | .table($tableName) 138 | .index('byAccount.Id') 139 | .where('account.id').eq( 'aaa' ) 140 | .having('"non.key.attribute"').gt(0) 141 | .having('"nested.object"."nested.attribute"').eq(2) 142 | .having('nested.object.nested.attribute').eq(3) 143 | .on('beforeRequest', function(op, payload) { 144 | //console.log(op, JSON.stringify(payload,null,"\t")) 145 | }) 146 | .query(function(err, data ) { 147 | if (err) 148 | throw err 149 | 150 | assert.equal(data.length,1) 151 | assert.equal(data[0]['non.key.attribute'],1) 152 | assert.equal(data[0]['nested.object']['nested.attribute'],2) 153 | assert.equal(data[0].nested.object.nested.attribute, 3 ) 154 | //console.log(JSON.stringify(data, null, "\t")) 155 | 156 | done() 157 | }) 158 | }) 159 | 160 | 161 | // allow "." in select by wrapping attribute name in "" 162 | it('should allow attributes with "." sign in select()', function(done) { 163 | DynamoDB 164 | .table($tableName) 165 | .select('"non.key.attribute"','"nested.object"."nested.attribute"', 'nested.object.nested.attribute') 166 | .index('byAccount.Id') 167 | .descending() 168 | .where('account.id').eq( 'aaa' ) 169 | .on('beforeRequest', function(op, payload) { 170 | //console.log(op, JSON.stringify(payload,null,"\t")) 171 | }) 172 | .query(function(err, data ) { 173 | if (err) 174 | throw err 175 | 176 | assert.equal(data.length,1) 177 | assert.equal(data[0]['non.key.attribute'],1) 178 | assert.equal(data[0]['nested.object']['nested.attribute'],2) 179 | assert.equal(data[0].nested.object.nested.attribute, 3 ) 180 | //console.log(JSON.stringify(data, null, "\t")) 181 | done() 182 | }) 183 | }) 184 | 185 | }) 186 | -------------------------------------------------------------------------------- /test/tests/030-insert.js: -------------------------------------------------------------------------------- 1 | 2 | describe('insert()', function () { 3 | 4 | it('should fail if missing RANGE', function(done) { 5 | DynamoDB 6 | .table($tableName) 7 | .insert({ 8 | hash: 'hash1' 9 | }, function(err, data) { 10 | if (err) 11 | done() 12 | else 13 | throw err 14 | }) 15 | }) 16 | it('should fail if missing HASH', function(done) { 17 | DynamoDB 18 | .table($tableName) 19 | .insert({ 20 | range: 1 21 | }, function(err, data) { 22 | if (err) 23 | done() 24 | else 25 | throw err 26 | }) 27 | }) 28 | it('should fail if HASH is wrong type', function(done) { 29 | DynamoDB 30 | .table($tableName) 31 | .insert({ 32 | hash: 1, 33 | range: 1 34 | }, function(err, data) { 35 | if (err) 36 | done() 37 | else 38 | throw err 39 | }) 40 | }) 41 | it('should fail if RANGE is wrong type', function(done) { 42 | DynamoDB 43 | .table($tableName) 44 | .insert({ 45 | hash: 'hash1', 46 | range: 'xxx' 47 | }, function(err, data) { 48 | if (err) 49 | done() 50 | else 51 | throw err 52 | }) 53 | }) 54 | it('should fail if GSI RANGE is wrong type', function(done) { 55 | DynamoDB 56 | .table($tableName) 57 | .where('hash').eq('hash1') 58 | .where('range').eq(1) 59 | .delete(function( err, data ) { 60 | DynamoDB 61 | .table($tableName) 62 | .insert({ 63 | hash: 'hash1', 64 | range: 1, 65 | gsi_range: 1 66 | }, function(err, data) { 67 | if (err) 68 | done() 69 | else 70 | throw err 71 | }) 72 | }) 73 | }) 74 | 75 | it('should NOT fail when missing callback', function(done) { 76 | DynamoDB 77 | .table($tableName) 78 | .insert({ 79 | hash: 'hash1', 80 | range: 99, 81 | number: 99, 82 | null: null 83 | }) 84 | setTimeout(function() { 85 | done() 86 | },5000) 87 | }) 88 | 89 | it('should insert when item does not exist', function(done) { 90 | DynamoDB 91 | .table($tableName) 92 | .where('hash').eq('hash1') 93 | .where('range').eq(1) 94 | .delete(function( err, data ) { 95 | DynamoDB 96 | .table($tableName) 97 | .insert({ 98 | hash: 'hash1', 99 | range: 1, 100 | number: 1, 101 | boolean: false, 102 | delete_me: 'aaa', 103 | gsi_range: 'a', 104 | array: [1,2,3], 105 | object: {aaa:1,bbb:2, ccc:3, ddd: {ddd1: 1}, eee: [1,'eee1']}, 106 | }, function(err, data) { 107 | if (err) 108 | throw err 109 | 110 | DynamoDB 111 | .table($tableName) 112 | .where('hash').eq('hash1') 113 | .where('range').eq(1) 114 | .get(function(err, item) { 115 | if (err) 116 | throw err 117 | 118 | assert.deepEqual(item, { 119 | hash: 'hash1', 120 | range: 1, 121 | number: 1, 122 | boolean: false, 123 | delete_me: 'aaa', 124 | gsi_range: 'a', 125 | array: [1,2,3], 126 | object: {aaa:1,bbb:2, ccc:3, ddd: {ddd1: 1}, eee: [1,'eee1']}, 127 | }, {strict: true }) 128 | done() 129 | }) 130 | }) 131 | }) 132 | }) 133 | 134 | it('insert stringset', function(done) { 135 | DynamoDB 136 | .table($tableName) 137 | .where('hash').eq('hash_test_stringset') 138 | .where('range').eq(1) 139 | .delete(function( err, data ) { 140 | DynamoDB 141 | .table($tableName) 142 | .insert({ 143 | hash: 'hash_test_stringset', 144 | range: 1, 145 | string_set: DynamoDB.stringSet(['aaa','bbb','ccc']), 146 | }, function(err, data) { 147 | if (err) 148 | throw err 149 | 150 | DynamoDB 151 | .table($tableName) 152 | .where('hash').eq('hash_test_stringset') 153 | .where('range').eq(1) 154 | .get(function(err, item) { 155 | if (err) 156 | throw err 157 | 158 | assert.deepEqual(item, { 159 | hash: 'hash_test_stringset', 160 | range: 1, 161 | string_set: ['aaa','bbb','ccc'], 162 | }, {strict: true }) 163 | done() 164 | }) 165 | }) 166 | }) 167 | }) 168 | 169 | it('insert numberset', function(done) { 170 | DynamoDB 171 | .table($tableName) 172 | .where('hash').eq('hash_test_numberset') 173 | .where('range').eq(1) 174 | .delete(function( err, data ) { 175 | DynamoDB 176 | .table($tableName) 177 | .insert({ 178 | hash: 'hash_test_numberset', 179 | range: 1, 180 | number_set: DynamoDB.numberSet([111,222,333]), 181 | }, function(err, data) { 182 | if (err) 183 | throw err 184 | 185 | DynamoDB 186 | .table($tableName) 187 | .where('hash').eq('hash_test_numberset') 188 | .where('range').eq(1) 189 | .get(function(err, item) { 190 | if (err) 191 | throw err 192 | 193 | item.number_set.sort() 194 | assert.deepEqual(item, { 195 | hash: 'hash_test_numberset', 196 | range: 1, 197 | number_set: [ 111, 222, 333 ], 198 | }, {strict: true }) 199 | done() 200 | }) 201 | }) 202 | }) 203 | }) 204 | 205 | 206 | it('.insert() - test new Set( )', function(done) { 207 | DynamoDB 208 | .table($tableName) 209 | .insert({ 210 | hash: 'hash-test-set-ns', 211 | range: 1, 212 | number_set: new Set([ 111, 222, 333 ]), 213 | }, function(err, data) { 214 | if (err) 215 | throw err 216 | 217 | DynamoDB 218 | .table($tableName) 219 | .where('hash').eq('hash-test-set-ns') 220 | .where('range').eq(1) 221 | .get(function(err, item) { 222 | if (err) 223 | throw err 224 | 225 | item.number_set.sort() 226 | assert.deepEqual(item, { 227 | hash: 'hash-test-set-ns', 228 | range: 1, 229 | number_set: [ 111, 222, 333 ], 230 | }, {strict: true }) 231 | done() 232 | }) 233 | }) 234 | }) 235 | 236 | it('.insert() - test new Set( )', function(done) { 237 | DynamoDB 238 | .table($tableName) 239 | .insert({ 240 | hash: 'hash-test-set-ss', 241 | range: 1, 242 | string_set: new Set([ 'aaa', 'bbb', 'ccc' ]), 243 | }, function(err, data ) { 244 | if (err) 245 | throw err 246 | 247 | 248 | DynamoDB 249 | .table($tableName) 250 | .where('hash').eq('hash-test-set-ss') 251 | .where('range').eq(1) 252 | .get(function(err, item, raw ) { 253 | if (err) 254 | throw err 255 | 256 | item.string_set.sort() 257 | assert.deepEqual(item, { 258 | hash: 'hash-test-set-ss', 259 | range: 1, 260 | string_set: [ 'aaa', 'bbb', 'ccc' ], 261 | }, {strict: true }) 262 | done() 263 | }) 264 | }) 265 | }) 266 | 267 | it('.insert() - test new Set( )', function(done) { 268 | DynamoDB 269 | .table($tableName) 270 | .insert({ 271 | hash: 'hash-test-set-mixed', 272 | range: 1, 273 | set1: new Set(), 274 | set2: new Set(['a', 1, {} ]), 275 | }, function(err, data ) { 276 | if (err) 277 | throw err 278 | 279 | 280 | DynamoDB 281 | .table($tableName) 282 | .where('hash').eq('hash-test-set-mixed') 283 | .where('range').eq(1) 284 | .get(function(err, item, raw ) { 285 | if (err) 286 | throw err 287 | 288 | assert.deepEqual(Array.isArray(item.set1), true ) 289 | assert.deepEqual(Array.isArray(item.set2), true ) 290 | 291 | done() 292 | }) 293 | }) 294 | }) 295 | 296 | it('insert binary', function(done) { 297 | var $buf_obj = { 298 | hash: 'hash_test_buffer', 299 | range: 1, 300 | buffer: new Buffer("test", "utf-8"), 301 | nested_array_buffer: [new Buffer("test", "utf-8")], 302 | nested_object_buffer: { buffer: new Buffer("test", "utf-8") }, 303 | } 304 | DynamoDB 305 | .table($tableName) 306 | .insert($buf_obj, function(err, data) { 307 | if (err) 308 | throw err 309 | 310 | DynamoDB 311 | .table($tableName) 312 | .where('hash').eq('hash_test_buffer') 313 | .where('range').eq(1) 314 | .get(function(err, item) { 315 | if (err) 316 | throw err 317 | 318 | assert.deepEqual(item, $buf_obj ) 319 | done() 320 | }) 321 | }) 322 | }) 323 | 324 | it('insert empty string', function(done) { 325 | var $obj = { 326 | hash: 'hash_empty_string', 327 | range: 1, 328 | empty_string: '', 329 | } 330 | DynamoDB 331 | .table($tableName) 332 | .insert($obj, function(err, data) { 333 | if (err) 334 | throw err 335 | 336 | DynamoDB 337 | .table($tableName) 338 | .where('hash').eq('hash_empty_string') 339 | .where('range').eq(1) 340 | .get(function(err, item) { 341 | if (err) 342 | throw err 343 | 344 | assert.deepEqual(item, $obj, {strict: true } ) 345 | done() 346 | }) 347 | }) 348 | }) 349 | it('should fail when item already exists', function(done) { 350 | DynamoDB 351 | .table($tableName) 352 | .insert({ 353 | hash: 'hash1', 354 | range: 1 355 | }, function(err, data) { 356 | if (err) 357 | done() 358 | else 359 | throw err 360 | }) 361 | }) 362 | 363 | it('.insert().then()', function(done) { 364 | DynamoDB 365 | .table($tableName) 366 | .insert({ 367 | hash: 'promise', 368 | range: 1 369 | }) 370 | .then(function(data) { 371 | done() 372 | }) 373 | }) 374 | 375 | // causes UnhandledPromiseRejectionWarning 376 | // it('.insert() - unhandled', function(done) { 377 | // DynamoDB 378 | // .table($tableName) 379 | // .insert({ 380 | // hash: 'promise', 381 | // range: 1 382 | // }) 383 | // setTimeout(function() { 384 | // done() 385 | // },5000) 386 | // }) 387 | 388 | it('.insert().catch()', function(done) { 389 | DynamoDB 390 | .table($tableName) 391 | .insert({ 392 | hash: 'promise', 393 | range: 1 394 | }) 395 | .catch(function(err) { 396 | done() 397 | }) 398 | }) 399 | it('.insert().then(,errorHandler)', function(done) { 400 | DynamoDB 401 | .table($tableName) 402 | .insert({ 403 | hash: 'promise', 404 | range: 1 405 | }) 406 | .then( null, function(err) { 407 | done() 408 | }) 409 | }) 410 | it('cleanup', function(done) { 411 | DynamoDB 412 | .table($tableName) 413 | .scan(function(err, data) { 414 | if (err) 415 | throw err 416 | else { 417 | async.each(data, function(item,cb) { 418 | DynamoDB.table($tableName).where('hash').eq(item.hash).where('range').eq(item.range).delete(function(err) { cb(err) }) 419 | }, function(err) { 420 | if (err) 421 | throw err 422 | else 423 | done() 424 | }) 425 | } 426 | }) 427 | }) 428 | 429 | }) 430 | -------------------------------------------------------------------------------- /test/tests/031-insert_or_update.js: -------------------------------------------------------------------------------- 1 | 2 | describe('insert_or_update()', function () { 3 | it('should not modify $attrz', function(done) { 4 | var $to_insert = { 5 | hash: 'hash_test_clone_object', 6 | range: 1, 7 | nested1: { value: 1}, 8 | 9 | string: DynamoDB.S("test"), 10 | number: DynamoDB.add(1), 11 | } 12 | var $cloned = JSON.parse(JSON.stringify($to_insert)) 13 | DynamoDB 14 | .table($tableName) 15 | .return(DynamoDB.ALL_NEW) 16 | .insert_or_update($to_insert, function(err, data) { 17 | if (err) 18 | throw err 19 | 20 | assert.deepEqual($to_insert,$cloned) 21 | done() 22 | }) 23 | }) 24 | it('should insert new item', function(done) { 25 | DynamoDB 26 | .table($tableName) 27 | .insert_or_update({ 28 | hash: 'hash1', 29 | range: 1, 30 | 31 | boolean: false, 32 | number_a: 5, 33 | number_b: 7, 34 | number_c: 1, 35 | number_d: 1, 36 | number_e: 1, 37 | number_f: 1, 38 | number_deleteme: 1, 39 | 40 | array_a: [0,null,{},"string"], 41 | array_b: [0,null,{},"string"], 42 | array_c: [0,null,{},"string"], 43 | array_d: [0,null,{},"string"], 44 | array_e: [0,null,{},"string"], 45 | array_deleteme: [0,null,{},"string"], 46 | 47 | 48 | object: { property1: 'value1' }, 49 | object_deleteme: { property1: 'value1' }, 50 | 51 | delete_me_later: {}, 52 | 53 | string_set_a: DynamoDB.stringSet(['sss','bbb','ccc']), 54 | string_set_b: DynamoDB.stringSet(['sss','bbb','ccc']), 55 | string_set_c: DynamoDB.stringSet(['sss','bbb','ccc']), 56 | string_set_d: DynamoDB.stringSet(['sss','bbb','ccc']), 57 | string_set_deleteme: DynamoDB.stringSet(['sss','bbb','ccc']), 58 | 59 | 60 | number_set_a: DynamoDB.numberSet([111,222,333]), 61 | number_set_b: DynamoDB.numberSet([111,222,333]), 62 | number_set_c: DynamoDB.numberSet([111,222,333]), 63 | number_set_d: DynamoDB.numberSet([111,222,333]), 64 | number_set_deleteme: DynamoDB.numberSet([111,222,333]), 65 | },function(err,data) { 66 | if (err) { 67 | throw err 68 | return 69 | } 70 | DynamoDB 71 | .table($tableName) 72 | .where('hash').eq('hash1') 73 | .where('range').eq(1) 74 | .get(function(err,item) { 75 | if (err) { 76 | throw err 77 | return 78 | } 79 | item.number_set_a.sort() 80 | item.number_set_b.sort() 81 | item.number_set_c.sort() 82 | item.number_set_d.sort() 83 | item.number_set_deleteme.sort() 84 | 85 | item.string_set_a.sort() 86 | item.string_set_b.sort() 87 | item.string_set_c.sort() 88 | item.string_set_d.sort() 89 | item.string_set_deleteme.sort() 90 | 91 | assert.deepEqual(item,{ 92 | hash: 'hash1', 93 | range: 1, 94 | 95 | boolean: false, 96 | number_a: 5, 97 | number_b: 7, 98 | number_c: 1, 99 | number_d: 1, 100 | number_e: 1, 101 | number_f: 1, 102 | number_deleteme: 1, 103 | 104 | array_a: [0,null,{},"string"], 105 | array_b: [0,null,{},"string"], 106 | array_c: [0,null,{},"string"], 107 | array_d: [0,null,{},"string"], 108 | array_e: [0,null,{},"string"], 109 | array_deleteme: [0,null,{},"string"], 110 | 111 | object: { property1: 'value1' }, 112 | object_deleteme: { property1: 'value1' }, 113 | 114 | delete_me_later: {}, 115 | 116 | string_set_a: ['bbb','ccc','sss'], 117 | string_set_b: ['bbb','ccc','sss'], 118 | string_set_c: ['bbb','ccc','sss'], 119 | string_set_d: ['bbb','ccc','sss'], 120 | string_set_deleteme: ['bbb','ccc','sss'], 121 | 122 | number_set_a: [111,222,333], 123 | number_set_b: [111,222,333], 124 | number_set_c: [111,222,333], 125 | number_set_d: [111,222,333], 126 | number_set_deleteme: [111,222,333], 127 | }) 128 | done() 129 | }) 130 | }) 131 | }) 132 | 133 | it('should update existing item', function(done) { 134 | DynamoDB 135 | .table($tableName) 136 | .insert_or_update({ 137 | hash: 'hash1', 138 | range: 1, 139 | a: 'a', // new item 140 | boolean2: false, 141 | 142 | number_a: DynamoDB.add(5,'N'), 143 | number_b: DynamoDB.add(DynamoDB.number(3)), 144 | number_c: DynamoDB.add(5), 145 | number_d: DynamoDB.add(), 146 | number_deleteme: DynamoDB.del(), 147 | 148 | string_set_a: DynamoDB.add(['aaa','zzz'],'SS'), 149 | string_set_b: DynamoDB.add(DynamoDB.stringSet(['aaa','zzz'])), 150 | string_set_c: DynamoDB.del(['bbb','sss','xxx'],'SS'), 151 | string_set_d: DynamoDB.del(DynamoDB.stringSet(['bbb','sss','yyy'])), 152 | string_set_deleteme: DynamoDB.del(), 153 | 154 | number_set_a: DynamoDB.add([444,555],'NS'), 155 | number_set_b: DynamoDB.add(DynamoDB.numberSet([444,555])), 156 | number_set_c: DynamoDB.del([111,333],'NS'), 157 | number_set_d: DynamoDB.del(DynamoDB.numberSet([111,333])), 158 | number_set_deleteme: DynamoDB.del(), 159 | 160 | // ValidationException: One or more parameter values were invalid: ADD action is not supported for the type L 161 | //array_a: DynamoDB.add(['fff','ggg'],'L'), 162 | //array_b: DynamoDB.add( DynamoDB.list(['fff','ggg'])), 163 | //array_c: DynamoDB.add( ['fff','ggg'] ), 164 | //array_deleteme: DynamoDB.del(), 165 | 166 | object_deleteme: DynamoDB.del(), 167 | 168 | }, function(err) { 169 | if (err) { 170 | console.log(err) 171 | process.exit() 172 | } 173 | 174 | DynamoDB 175 | .table($tableName) 176 | .where('hash').eq('hash1') 177 | .where('range').eq(1) 178 | .consistent_read() 179 | .get(function(err,item) { 180 | if (err) { 181 | throw err 182 | return 183 | } 184 | 185 | // @todo: check return 186 | //assert.deepEqual(item, {}) 187 | done() 188 | }) 189 | }) 190 | }) 191 | 192 | 193 | 194 | it('insert new item with empty string', function(done) { 195 | var $obj = { 196 | hash: 'insert_or_update_empty_string', 197 | range: 1, 198 | empty_string: '', 199 | } 200 | DynamoDB 201 | .table($tableName) 202 | .insert_or_update($obj, function(err, data) { 203 | if (err) 204 | throw err 205 | 206 | DynamoDB 207 | .table($tableName) 208 | .where('hash').eq('insert_or_update_empty_string') 209 | .where('range').eq(1) 210 | .get(function(err, item) { 211 | if (err) 212 | throw err 213 | 214 | assert.deepEqual(item, $obj, {strict: true } ) 215 | done() 216 | }) 217 | }) 218 | }) 219 | it('update new item with empty string', function(done) { 220 | var $obj = { 221 | hash: 'insert_or_update_empty_string', 222 | range: 1, 223 | empty_string: DynamoDB.del(), 224 | empty_string2: '', 225 | } 226 | DynamoDB 227 | .table($tableName) 228 | .insert_or_update($obj, function(err, data) { 229 | if (err) 230 | throw err 231 | 232 | DynamoDB 233 | .table($tableName) 234 | .where('hash').eq('insert_or_update_empty_string') 235 | .where('range').eq(1) 236 | .get(function(err, item) { 237 | if (err) 238 | throw err 239 | 240 | assert.deepEqual(item.empty_string2, $obj.empty_string2, {strict: true } ) 241 | done() 242 | }) 243 | }) 244 | }) 245 | 246 | it('should return nothing by default', function(done) { 247 | DynamoDB 248 | .table($tableName) 249 | .return(DynamoDB.NONE) 250 | .insert_or_update({ 251 | hash: 'hash1', 252 | range: 1, 253 | something: 'somevalue', 254 | }, function(err, data ) { 255 | if (err) throw err 256 | 257 | assert.deepEqual(data, {}) 258 | done() 259 | }) 260 | }) 261 | 262 | 263 | 264 | it('.insert_or_update().then()', function(done) { 265 | DynamoDB 266 | .table($tableName) 267 | .insert_or_update({ 268 | hash: 'promise', 269 | range: 1, 270 | }) 271 | .then(function(data) { 272 | done() 273 | }) 274 | }) 275 | 276 | // causes UnhandledPromiseRejectionWarning 277 | // it('.insert_or_update() - unhandled', function(done) { 278 | // DynamoDB 279 | // .table($tableName) 280 | // .insert_or_update({ 281 | // hash: 'promise', 282 | // range: 1, 283 | // }) 284 | // setTimeout(function() { 285 | // done() 286 | // },5000) 287 | // }) 288 | 289 | 290 | it('.insert_or_update().catch()', function(done) { 291 | DynamoDB 292 | .table($tableName) 293 | .insert_or_update({ 294 | hash: 1, // will fail because of wrong data type 295 | range: 1, 296 | }) 297 | .catch(function(err) { 298 | done() 299 | }) 300 | }) 301 | 302 | it('cleanup...', function(done) { 303 | DynamoDB 304 | .table($tableName) 305 | .scan(function(err, data) { 306 | if (err) 307 | throw err 308 | else { 309 | async.each(data, function(item,cb) { 310 | DynamoDB.table($tableName).where('hash').eq(item.hash).where('range').eq(item.range).delete(function(err) { cb(err) }) 311 | }, function(err) { 312 | if (err) 313 | throw err 314 | else 315 | done() 316 | }) 317 | } 318 | }) 319 | }) 320 | }) 321 | -------------------------------------------------------------------------------- /test/tests/032-insert_or_replace.js: -------------------------------------------------------------------------------- 1 | 2 | describe('insert_or_replace( new_item )', function () { 3 | it('should insert new item', function(done) { 4 | DynamoDB 5 | .table($tableName) 6 | .return(DynamoDB.ALL_OLD) 7 | .insert_or_replace({ 8 | hash: 'hash1', 9 | range: 1, 10 | number: 5, 11 | boolean: false, 12 | array: [0,null,{},"string"], 13 | delete_me_later: {} 14 | },function(err,data) { 15 | if (err) { 16 | throw err 17 | return 18 | } 19 | DynamoDB 20 | .table($tableName) 21 | .where('hash').eq('hash1') 22 | .where('range').eq(1) 23 | .get(function(err,data) { 24 | if (err) { 25 | throw err 26 | return 27 | } 28 | assert.deepEqual(data, {hash: 'hash1', range: 1, number: 5, boolean: false, array: [0,null,{},"string"], delete_me_later: {} }) 29 | done() 30 | }) 31 | }) 32 | }) 33 | it('.insert_or_replace( existing_item )', function(done) { 34 | DynamoDB 35 | .table($tableName) 36 | .return(DynamoDB.ALL_OLD) 37 | //.return_consumed_capacity('INDEXES') 38 | .insert_or_replace({ 39 | hash: 'hash1', 40 | range: 1, 41 | boolean: false, 42 | a: 'a' 43 | }, function(err, data) { 44 | if (err) { 45 | throw err 46 | return 47 | } 48 | 49 | DynamoDB 50 | .table($tableName) 51 | .where('hash').eq('hash1') 52 | .where('range').eq(1) 53 | .get(function(err,data) { 54 | if (err) { 55 | throw err 56 | return 57 | } 58 | assert.deepEqual(data, {hash: 'hash1', range: 1, boolean: false, a: 'a'}) 59 | done() 60 | }) 61 | }) 62 | }) 63 | it('.return(DynamoDB.ALL_OLD).insert_or_replace()', function(done) { 64 | DynamoDB 65 | .table($tableName) 66 | .return(DynamoDB.ALL_OLD) 67 | .insert_or_replace({ 68 | hash: 'hash1', 69 | range: 1, 70 | something: 'somevalue', 71 | }, function(err, data ) { 72 | if (err) throw err 73 | 74 | assert.deepEqual(data, {hash: 'hash1', range: 1, boolean: false, a: 'a'}) 75 | done() 76 | }) 77 | }) 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | it('.insert_or_replace() - test new Set( )', function(done) { 86 | DynamoDB 87 | .table($tableName) 88 | .insert_or_replace({ 89 | hash: 'test-replace-set-ns', 90 | range: 1, 91 | number_set: new Set([ 111, 222, 333 ]), 92 | }, function(err, data) { 93 | if (err) 94 | throw err 95 | 96 | DynamoDB 97 | .table($tableName) 98 | .where('hash').eq('test-replace-set-ns') 99 | .where('range').eq(1) 100 | .get(function(err, item) { 101 | if (err) 102 | throw err 103 | 104 | item.number_set.sort() 105 | assert.deepEqual(item, { 106 | hash: 'test-replace-set-ns', 107 | range: 1, 108 | number_set: [ 111, 222, 333 ], 109 | }, {strict: true }) 110 | done() 111 | }) 112 | }) 113 | }) 114 | 115 | it('.insert_or_replace() - test new Set( )', function(done) { 116 | DynamoDB 117 | .table($tableName) 118 | .insert_or_replace({ 119 | hash: 'test-replace-set-ss', 120 | range: 1, 121 | string_set: new Set([ 'aaa', 'bbb', 'ccc' ]), 122 | }, function(err, data ) { 123 | if (err) 124 | throw err 125 | 126 | 127 | DynamoDB 128 | .table($tableName) 129 | .where('hash').eq('test-replace-set-ss') 130 | .where('range').eq(1) 131 | .get(function(err, item, raw ) { 132 | if (err) 133 | throw err 134 | 135 | item.string_set.sort() 136 | assert.deepEqual(item, { 137 | hash: 'test-replace-set-ss', 138 | range: 1, 139 | string_set: [ 'aaa', 'bbb', 'ccc' ], 140 | }, {strict: true }) 141 | done() 142 | }) 143 | }) 144 | }) 145 | 146 | it('.insert_or_replace() - test new Set( )', function(done) { 147 | DynamoDB 148 | .table($tableName) 149 | .insert_or_replace({ 150 | hash: 'test-replace-set-mixed', 151 | range: 1, 152 | set1: new Set(), 153 | set2: new Set(['a', 1, {} ]), 154 | }, function(err, data ) { 155 | if (err) 156 | throw err 157 | 158 | 159 | DynamoDB 160 | .table($tableName) 161 | .where('hash').eq('test-replace-set-mixed') 162 | .where('range').eq(1) 163 | .get(function(err, item, raw ) { 164 | if (err) 165 | throw err 166 | 167 | assert.deepEqual(Array.isArray(item.set1), true ) 168 | assert.deepEqual(Array.isArray(item.set2), true ) 169 | 170 | done() 171 | }) 172 | }) 173 | }) 174 | 175 | 176 | 177 | 178 | 179 | it('insert_or_replace() insert new item with empty string', function(done) { 180 | var $obj = { 181 | hash: 'insert_or_replace_empty_string', 182 | range: 1, 183 | empty_string: '', 184 | } 185 | DynamoDB 186 | .table($tableName) 187 | .insert_or_replace($obj, function(err, data) { 188 | if (err) 189 | throw err 190 | 191 | DynamoDB 192 | .table($tableName) 193 | .where('hash').eq('insert_or_replace_empty_string') 194 | .where('range').eq(1) 195 | .get(function(err, item) { 196 | if (err) 197 | throw err 198 | 199 | if (item.empty_string !== '') 200 | throw 'empty string failed' 201 | 202 | done() 203 | }) 204 | }) 205 | }) 206 | it('insert_or_replace() replace existing item with empty string attribute', function(done) { 207 | var $obj = { 208 | hash: 'insert_or_replace_empty_string', 209 | range: 1, 210 | empty_string2: '', 211 | } 212 | DynamoDB 213 | .table($tableName) 214 | .insert_or_replace($obj, function(err, data) { 215 | if (err) 216 | throw err 217 | 218 | DynamoDB 219 | .table($tableName) 220 | .where('hash').eq('insert_or_replace_empty_string') 221 | .where('range').eq(1) 222 | .get(function(err, item) { 223 | if (err) 224 | throw err 225 | 226 | assert.deepEqual(item.empty_string2, $obj.empty_string2, {strict: true } ) 227 | assert.deepEqual(item.hasOwnProperty('empty_string'), false ) 228 | done() 229 | }) 230 | }) 231 | }) 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | it('.insert_or_replace().then()', function(done) { 246 | DynamoDB 247 | .table($tableName) 248 | .insert_or_replace({ 249 | hash: 'promise', 250 | range: 1, 251 | }) 252 | .then(function(data) { 253 | done() 254 | }) 255 | }) 256 | 257 | // causes UnhandledPromiseRejectionWarning 258 | // it('.insert_or_replace() - unhandled', function(done) { 259 | // DynamoDB 260 | // .table($tableName) 261 | // .insert_or_replace({ 262 | // hash: 1, 263 | // range: 1, 264 | // }) 265 | // setTimeout(function() { 266 | // done() 267 | // },5000) 268 | // }) 269 | 270 | it('.insert_or_replace().catch()', function(done) { 271 | DynamoDB 272 | .table($tableName) 273 | .insert_or_replace({ 274 | hash: 1, // will fail because of wrong data type 275 | range: 1, 276 | }) 277 | .catch(function(err) { 278 | done() 279 | }) 280 | }) 281 | it('.insert_or_replace().then(,errorHandler)', function(done) { 282 | DynamoDB 283 | .table($tableName) 284 | .insert_or_replace({ 285 | hash: 1, 286 | range: 1, 287 | }) 288 | .then( null, function(err) { 289 | done() 290 | }) 291 | }) 292 | 293 | it('cleanup...', function(done) { 294 | DynamoDB 295 | .table($tableName) 296 | .scan(function(err, data) { 297 | if (err) 298 | throw err 299 | else { 300 | async.each(data, function(item,cb) { 301 | DynamoDB.table($tableName).where('hash').eq(item.hash).where('range').eq(item.range).delete(function(err) { cb(err) }) 302 | }, function(err) { 303 | if (err) 304 | throw err 305 | else 306 | done() 307 | }) 308 | } 309 | }) 310 | }) 311 | }) 312 | -------------------------------------------------------------------------------- /test/tests/040-update.js: -------------------------------------------------------------------------------- 1 | 2 | describe('update()', function () { 3 | 4 | it('.explain().update', function(done) { 5 | 6 | DynamoDB 7 | .explain() 8 | .table($tableName) 9 | .where('hash').eq('h') 10 | .where('range').eq('r') 11 | .update({ 12 | string: 'newstring', 13 | boolean: false, 14 | increment: DynamoDB.add(9), 15 | decrement: DynamoDB.add(-2), 16 | null: null, 17 | ss: new Set(['a','b','c']), 18 | ns: new Set([1,2,3]), 19 | ssadd: DynamoDB.add( DynamoDB.SS(['d','e']) ), 20 | nsadd: DynamoDB.add( DynamoDB.NS([4,5]) ), 21 | ssdel: DynamoDB.add( DynamoDB.SS(['f','g']) ), 22 | nsdel: DynamoDB.add( DynamoDB.NS([6,7]) ), 23 | }, function(err, data) { 24 | assert.deepEqual(data,{ 25 | "method":"updateItem", 26 | "payload":{ 27 | "TableName":"test_hash_range", 28 | "Key":{"hash":{"S":"h"}, 29 | "range":{"S":"r"}}, 30 | "Expected":{"hash":{"Exists":true,"Value":{"S":"h"}},"range":{"Exists":true,"Value":{"S":"r"}}}, 31 | "AttributeUpdates":{ 32 | "string":{"Action":"PUT","Value":{"S":"newstring"}}, 33 | "boolean":{"Action":"PUT","Value":{"BOOL":false}}, 34 | "increment":{"Action":"ADD","Value":{"N":"9"}}, 35 | "decrement":{"Action":"ADD","Value":{"N":"-2"}}, 36 | "null":{"Action":"PUT","Value":{"NULL":true}}, 37 | "ss":{"Action":"PUT","Value":{"SS":["a","b","c"]}}, 38 | "ns":{"Action":"PUT","Value":{"NS":["1","2","3"]}}, 39 | "ssadd":{"Action":"ADD","Value":{"SS":["d","e"]}}, 40 | "nsadd":{"Action":"ADD","Value":{"NS":["4","5"]}}, 41 | "ssdel":{"Action":"ADD","Value":{"SS":["f","g"]}}, 42 | "nsdel":{"Action":"ADD","Value":{"NS":["6","7"]}} 43 | }, 44 | "ReturnConsumedCapacity":"TOTAL" 45 | } 46 | }) 47 | done() 48 | }) 49 | 50 | }) 51 | 52 | // dynalite: "ADD action is not supported for the type L", UpdateExpression 53 | it('.update( existing_item )', function(done) { 54 | // insert 55 | DynamoDB 56 | .table($tableName) 57 | .insert_or_replace({ 58 | hash: 'test-update', 59 | range:1, 60 | old_number: 1, 61 | //old_array: [1,2,3], 62 | string: 'aaa', 63 | 'ignore': 'me', 64 | 'delete': 'me' 65 | }, function(err) { 66 | if (err) 67 | throw err 68 | 69 | DynamoDB 70 | .table($tableName) 71 | .where('hash').eq('test-update') 72 | .where('range').eq(1) 73 | .return(DynamoDB.ALL_NEW) 74 | .update({ 75 | gsi_range: 'b', 76 | string: 'newstring', 77 | boolean: false, 78 | old_number: DynamoDB.add(9), 79 | null: null, 80 | //old_array: DynamoDB.add([ 1, 'a', null, { k1: 'v1', k2: 'v2', k3: 'v3' }, [] ]), 81 | object: { key1: 'value1', key2: 22 }, 82 | delete: DynamoDB.del() 83 | }, function(err, data) { 84 | if (err) 85 | throw err 86 | 87 | assert.deepEqual(data, { 88 | hash: 'test-update', 89 | range: 1, 90 | old_number: 10, 91 | //old_array: [ 1,2,3, 1, 'a', null, { k3: 'v3', k1: 'v1', k2: 'v2' }, [] ], 92 | string: 'newstring', 93 | boolean: false, 94 | ignore: 'me', 95 | 96 | null: null, 97 | gsi_range: 'b', 98 | object: { key2: 22, key1: 'value1' }, 99 | }) 100 | done() 101 | }) 102 | }) 103 | }) 104 | 105 | 106 | it('.return(DynamoDB.UPDATED_OLD).update( existing_item )', function(done) { 107 | 108 | DynamoDB 109 | .table($tableName) 110 | //.on('beforeRequest', function(op, payload) { 111 | // console.log(op, JSON.stringify(payload,null,"\t")) 112 | //}) 113 | .insert_or_replace({ 114 | hash: 'test-updated-old', 115 | range: 1, 116 | 117 | number: 10, 118 | number2: 10.5, 119 | boolean: true, 120 | }, function(err) { 121 | if (err) throw err 122 | 123 | DynamoDB 124 | .table($tableName) 125 | .where('hash').eq('test-updated-old') 126 | .where('range').eq(1) 127 | .return(DynamoDB.UPDATED_OLD) 128 | //.on('beforeRequest', function(op, payload) { 129 | // console.log(op, JSON.stringify(payload,null,"\t")) 130 | //}) 131 | .update({ 132 | number: DynamoDB.add(20), 133 | number2: 30, 134 | boolean: false, 135 | }, function(err, data ) { 136 | 137 | if (err) throw err 138 | assert.deepEqual(data, { number: 10, number2: 10.5, boolean: true }) 139 | done() 140 | }) 141 | }) 142 | }) 143 | 144 | 145 | it('.update() - test new Set( )', function(done) { 146 | DynamoDB 147 | .table($tableName) 148 | .where('hash').eq('test-updated-old') 149 | .where('range').eq(1) 150 | .update({ 151 | set: new Set([ 111, 222, 333 ]), 152 | }, function(err, data) { 153 | if (err) 154 | throw err 155 | 156 | DynamoDB 157 | .table($tableName) 158 | .where('hash').eq('test-updated-old') 159 | .where('range').eq(1) 160 | .get(function(err, item, raw ) { 161 | if (err) 162 | throw err 163 | 164 | assert.deepEqual(raw.Item.set.NS, [ 111, 222, 333 ], {strict: true }) 165 | done() 166 | }) 167 | }) 168 | }) 169 | 170 | it('.update() - test new Set( )', function(done) { 171 | DynamoDB 172 | .table($tableName) 173 | .where('hash').eq('test-updated-old') 174 | .where('range').eq(1) 175 | .update({ 176 | set: new Set([ 'aaa', 'bbb', 'ccc' ]), 177 | }, function(err, data ) { 178 | if (err) 179 | throw err 180 | 181 | 182 | DynamoDB 183 | .table($tableName) 184 | .where('hash').eq('test-updated-old') 185 | .where('range').eq(1) 186 | .get(function(err, item, raw ) { 187 | if (err) 188 | throw err 189 | 190 | 191 | assert.deepEqual(raw.Item.set.SS, [ 'aaa', 'bbb', 'ccc' ], {strict: true }) 192 | done() 193 | }) 194 | }) 195 | }) 196 | 197 | it('.update() - test new Set( )', function(done) { 198 | DynamoDB 199 | .table($tableName) 200 | .where('hash').eq('test-updated-old') 201 | .where('range').eq(1) 202 | .update({ 203 | set1: new Set(), 204 | set2: new Set(['a', 1, {} ]), 205 | }, function(err, data ) { 206 | if (err) 207 | throw err 208 | 209 | 210 | DynamoDB 211 | .table($tableName) 212 | .where('hash').eq('test-updated-old') 213 | .where('range').eq(1) 214 | .get(function(err, item, raw ) { 215 | if (err) 216 | throw err 217 | 218 | //console.log(JSON.stringify(raw, null, "\t")) 219 | assert.deepEqual(raw.Item.set1.L, [], {strict: true }) 220 | assert.deepEqual(raw.Item.set2.L, [ { "S": "a" }, { "N": "1" }, { "M": {} } ], {strict: true }) 221 | 222 | done() 223 | }) 224 | }) 225 | }) 226 | 227 | 228 | 229 | 230 | it('update() test empty string', function(done) { 231 | var $obj = { 232 | empty_string: '', 233 | } 234 | DynamoDB 235 | .table($tableName) 236 | .where('hash').eq('test-updated-old') 237 | .where('range').eq(1) 238 | .update($obj, function(err, data) { 239 | if (err) 240 | throw err 241 | 242 | DynamoDB 243 | .table($tableName) 244 | .where('hash').eq('test-updated-old') 245 | .where('range').eq(1) 246 | .get(function(err, item) { 247 | if (err) 248 | throw err 249 | 250 | assert.deepEqual(item.empty_string, '', {strict: true } ) 251 | done() 252 | }) 253 | }) 254 | }) 255 | 256 | 257 | 258 | 259 | it('.update().then()', function(done) { 260 | DynamoDB 261 | .table($tableName) 262 | .return(DynamoDB.UPDATED_OLD) 263 | .where('hash').eq('test-updated-old') 264 | .where('range').eq(1) 265 | .update({ 266 | number: 1, 267 | }) 268 | .then(function(data) { 269 | done() 270 | }) 271 | }) 272 | 273 | // causes UnhandledPromiseRejectionWarning 274 | // it('.update() - unhandled', function(done) { 275 | // DynamoDB 276 | // .table($tableName) 277 | // .where('hash').eq(1) 278 | // .where('range_unexistent').eq(1) 279 | // .update({}) 280 | // setTimeout(function() { 281 | // done() 282 | // },5000) 283 | // }) 284 | 285 | it('.update().catch()', function(done) { 286 | DynamoDB 287 | .table($tableName) 288 | .where('hash').eq(1) 289 | .where('range_unexistent').eq(1) 290 | .update({}) 291 | .catch(function(err) { 292 | done() 293 | }) 294 | }) 295 | 296 | 297 | 298 | 299 | it('.update() - add() to List, StringSet, NumberSet', function(done) { 300 | async.waterfall([ 301 | 302 | function(cb) { 303 | DynamoDB 304 | .table($tableName) 305 | .insert_or_replace({ 306 | hash: 'test-updated-ss-add', 307 | range: 1, 308 | arr: ['x','y'], 309 | //obj: {number: 1}, 310 | ss: DynamoDB.SS( ['bbb','ccc'] ), 311 | ns: DynamoDB.NS( [ 33,22] ), 312 | }, function(err) { 313 | if (err) throw err 314 | cb() 315 | }) 316 | }, 317 | 318 | function(cb) { 319 | DynamoDB 320 | .table($tableName) 321 | .where('hash').eq('test-updated-ss-add') 322 | .where('range').eq(1) 323 | .update({ 324 | arr: DynamoDB.add( ['x','y', false, null, {}] ), 325 | //obj: DynamoDB.add( { color: 'blue'} ), 326 | ss: DynamoDB.add( DynamoDB.SS(['aaa','ddd']) ), 327 | ns: DynamoDB.add( DynamoDB.NS([11,44]) ), 328 | 329 | }, function(err, data ) { 330 | if (err) throw err 331 | 332 | cb() 333 | }) 334 | }, 335 | 336 | ],function(err) { 337 | DynamoDB 338 | .table($tableName) 339 | .where('hash').eq('test-updated-ss-add') 340 | .where('range').eq(1) 341 | //.return(DynamoDB.UPDATED_OLD) 342 | //.on('beforeRequest', function(op, payload) { 343 | // console.log(op, JSON.stringify(payload,null,"\t")) 344 | //}) 345 | .get( function(err, data ) { 346 | if (err) throw err 347 | 348 | assert.deepEqual(data.arr, [ 'x', 'y', 'x', 'y', false, null, {} ] ) 349 | assert.deepEqual(data.ss.sort(function(a,b) { return a > b ? 1 : -1 }), [ 'aaa', 'bbb', 'ccc', 'ddd' ] ) 350 | assert.deepEqual(data.ns.sort(function(a,b) { return a > b ? 1 : -1 }), [ 11, 22, 33, 44 ] ) 351 | 352 | done() 353 | }) 354 | }) 355 | }) 356 | 357 | 358 | it('.update() - del() from StringSet and NumberSet', function(done) { 359 | async.waterfall([ 360 | 361 | function(cb) { 362 | DynamoDB 363 | .table($tableName) 364 | .insert_or_replace({ 365 | hash: 'test-updated-ss-del', 366 | range: 1, 367 | //arr: ['x','y'], 368 | ss: DynamoDB.SS( ['bbb','ccc'] ), 369 | ns: DynamoDB.NS( [ 33,22] ), 370 | }, function(err) { 371 | if (err) throw err 372 | cb() 373 | }) 374 | }, 375 | 376 | function(cb) { 377 | DynamoDB 378 | .table($tableName) 379 | .where('hash').eq('test-updated-ss-del') 380 | .where('range').eq(1) 381 | .update({ 382 | 383 | ss: DynamoDB.del( DynamoDB.SS(['bbb']) ), 384 | ns: DynamoDB.del( DynamoDB.NS([22]) ), 385 | 386 | }, function(err, data ) { 387 | if (err) throw err 388 | 389 | cb() 390 | }) 391 | }, 392 | 393 | ],function(err) { 394 | DynamoDB 395 | .table($tableName) 396 | .where('hash').eq('test-updated-ss-del') 397 | .where('range').eq(1) 398 | //.return(DynamoDB.UPDATED_OLD) 399 | //.on('beforeRequest', function(op, payload) { 400 | // console.log(op, JSON.stringify(payload,null,"\t")) 401 | //}) 402 | .get( function(err, data ) { 403 | if (err) throw err 404 | 405 | assert.deepEqual(data.ss, [ 'ccc' ] ) 406 | assert.deepEqual(data.ns, [ 33 ] ) 407 | 408 | done() 409 | }) 410 | }) 411 | }) 412 | 413 | 414 | it('cleanup...', function(done) { 415 | DynamoDB 416 | .table($tableName) 417 | .scan(function(err, data) { 418 | if (err) 419 | throw err 420 | 421 | async.each(data, function(item,cb) { 422 | DynamoDB.table($tableName).where('hash').eq(item.hash).where('range').eq(item.range).delete(function(err) { cb(err) }) 423 | }, function(err) { 424 | if (err) 425 | throw err 426 | 427 | done() 428 | }) 429 | }) 430 | }) 431 | }) 432 | -------------------------------------------------------------------------------- /test/tests/050-replace.js: -------------------------------------------------------------------------------- 1 | 2 | describe('replace()', function () { 3 | it('.where(hash).eq( wrongtype ) - should fail', function(done) { 4 | DynamoDB 5 | .table($tableName) 6 | .replace({ 7 | hash: 1, 8 | range: 1, 9 | key: 'value' 10 | }, function(err, data) { 11 | if (err) 12 | return done() 13 | 14 | throw {error: 'should fail'} 15 | }) 16 | }) 17 | 18 | it('should fail if we try to replace GSI index range key with the wrong type', function(done) { 19 | DynamoDB 20 | .table($tableName) 21 | .replace({ 22 | hash: 'hash', 23 | range: 1, 24 | gsi_range: 1 25 | }, function(err, data) { 26 | if (err) 27 | done() 28 | else 29 | throw {error: 'should fail'} 30 | }) 31 | }) 32 | it('should fail if we try to replace an inexistent item', function(done) { 33 | DynamoDB 34 | .table($tableName) 35 | .where('hash').eq() 36 | .where('range').eq(1) 37 | .replace({ 38 | hash: 'hash999', 39 | range: 1, 40 | }, function(err, data) { 41 | if (err) 42 | done() 43 | else 44 | throw {error: 'should fail'} 45 | }) 46 | }) 47 | 48 | 49 | 50 | it('.replace()', function(done) { 51 | // insert 52 | DynamoDB.table($tableName).insert({hash: 'hash1',range:1, old_number: 1, old_array: [1,2,3], string: 'aaa', 'ignore': 'me', 'delete': 'me'}, function(err) { 53 | if (err) 54 | throw err 55 | 56 | DynamoDB 57 | .table($tableName) 58 | .replace({ 59 | hash: 'hash1', 60 | range: 1, 61 | gsi_range: 'b', 62 | string: 'newstring', 63 | boolean: false, 64 | null: null, 65 | old_array: [ 1, 'a', null, { k1: 'v1', k2: 'v2', k3: 'v3' }, [] ], 66 | object: { key1: 'value1', key2: 22 }, 67 | }, function(err, data) { 68 | if (err) 69 | throw err 70 | else 71 | DynamoDB 72 | .table($tableName) 73 | .where('hash').eq('hash1') 74 | .where('range').eq(1) 75 | .consistentRead() 76 | .get(function( err, data ) { 77 | if (err) 78 | throw err 79 | 80 | assert.deepEqual(data, { 81 | hash: 'hash1', 82 | range: 1, 83 | old_array: [ 1, 'a', null, { k3: 'v3', k1: 'v1', k2: 'v2' }, [] ], 84 | string: 'newstring', 85 | boolean: false, 86 | 87 | null: null, 88 | gsi_range: 'b', 89 | object: { key2: 22, key1: 'value1' }, 90 | }) 91 | done() 92 | }) 93 | }) 94 | }) 95 | }) 96 | it('.return(DynamoDB.ALL_OLD).replace( existing_item )', function(done) { 97 | DynamoDB 98 | .table($tableName) 99 | .return(DynamoDB.ALL_OLD) 100 | .replace({ 101 | hash: 'hash1', 102 | range: 1, 103 | string: 'aaa', 104 | }, function(err, data ) { 105 | if (err) throw err 106 | 107 | assert.deepEqual(data, { hash: 'hash1', 108 | null: null, 109 | range: 1, 110 | gsi_range: 'b', 111 | object: { key2: 22, key1: 'value1' }, 112 | old_array: [ 1, 'a', null, { k3: 'v3', k1: 'v1', k2: 'v2' }, [] ], 113 | string: 'newstring', 114 | boolean: false, 115 | }) 116 | done() 117 | }) 118 | }) 119 | 120 | 121 | 122 | 123 | it('replace() test empty string', function(done) { 124 | var $obj = { 125 | hash: 'hash1', 126 | range: 1, 127 | empty_string: '', 128 | } 129 | DynamoDB 130 | .table($tableName) 131 | .replace($obj, function(err, data) { 132 | if (err) 133 | throw err 134 | 135 | DynamoDB 136 | .table($tableName) 137 | .where('hash').eq('hash1') 138 | .where('range').eq(1) 139 | .get(function(err, item) { 140 | if (err) 141 | throw err 142 | 143 | assert.deepEqual(item.empty_string, '', {strict: true } ) 144 | 145 | done() 146 | }) 147 | }) 148 | }) 149 | 150 | 151 | 152 | 153 | it('.replace().then()', function(done) { 154 | DynamoDB 155 | .table($tableName) 156 | .return(DynamoDB.ALL_OLD) 157 | .replace({ 158 | hash: 'hash1', 159 | range: 1, 160 | key: 'promise1' 161 | }) 162 | .then(function(data) { 163 | done() 164 | }) 165 | }) 166 | it('.replace() - unhandled', function(done) { 167 | DynamoDB 168 | .table($tableName) 169 | .return(DynamoDB.ALL_OLD) 170 | .replace({ 171 | hash: 'hash1', 172 | range: 1, 173 | key: 'promise2' 174 | }) 175 | setTimeout(function() { 176 | done() 177 | },5000) 178 | }) 179 | it('.replace().catch()', function(done) { 180 | DynamoDB 181 | .table($tableName) 182 | .replace({ 183 | hash: 'promise', 184 | range: 1, 185 | }) 186 | .catch(function(err) { 187 | done() 188 | }) 189 | }) 190 | it('.replace().then(,errorHandler)', function(done) { 191 | DynamoDB 192 | .table($tableName) 193 | .replace({ 194 | hash: 'promise', 195 | range: 1, 196 | }) 197 | .then( null, function(err) { 198 | done() 199 | }) 200 | }) 201 | it('cleanup...', function(done) { 202 | DynamoDB 203 | .table($tableName) 204 | .scan(function(err, data) { 205 | if (err) 206 | throw err 207 | else { 208 | async.each(data, function(item,cb) { 209 | DynamoDB.table($tableName).where('hash').eq(item.hash).where('range').eq(item.range).delete(function(err) { cb(err) }) 210 | }, function(err) { 211 | if (err) 212 | throw err 213 | else 214 | done() 215 | }) 216 | } 217 | }) 218 | }) 219 | }) 220 | -------------------------------------------------------------------------------- /test/tests/060-delete.js: -------------------------------------------------------------------------------- 1 | 2 | describe('delete()', function () { 3 | it('should fail if wrong type for HASH', function(done) { 4 | DynamoDB 5 | .table($tableName) 6 | .where('hash').eq(1) 7 | .where('range').eq(1) 8 | .delete(function(err, data) { 9 | if (err) 10 | done() 11 | else 12 | throw {error: 'should fail'} 13 | }) 14 | }) 15 | it('should fail if wrong type for RANGE', function(done) { 16 | DynamoDB 17 | .table($tableName) 18 | .where('hash').eq('hash') 19 | .where('range').eq('range') 20 | .delete(function(err, data) { 21 | if (err) 22 | done() 23 | else 24 | throw {error: 'should fail'} 25 | }) 26 | }) 27 | it('should deleate existing item and return it', function(done) { 28 | // insert 29 | DynamoDB.table($tableName).return(DynamoDB.ALL_OLD).insert({hash: 'hash1',range:1, old_number: 1, old_array: [1,2,3], string: 'aaa', 'ignore': 'me', 'delete': 'me'}, function(err, item ) { 30 | if (err) 31 | throw err 32 | 33 | DynamoDB 34 | .table($tableName) 35 | .where('hash').eq('hash1') 36 | .where('range').eq(1) 37 | .return(DynamoDB.ALL_OLD) 38 | .delete(function(err, data ) { 39 | if (err) throw err 40 | 41 | assert.deepEqual(data, {hash: 'hash1',range:1, old_number: 1, old_array: [1,2,3], string: 'aaa', 'ignore': 'me', 'delete': 'me'}) 42 | done() 43 | }) 44 | }) 45 | }) 46 | it('.delete().then()', function(done) { 47 | DynamoDB.table($tableName).return(DynamoDB.ALL_OLD).insert({hash: 'promise',range:1, }).then(function() { 48 | DynamoDB 49 | .table($tableName) 50 | .where('hash').eq('promise') 51 | .where('range').eq(1) 52 | .delete() 53 | .then(function(data) { 54 | done() 55 | }) 56 | }) 57 | }) 58 | 59 | // causes UnhandledPromiseRejectionWarning 60 | // it('.delete() - unhandled', function(done) { 61 | // DynamoDB 62 | // .table($tableName) 63 | // .where('hash').eq(1) 64 | // .where('range').eq(1) 65 | // .delete() 66 | // setTimeout(function() { 67 | // done() 68 | // },5000) 69 | // }) 70 | 71 | it('.delete().catch()', function(done) { 72 | DynamoDB 73 | .table($tableName) 74 | .where('hash').eq(1) 75 | .where('range').eq(1) 76 | .delete() 77 | .catch(function(err) { 78 | done() 79 | }) 80 | }) 81 | it('.delete().then(,errorHandler)', function(done) { 82 | DynamoDB 83 | .table($tableName) 84 | .where('hash').eq(1) 85 | .where('range').eq(1) 86 | .delete() 87 | .then(null,function(err) { 88 | done() 89 | }) 90 | }) 91 | it('cleanup...', function(done) { 92 | DynamoDB 93 | .table($tableName) 94 | .scan(function(err, data) { 95 | if (err) 96 | throw err 97 | else { 98 | async.each(data, function(item,cb) { 99 | DynamoDB.table($tableName).where('hash').eq(item.hash).where('range').eq(item.range).delete(function(err) { cb(err) }) 100 | }, function(err) { 101 | if (err) 102 | throw err 103 | else 104 | done() 105 | }) 106 | } 107 | }) 108 | }) 109 | }) 110 | -------------------------------------------------------------------------------- /test/tests/070-get.js: -------------------------------------------------------------------------------- 1 | 2 | describe('get()', function (done) { 3 | it('prepare for get()', function(done) { 4 | DynamoDB 5 | .table($tableName) 6 | .insert({ 7 | hash: 'test-get', 8 | range: 1, 9 | number: 1, 10 | delete_me: 'aaa', 11 | gsi_range: 'a', 12 | array: [1,2,3], 13 | object: {aaa:1,bbb:2, ccc:3, ddd: {ddd1: 1}, eee: [1,'eee1']}, 14 | string_set: DynamoDB.stringSet(['aaa','bbb','ccc']), 15 | number_set: DynamoDB.numberSet([111,222,333]), 16 | }, function(err, data) { 17 | if (err) 18 | throw err 19 | 20 | done() 21 | }) 22 | }) 23 | it('.select().get()', function(done) { 24 | DynamoDB 25 | .table($tableName) 26 | .select('range','delete_me','inexistent','object.ccc','object.ddd', 'string_set[0]', 'number_set[0]','array[0]') 27 | .where('hash').eq('test-get') 28 | .where('range').eq(1) 29 | .get( function(err, data) { 30 | if (err) 31 | throw err 32 | 33 | // @todo: incomplete 34 | //console.log(JSON.stringify(data)) 35 | done() 36 | }) 37 | }) 38 | it('.get().then()', function(done) { 39 | DynamoDB 40 | .table($tableName) 41 | .where('hash').eq('test-get') 42 | .where('range').eq(1) 43 | .get() 44 | .then(function(data) { 45 | done() 46 | }) 47 | }) 48 | 49 | // causes UnhandledPromiseRejectionWarning 50 | // it('.get() - unhandled', function(done) { 51 | // DynamoDB 52 | // .table($tableName) 53 | // .where('hash').eq('test-get') 54 | // .where('range_unexistent').eq(1) 55 | // .get() 56 | // setTimeout(function() { 57 | // done() 58 | // },5000) 59 | 60 | // }) 61 | 62 | it('.get().then().catch()', function(done) { 63 | DynamoDB 64 | .table($tableName) 65 | .where('hash').eq('test-get') 66 | .where('range_unexistent').eq(1) 67 | .get() 68 | .catch(function(err) { 69 | done() 70 | }) 71 | }) 72 | it('.get().then(,errorHandler)', function(done) { 73 | DynamoDB 74 | .table($tableName) 75 | .where('hash').eq('test-get') 76 | .where('range_unexistent').eq(1) 77 | .get() 78 | .then( null, function(e) { 79 | done() 80 | }) 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /test/tests/080-query.js: -------------------------------------------------------------------------------- 1 | 2 | describe('query()', function () { 3 | it('prepare data for query', function(done) { 4 | async.parallel([ 5 | function(cb) { 6 | DynamoDB 7 | .table($tableName) 8 | .insert({ 9 | hash: 'query', 10 | range: 1, 11 | number: 1, 12 | string: "one two three", 13 | array: [1,2,3,4], 14 | object: {aaa:1,bbb:2, ccc:3, ddd: {ddd1: 1}, eee: [1,'eee1']}, 15 | string_set: DynamoDB.stringSet(['aaa','bbb','ccc','ddd1']), 16 | number_set: DynamoDB.numberSet([111,222,333]), 17 | empty_string: '', 18 | }, function(err, data) {cb(err)}) 19 | }, 20 | function(cb) { 21 | DynamoDB 22 | .table($tableName) 23 | .insert({ 24 | hash: 'query', 25 | range: 2, 26 | number: 2, 27 | string: "one two three four", 28 | array: [1,2,3,4], 29 | object: {aaa:1,bbb:2, ccc:3, ddd: {ddd1: 1}, eee: [1,'eee2']}, 30 | string_set: DynamoDB.stringSet(['aaa','bbb','ccc']), 31 | number_set: DynamoDB.numberSet([111,222,333]), 32 | }, function(err, data) {cb(err)}) 33 | }, 34 | function(cb) { 35 | DynamoDB 36 | .table($tableName) 37 | .insert({ 38 | hash: 'query', 39 | range: 99, 40 | number: 3, 41 | string: "three four five", 42 | array: [1,2,3,4], 43 | object: {aaa:1,bbb:2, ccc:3, ddd: {ddd1: 1}, eee: [1,'eee1']}, 44 | string_set: DynamoDB.stringSet(['aaa','bbb','ccc','ddd3']), 45 | number_set: DynamoDB.numberSet([111,222,333]), 46 | }, function(err, data) {cb(err)}) 47 | } 48 | ], function(err) { 49 | if (err) 50 | throw err 51 | 52 | done() 53 | }) 54 | }) 55 | 56 | it('.query() test empty string', function(done) { 57 | DynamoDB 58 | .table($tableName) 59 | .where('hash').eq('query') 60 | .where('range').eq(1) 61 | .query( function(err, items, raw ) { 62 | if (err) 63 | throw err 64 | 65 | //console.log(JSON.stringify(raw, null, "\t")) 66 | assert.deepEqual(items[0].empty_string, '', {strict: true } ) 67 | done() 68 | }) 69 | }) 70 | 71 | it('.where(RANGE).le()', function(done) { 72 | DynamoDB 73 | .table($tableName) 74 | .where('hash').eq('query') 75 | .where('range').le(99) 76 | //.on('beforeRequest', function(op, payload) { 77 | // console.log(op, JSON.stringify(payload,null,"\t")) 78 | //}) 79 | .query( function(err, data) { 80 | if (err) 81 | throw err 82 | 83 | if (data.length !== 3) 84 | throw "expected 3 got " + data.length 85 | 86 | done() 87 | }) 88 | }) 89 | it('.where(RANGE).lt()', function(done) { 90 | DynamoDB 91 | .table($tableName) 92 | .where('hash').eq('query') 93 | .where('range').lt(99) 94 | //.on('beforeRequest', function(op, payload) { 95 | // console.log(op, JSON.stringify(payload,null,"\t")) 96 | //}) 97 | .query( function(err, data) { 98 | if (err) 99 | throw err 100 | 101 | if (data.length !== 2) 102 | throw "expected 2 got " + data.length 103 | 104 | done() 105 | }) 106 | }) 107 | it('.where(RANGE).ge()', function(done) { 108 | DynamoDB 109 | .table($tableName) 110 | .where('hash').eq('query') 111 | .where('range').ge(2) 112 | //.on('beforeRequest', function(op, payload) { 113 | // console.log(op, JSON.stringify(payload,null,"\t")) 114 | //}) 115 | .query( function(err, data) { 116 | if (err) 117 | throw err 118 | 119 | if (data.length !== 2) 120 | throw "expected 2 got " + data.length 121 | 122 | done() 123 | }) 124 | }) 125 | it('.where(RANGE).gt()', function(done) { 126 | DynamoDB 127 | .table($tableName) 128 | .where('hash').eq('query') 129 | .where('range').gt(2) 130 | //.on('beforeRequest', function(op, payload) { 131 | // console.log(op, JSON.stringify(payload,null,"\t")) 132 | //}) 133 | .query( function(err, data) { 134 | if (err) 135 | throw err 136 | 137 | if (data.length !== 1) 138 | throw "expected 1 got " + data.length 139 | 140 | done() 141 | }) 142 | }) 143 | it('.where(RANGE).between()', function(done) { 144 | DynamoDB 145 | .table($tableName) 146 | .where('hash').eq('query') 147 | .where('range').between(2,99) 148 | //.on('beforeRequest', function(op, payload) { 149 | // console.log(op, JSON.stringify(payload,null,"\t")) 150 | //}) 151 | .query( function(err, data) { 152 | if (err) 153 | throw err 154 | 155 | if (data.length !== 2) 156 | throw "expected 2 got " + data.length 157 | 158 | done() 159 | }) 160 | }) 161 | /* No begins with for type N 162 | it('.where(RANGE).begins_with()', function(done) { 163 | DynamoDB 164 | .table($tableName) 165 | .where('hash').eq('query') 166 | .where('range').begins_with(1) 167 | .on('beforeRequest', function(op, payload) { 168 | console.log(op, JSON.stringify(payload,null,"\t")) 169 | }) 170 | .query( function(err, data) { 171 | if (err) 172 | throw err 173 | 174 | if (data.length !== 2) 175 | throw "expected 2 got " + data.length 176 | 177 | done() 178 | }) 179 | }) 180 | */ 181 | 182 | it('.select().where().having().query()', function(done) { 183 | DynamoDB 184 | .table($tableName) 185 | .select('hash','range','number','object.ccc','object.ddd', 'object.eee','string_set[0]', 'number_set[0]','array[0]','array[1]','array[2]') 186 | .where('hash').eq('query') 187 | .where('range').gt(0) 188 | .having('object.ccc').eq(3) 189 | //.having('number').eq(1) 190 | .having('number').ne(99) 191 | .having('array[1]').between(0,2) 192 | .having('array[2]').in([3,4,'a']) 193 | .having('object.eee').not_null() 194 | .having('object.fff').null() 195 | .having('object.eee[1]').begins_with('eee') 196 | .having('object.eee[1]').contains('eee') 197 | .having('string_set').contains('aaa') 198 | .having('string_set').not_contains('ddd1') 199 | //.on('beforeRequest', function(op, payload) { 200 | // console.log(op, JSON.stringify(payload,null,"\t")) 201 | //}) 202 | .query( function(err, data) { 203 | if (err) 204 | throw err 205 | 206 | // @todo: check returned value 207 | done() 208 | }) 209 | }) 210 | // having in string 211 | it('.select().where().having(string).contains().not_contains().query()', function(done) { 212 | DynamoDB 213 | .table($tableName) 214 | .select('number','string') 215 | .where('hash').eq('query') 216 | .having('string').contains('one') 217 | .having('string').contains('two') 218 | .having('string').not_contains('four') 219 | //.on('beforeRequest', function(op, payload) { 220 | // console.log(op, JSON.stringify(payload,null,"\t")) 221 | //}) 222 | .query( function(err, data) { 223 | if (err) 224 | throw err 225 | 226 | //console.log(JSON.stringify(data)) 227 | // @todo: check returned value 228 | done() 229 | }) 230 | }) 231 | // having in stringset 232 | it('.select().where().having(stringset).contains().not_contains().query()', function(done) { 233 | DynamoDB 234 | .table($tableName) 235 | .select('string_set') 236 | .where('hash').eq('query') 237 | .having('string_set').contains('aaa') 238 | .having('string_set').contains('bbb') 239 | .having('string_set').not_contains('ddd1') 240 | //.on('beforeRequest', function(op, payload) { 241 | // console.log(op, JSON.stringify(payload,null,"\t")) 242 | //}) 243 | .query( function(err, data) { 244 | if (err) 245 | throw err 246 | 247 | //console.log(JSON.stringify(data)) 248 | // @todo: check returned value 249 | done() 250 | }) 251 | }) 252 | 253 | 254 | 255 | it('.select( array )', function(done) { 256 | DynamoDB 257 | .table($tableName) 258 | .select([ 259 | 'number', 260 | 'array[0]', 261 | 'array[2]', 262 | 263 | 'object.ccc', 264 | 'object.ddd', 265 | 'object.eee', 266 | 'string_set[0]', 267 | 'number_set[0]' 268 | 269 | ]) 270 | .where('hash').eq('query') 271 | .where('range').gt(0) 272 | .limit(1) 273 | .query( function(err, data) { 274 | if (err) 275 | throw err 276 | 277 | assert.deepEqual(data[0].number, 1, {strict: true }) 278 | assert.deepEqual(data[0].array, [1,3], {strict: true }) 279 | 280 | assert.deepEqual(Object.keys(data[0].object).length, 3, {strict: true }) 281 | assert.deepEqual(data[0].object.ccc, 3, {strict: true }) 282 | assert.deepEqual(data[0].object.ddd, {"ddd1": 1}, {strict: true }) 283 | assert.deepEqual(data[0].object.eee, [ 1, "eee1" ], {strict: true }) 284 | 285 | // @todo: check returned value 286 | done() 287 | }) 288 | }) 289 | 290 | 291 | it('.query().then()', function(done) { 292 | DynamoDB 293 | .table($tableName) 294 | .where('hash').eq('query') 295 | .query() 296 | .then(function(data) { 297 | done() 298 | }) 299 | }) 300 | it('.query().then() - unhandled', function(done) { 301 | DynamoDB 302 | .table($tableName) 303 | .where('hash').eq('query') 304 | .where('range').le(99) 305 | .query() 306 | setTimeout(function() { 307 | done() 308 | },5000) 309 | }) 310 | it('.query().then().catch()', function(done) { 311 | DynamoDB 312 | .table($tableName) 313 | .where('unexistent_hash').eq('query') 314 | .query() 315 | .catch(function(err) { 316 | done() 317 | }) 318 | }) 319 | it('.query().then(,errorHandler)', function(done) { 320 | DynamoDB 321 | .table($tableName) 322 | .where('unexistent_hash').eq('query') 323 | .query() 324 | .then(null,function(err) { 325 | done() 326 | }) 327 | }) 328 | 329 | }) 330 | -------------------------------------------------------------------------------- /test/tests/090-scan.js: -------------------------------------------------------------------------------- 1 | 2 | describe('scan()', function () { 3 | it('should fail when table name is wrong', function(done) { 4 | DynamoDB 5 | .table('inexistent-table') 6 | .scan( function(err, data) { 7 | if (err) 8 | done() 9 | else 10 | throw err 11 | }) 12 | }) 13 | it('insert before scan', function(done) { 14 | async.parallel([ 15 | function(cb) { 16 | DynamoDB 17 | .table($tableName) 18 | .insert({ 19 | hash: 'scan1', 20 | range: 1, 21 | number: 1, 22 | delete_me: 'aaa', 23 | gsi_range: 'a', 24 | array: [1,2,3], 25 | object: {aaa:1,bbb:2, ccc:3, ddd: {ddd1: 1}, eee: [1,'eee1']}, 26 | string_set: DynamoDB.stringSet(['aaa','bbb','ccc']), 27 | number_set: DynamoDB.numberSet([111,222,333]), 28 | empty_string: '', 29 | }, function(err) { 30 | cb(err) 31 | }) 32 | }, 33 | 34 | function(cb) { 35 | DynamoDB 36 | .table($tableName) 37 | .insert({ 38 | hash: 'scan2', 39 | range: 99, 40 | number: 3, 41 | string: "three four five", 42 | array: [1,2,3,4], 43 | object: {aaa:1,bbb:2, ccc:3, ddd: {ddd1: 1}, eee: [1,'eee1']}, 44 | string_set: DynamoDB.stringSet(['aaa','bbb','ccc','ddd3']), 45 | number_set: DynamoDB.numberSet([111,222,333]), 46 | }, function(err) { 47 | cb(err) 48 | }) 49 | } 50 | ], function(err) { 51 | if (err) throw err 52 | 53 | done() 54 | }) 55 | }) 56 | it('.select().having().scan()', function(done) { 57 | DynamoDB 58 | .table($tableName) 59 | .select('number','object.ccc','object.ddd', 'object.eee','string_set[0]', 'number_set[0]','array[0]','array[1]','array[2]') 60 | .having('number').ne(99) 61 | //.having('number').eq(1) 62 | .having('array[1]').between(1,3) 63 | .having('array[2]').in([3,4,'a']) 64 | .having('object.eee').not_null() 65 | .having('object.fff').null() 66 | .having('object.eee[1]').begins_with('eee') 67 | .having('object.eee[1]').contains('eee') 68 | .having('string_set').contains('aaa') 69 | .having('string_set').not_contains('ddd1') 70 | .scan( function(err, data) { 71 | if (err) 72 | throw err 73 | 74 | // @todo: check returned data 75 | done() 76 | }) 77 | }) 78 | it('.scan() test empty string', function(done) { 79 | DynamoDB 80 | .table($tableName) 81 | .having('hash').contains('scan1') 82 | .scan( function(err, items, raw ) { 83 | if (err) 84 | throw err 85 | 86 | //console.log(JSON.stringify(raw, null, "\t")) 87 | assert.deepEqual(items[0].empty_string, '', {strict: true } ) 88 | done() 89 | }) 90 | }) 91 | it('.scan().then()', function(done) { 92 | DynamoDB 93 | .table($tableName) 94 | .scan() 95 | .then( function(data) { 96 | done() 97 | }) 98 | }) 99 | 100 | // causes UnhandledPromiseRejectionWarning 101 | // it('.scan().then() - unhandled', function(done) { 102 | // DynamoDB 103 | // .table('inexistent_table') 104 | // .scan() 105 | // setTimeout(function() { 106 | // done() 107 | // },5000) 108 | // }) 109 | 110 | it('.scan().catch()', function(done) { 111 | DynamoDB 112 | .table('inexistent_table') 113 | .scan() 114 | .catch( function(err) { 115 | done() 116 | }) 117 | }) 118 | it('.scan().then(, errorHandler)', function(done) { 119 | DynamoDB 120 | .table('inexistent_table') 121 | .scan() 122 | .then(null, function(err) { 123 | done() 124 | }) 125 | }) 126 | }) 127 | 128 | // @todo: gsi scan 129 | -------------------------------------------------------------------------------- /test/tests/0A0-batch_write.js: -------------------------------------------------------------------------------- 1 | describe('batch().write()', function () { 2 | // it('prepare', function(done) { 3 | // 4 | // DynamoDB 5 | // .table($tableName) 6 | // .insert({ 7 | // hash: 'batch-write1', 8 | // range: 1, 9 | // hello: 'world1', 10 | // }, function(err, data) { 11 | // if (err) 12 | // throw err 13 | // 14 | // done() 15 | // 16 | // }) 17 | // 18 | // 19 | // }) 20 | 21 | 22 | it('.batch().put().write()', function(done) { 23 | var item = { 24 | hash: 'batch-write1', 25 | range: 1, 26 | buffer: new Buffer("test", "utf-8"), 27 | boolean: true, 28 | null: null, 29 | array: [99.9,66.6,33.3,-1], 30 | object: { 31 | number: 1, 32 | string: 'string', 33 | }, 34 | ns1: new Set([1.1,-1.1]), 35 | ns2: DynamoDB.numberSet([1.1,-1.1]), 36 | 37 | ss1: new Set(['aaa','bbb']), 38 | ss2: DynamoDB.stringSet(['aaa','bbb']), 39 | 40 | //bs1: new Set([new Buffer("aaa", "utf-8"),new Buffer("bbb", "utf-8")]), 41 | bs2: DynamoDB.binarySet([new Buffer("aaa", "utf-8"),new Buffer("bbb", "utf-8")]), 42 | 43 | } 44 | DynamoDB 45 | .batch() 46 | .table($tableName) 47 | .put(item) 48 | .write(function( err, data ) { 49 | if (err) 50 | throw err; 51 | 52 | DynamoDB 53 | .table($tableName) 54 | .where('hash').eq('batch-write1') 55 | .where('range').eq(1) 56 | .consistentRead() 57 | .get(function( err, data ) { 58 | if (err) 59 | throw err 60 | 61 | //console.log(JSON.stringify(data,null,"\t")) 62 | 63 | assert.deepEqual(data.hash, item.hash ) 64 | assert.deepEqual(data.range, item.range ) 65 | assert.deepEqual(data.buffer.toString(), 'test' ) 66 | assert.deepEqual(data.boolean, true ) 67 | assert.deepEqual(data.null, null ) 68 | assert.deepEqual(data.array, [99.9,66.6,33.3,-1] ) 69 | assert.deepEqual(data.object, { number: 1, string: 'string', }) 70 | 71 | // @todo: check ss1, ss2, ns1, ns2, bs1, bs2 72 | 73 | done() 74 | }) 75 | 76 | }) 77 | }) 78 | 79 | 80 | 81 | 82 | it('.batch().put().write() - existing item', function(done) { 83 | var item = { 84 | hash: 'batch-write1', 85 | range: 1, 86 | hello: 'world2', 87 | } 88 | DynamoDB 89 | .batch() 90 | .table($tableName) 91 | .put(item) 92 | .write(function( err, data ) { 93 | if (err) 94 | throw err; 95 | 96 | DynamoDB 97 | .table($tableName) 98 | .where('hash').eq('batch-write1') 99 | .where('range').eq(1) 100 | .consistentRead() 101 | .get(function( err, data ) { 102 | if (err) 103 | throw err 104 | 105 | assert.deepEqual(data.hello, 'world2' ) 106 | 107 | done() 108 | }) 109 | 110 | }) 111 | }) 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | it('.batch().del().write()', function(done) { 122 | var item = { 123 | hash: 'batch-write1', 124 | range: 1, 125 | } 126 | DynamoDB 127 | .batch() 128 | .table($tableName) 129 | .del(item) 130 | .write(function( err, data ) { 131 | if (err) 132 | throw err; 133 | 134 | DynamoDB 135 | .table($tableName) 136 | .where('hash').eq('batch-write1') 137 | .where('range').eq(1) 138 | .consistentRead() 139 | .get(function( err, data ) { 140 | if (err) 141 | throw err 142 | 143 | assert.deepEqual(data, {} ) 144 | 145 | done() 146 | }) 147 | 148 | }) 149 | }) 150 | 151 | 152 | it('.batch().del().write() - inexisting item', function(done) { 153 | var item = { 154 | hash: 'batch-write1', 155 | range: 1, 156 | } 157 | DynamoDB 158 | .batch() 159 | .table($tableName) 160 | .del(item) 161 | .write(function( err, data ) { 162 | if (err) 163 | throw err; 164 | 165 | DynamoDB 166 | .table($tableName) 167 | .where('hash').eq('batch-write1') 168 | .where('range').eq(1) 169 | .consistentRead() 170 | .get(function( err, data ) { 171 | if (err) 172 | throw err 173 | 174 | assert.deepEqual(data, {} ) 175 | 176 | done() 177 | }) 178 | 179 | }) 180 | }) 181 | 182 | it('.put();.put();.write()', function(done) { 183 | 184 | var ddb = DynamoDB.batch() 185 | ddb.table($tableName) 186 | ddb.put({hash: 'batch-write2', range: 1,}) 187 | ddb.put({hash: 'batch-write2', range: 2,}) 188 | ddb.put({hash: 'batch-write2', range: 3,}) 189 | ddb.put({hash: 'batch-write2', range: 4,}) 190 | ddb.put({hash: 'batch-write2', range: 5,}) 191 | ddb.put({hash: 'batch-write2', range: 6,}) 192 | ddb.put({hash: 'batch-write2', range: 7,}) 193 | ddb.put({hash: 'batch-write2', range: 8,}) 194 | ddb.put({hash: 'batch-write2', range: 9,}) 195 | ddb.put({hash: 'batch-write2', range: 10,}) 196 | ddb.write(function( err, data ) { 197 | if (err) 198 | throw err; 199 | 200 | 201 | DynamoDB 202 | .table($tableName) 203 | .where('hash').eq('batch-write2') 204 | .query(function( err, data ) { 205 | if (err) 206 | throw err 207 | 208 | assert.deepEqual(data.length, 10 ) 209 | 210 | done() 211 | }) 212 | 213 | }) 214 | }) 215 | 216 | }) 217 | -------------------------------------------------------------------------------- /test/tests/0B0-batch_read.js: -------------------------------------------------------------------------------- 1 | describe('batch().read()', function () { 2 | 3 | 4 | it('.get().get().read()', function(done) { 5 | 6 | DynamoDB 7 | .batch() 8 | .table($tableName) 9 | .get({hash: 'batch-write2', range: 1,}) 10 | .get({hash: 'batch-write2', range: 2,}) 11 | .get({hash: 'batch-write2', range: 3,}) 12 | .get({hash: 'batch-write2', range: 4,}) 13 | .get({hash: 'batch-write2', range: 5,}) 14 | .get({hash: 'batch-write2', range: 6,}) 15 | .get({hash: 'batch-write2', range: 7,}) 16 | .get({hash: 'batch-write2', range: 8,}) 17 | .get({hash: 'batch-write2', range: 9,}) 18 | .get({hash: 'batch-write2', range: 10,}) 19 | .consistent_read() 20 | .read(function( err, data ) { 21 | if (err) 22 | throw err; 23 | 24 | //console.log(JSON.stringify(data,null,"\t")) 25 | assert.deepEqual(data[$tableName].length, 10 ) 26 | 27 | done() 28 | 29 | 30 | }) 31 | }) 32 | 33 | }) 34 | -------------------------------------------------------------------------------- /test/tests/0C0-transact.js: -------------------------------------------------------------------------------- 1 | describe('.transact()', function () { 2 | 3 | it('.transact().table( )', function(done) { 4 | 5 | DynamoDB 6 | .transact() 7 | .table('nonexistent').insert_or_replace({hash: 'h1', range: 1, number: 1}) 8 | .write(function( err, data ) { 9 | //console.log(err) 10 | if (err) 11 | return done() 12 | 13 | throw 'should have failed on inexistent table'; 14 | 15 | }) 16 | }) 17 | 18 | 19 | 20 | it('.transact().insert()', function(done) { 21 | 22 | DynamoDB 23 | .transact() 24 | .table($tableName).insert({hash: 'insert', range: 1, number: 1}) 25 | .table($tableName).insert({hash: 'insert', range: 2, bool: true}) 26 | .table($tableName).insert({hash: 'insert', range: 3, null: null}) 27 | .write(function( err, data ) { 28 | if (err) 29 | throw err; 30 | 31 | //console.log(err,data) 32 | 33 | 34 | DynamoDB 35 | .batch() 36 | .table($tableName) 37 | .get({hash: 'insert', range: 1,}) 38 | .get({hash: 'insert', range: 2,}) 39 | .get({hash: 'insert', range: 3,}) 40 | .consistent_read() 41 | .read(function( err, data ) { 42 | if (err) 43 | throw err; 44 | 45 | //console.log(JSON.stringify(data,null,"\t")) 46 | 47 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 1 })[0] , { 48 | "range": 1, 49 | "number": 1, 50 | "hash": "insert" 51 | }) 52 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 2 })[0] , { 53 | "bool": true, 54 | "range": 2, 55 | "hash": "insert" 56 | }) 57 | 58 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 3 })[0] , { 59 | "null": null, 60 | "range": 3, 61 | "hash": "insert" 62 | }) 63 | 64 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 65 | 66 | }) 67 | 68 | }) 69 | }) 70 | 71 | 72 | 73 | it('.transact().insert_or_replace()', function(done) { 74 | 75 | DynamoDB 76 | .transact() 77 | .table($tableName).insert_or_replace({hash: 'insert_or_replace', range: 1, number: 1, object: { attribute: 99 }, pi: 3.14, "searchme": "Hello World", arr: [true, { number: 99 }] }) 78 | .table($tableName).insert_or_replace({hash: 'insert_or_replace', range: 2, bool: true}) 79 | .table($tableName).insert_or_replace({hash: 'insert', range: 3, bool: true}) 80 | .write(function( err, data ) { 81 | //console.log(err,data) 82 | if (err) 83 | throw err; 84 | 85 | DynamoDB 86 | .batch() 87 | .table($tableName) 88 | .get({hash: 'insert_or_replace', range: 1,}) 89 | .get({hash: 'insert_or_replace', range: 2,}) 90 | .get({hash: 'insert', range: 3,}) 91 | .consistent_read() 92 | .read(function( err, data ) { 93 | if (err) 94 | throw err; 95 | 96 | //console.log(JSON.stringify(data,null,"\t")) 97 | 98 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 1 })[0] , { 99 | hash: "insert_or_replace", 100 | range: 1, 101 | number: 1, 102 | object: { attribute: 99 }, 103 | pi: 3.14, 104 | searchme: "Hello World", 105 | arr: [true, { number: 99 }] 106 | }) 107 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 2 })[0] , { 108 | "bool": true, 109 | "range": 2, 110 | "hash": "insert_or_replace" 111 | }) 112 | 113 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 3 })[0] , { 114 | "bool": true, 115 | "range": 3, 116 | "hash": "insert" 117 | }) 118 | 119 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 120 | 121 | }) 122 | 123 | 124 | 125 | }) 126 | }) 127 | 128 | 129 | it('.transact().if().insert_or_replace()', function(done) { 130 | 131 | DynamoDB 132 | .transact() 133 | .table($tableName) 134 | .if('pi').eq(3.14) 135 | .if('number').eq(1) // number is keyword and will go to ExpressionAttributeNames 136 | 137 | .if('pi').ge(3.14) 138 | .if('pi').gt(3) 139 | .if('pi').le(3.14) 140 | .if('pi').lt(3.15) 141 | //.if('number').ne('z') 142 | .if('pi').ne(null) 143 | .if('pi').between(3.1,3.2) 144 | .if('pi').not().between(5,7) 145 | .if('pi').in( [ 0.9,1.1,3.14, "a", "Z" ] ) 146 | .if('pi').not().in( [ 0.9,1.1, "a", "Z" ] ) 147 | 148 | .if('searchme').contains('ello') 149 | .if('searchme').not().contains('I-no-exist') 150 | .if('searchme').begins_with('Hello') 151 | .if('searchme').not().begins_with('World') 152 | 153 | .if('searchme').exists() 154 | .if('me-no-exist').not().exists() 155 | 156 | .if('object.attribute').eq(99) 157 | .if('arr[1].number').between(98,100) 158 | //.debug() 159 | .insert_or_replace({hash: 'insert_or_replace', range: 1, number: 1}) 160 | // .table($tableName).insert_or_replace({hash: 'insert_or_replace', range: 2, bool: true}) 161 | // .table($tableName).insert_or_replace({hash: 'insert', range: 3, bool: true}) 162 | .write(function( err, data ) { 163 | //console.log(err,data) 164 | if (err) 165 | throw err; 166 | 167 | DynamoDB 168 | .batch() 169 | .table($tableName) 170 | .get({hash: 'insert_or_replace', range: 1,}) 171 | // .get({hash: 'insert_or_replace', range: 2,}) 172 | // .get({hash: 'insert', range: 3,}) 173 | .consistent_read() 174 | .read(function( err, data ) { 175 | if (err) 176 | throw err; 177 | 178 | //console.log(JSON.stringify(data,null,"\t")) 179 | 180 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 1 })[0] , { 181 | "range": 1, 182 | "number": 1, 183 | "hash": "insert_or_replace" 184 | }) 185 | // assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 2 })[0] , { 186 | // "bool": true, 187 | // "range": 2, 188 | // "hash": "insert_or_replace" 189 | // }) 190 | // 191 | // assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 3 })[0] , { 192 | // "bool": true, 193 | // "range": 3, 194 | // "hash": "insert" 195 | // }) 196 | 197 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 198 | 199 | }) 200 | 201 | 202 | 203 | }) 204 | }) 205 | 206 | 207 | 208 | 209 | it('.transact().replace()', function(done) { 210 | 211 | DynamoDB 212 | .transact() 213 | .table($tableName).replace({hash: 'insert', range: 1, status: 'replaced' }) 214 | .table($tableName).replace({hash: 'insert', range: 2, status: 'replaced' }) 215 | .table($tableName).replace({ 216 | hash: 'insert', 217 | range: 3, 218 | status: 'replaced', 219 | insert_number: 99, 220 | // this item is here to be removed later with transaction() insert_or_update() 221 | remove_me_on_insert_or_update: true, 222 | }) 223 | .write(function( err, data ) { 224 | // console.log(err,data) 225 | 226 | if (err) 227 | throw err; 228 | 229 | DynamoDB 230 | .batch() 231 | .table($tableName) 232 | .get({hash: 'insert', range: 1,}) 233 | .get({hash: 'insert', range: 2,}) 234 | .get({hash: 'insert', range: 3,}) 235 | .consistent_read() 236 | .read(function( err, data ) { 237 | if (err) 238 | throw err; 239 | 240 | //console.log(JSON.stringify(data,null,"\t")) 241 | 242 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 1 })[0] , { 243 | hash: "insert", 244 | range: 1, 245 | status: 'replaced' 246 | 247 | }) 248 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 2 })[0] , { 249 | hash: "insert", 250 | range: 2, 251 | status: 'replaced' 252 | 253 | 254 | }) 255 | 256 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 3 })[0] , { 257 | hash: "insert", 258 | range: 3, 259 | status: 'replaced', 260 | insert_number: 99, 261 | remove_me_on_insert_or_update: true, 262 | }) 263 | 264 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 265 | 266 | }) 267 | 268 | }) 269 | }) 270 | 271 | 272 | it('.transact().if().replace()', function(done) { 273 | 274 | DynamoDB 275 | .transact() 276 | .table($tableName) 277 | .if('status').eq('replaced') 278 | .if('range').eq(1) 279 | .if('range').ge(1) 280 | .if('range').gt(0.9) 281 | .if('range').le(1) 282 | .if('range').lt(1.1) 283 | //.if('number').ne('z') 284 | .if('range').ne(null) 285 | .if('range').between(0.9,1.1) 286 | .if('range').not().between(2,7) 287 | .if('range').in( [ 0.9,1,1.1, "a", "Z" ] ) 288 | .if('range').not().in( [ 0.9,1.1, "a", "Z" ] ) 289 | .if('hash').contains('ins') 290 | .if('hash').not().contains('Z') 291 | .if('hash').begins_with('ins') 292 | .if('hash').not().begins_with('nsert') 293 | .replace({hash: 'insert', range: 1, status: 'replaced' }) 294 | 295 | //.table($tableName).replace({hash: 'insert', range: 2, status: 'replaced' }) 296 | //.table($tableName).replace({hash: 'insert', range: 3, status: 'replaced', insert_number: 99 }) 297 | .write(function( err, data ) { 298 | // console.log(err,data) 299 | 300 | if (err) 301 | throw err; 302 | 303 | DynamoDB 304 | .batch() 305 | .table($tableName) 306 | .get({hash: 'insert', range: 1,}) 307 | // .get({hash: 'insert', range: 2,}) 308 | // .get({hash: 'insert', range: 3,}) 309 | .consistent_read() 310 | .read(function( err, data ) { 311 | if (err) 312 | throw err; 313 | 314 | //console.log(JSON.stringify(data,null,"\t")) 315 | 316 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 1 })[0] , { 317 | hash: "insert", 318 | range: 1, 319 | status: 'replaced' 320 | }) 321 | // assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 2 })[0] , { 322 | // hash: "insert", 323 | // range: 2, 324 | // status: 'replaced' 325 | // 326 | // 327 | // }) 328 | // 329 | // assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 3 })[0] , { 330 | // hash: "insert", 331 | // range: 3, 332 | // status: 'replaced', 333 | // insert_number: 99 334 | // }) 335 | 336 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 337 | 338 | }) 339 | 340 | }) 341 | }) 342 | 343 | 344 | it('.transact().if().insert_or_update()', function(done) { 345 | 346 | DynamoDB 347 | .transact() 348 | .table($tableName) 349 | .insert_or_update({ 350 | hash: 'insert_or_update', 351 | range: 1, 352 | status: 'inserted', 353 | list: [1], 354 | map: { b: true }, 355 | remove_me_on_update: true, 356 | }) 357 | .table($tableName) 358 | .insert_or_update({ 359 | hash: 'insert_or_update', 360 | range: 2, 361 | status: 'inserted', 362 | list: [2], 363 | map: { b: true }, 364 | remove_me_on_update: true, 365 | }) 366 | .table($tableName) 367 | 368 | .if('inexisintg_attribute').not().exists() 369 | .if('status').exists() 370 | // .if('status').eq('replaced') 371 | .if('range').eq(3) 372 | .if('range').ge(3) 373 | .if('range').gt(2.9) 374 | .if('range').le(4) 375 | .if('range').lt(3.1) 376 | // .if('number').ne('z') 377 | .if('range').ne(null) 378 | .if('range').between(2.9,3.1) 379 | .if('range').not().between(4,7) 380 | .if('range').in( [ 0.9,3,1.1, "a", "Z" ] ) 381 | .if('range').not().in( [ 0.9,1.1, "a", "Z" ] ) 382 | .if('hash').contains('ins') 383 | .if('hash').not().contains('Z') 384 | .if('hash').begins_with('ins') 385 | .if('hash').not().begins_with('nsert') 386 | .insert_or_update({ 387 | hash: 'insert', 388 | range: 3, 389 | status: 'updated', 390 | list: [3], 391 | map: { b: true }, 392 | remove_me_on_insert_or_update: undefined, 393 | }) 394 | .write(function( err, data ) { 395 | 396 | if (err) 397 | throw err; 398 | 399 | DynamoDB 400 | .batch() 401 | .table($tableName) 402 | .get({hash: 'insert_or_update', range: 1,}) 403 | .get({hash: 'insert_or_update', range: 2,}) 404 | .get({hash: 'insert', range: 3,}) 405 | .consistent_read() 406 | .read(function( err, data ) { 407 | if (err) 408 | throw err; 409 | 410 | //console.log(JSON.stringify(data,null,"\t")) 411 | 412 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 1 })[0] , { 413 | hash: "insert_or_update", 414 | range: 1, 415 | list: [1], 416 | map: {b: true}, 417 | status: "inserted", 418 | remove_me_on_update: true, 419 | }) 420 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 2 })[0] , { 421 | hash: "insert_or_update", 422 | range: 2, 423 | list: [2], 424 | map: {b: true}, 425 | status: "inserted", 426 | remove_me_on_update: true, 427 | }) 428 | 429 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 3 })[0] , { 430 | hash: "insert", 431 | range: 3, 432 | list: [3], 433 | map: {b: true}, 434 | status: "updated", 435 | insert_number: 99, 436 | // remove_me_on_insert_or_update <- should not exist because was removed by setting it to undefined 437 | }) 438 | 439 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 440 | 441 | }) 442 | 443 | }) 444 | }) 445 | 446 | it('.transact().if().update()', function(done) { 447 | 448 | DynamoDB 449 | .transact() 450 | .table($tableName) 451 | .where('hash').eq('insert_or_update') 452 | .where('range').eq(1) 453 | 454 | .if('inexisintg_attribute').not().exists() 455 | .if('status').exists() 456 | .if('status').eq('inserted') 457 | .if('range').eq(1) 458 | .if('range').ge(1) 459 | .if('range').gt(0.9) 460 | .if('range').le(1) 461 | .if('range').lt(1.1) 462 | .if('range').ne(null) 463 | .if('range').between(0.9,1.1) 464 | .if('range').not().between(2,7) 465 | .if('range').in( [ 0.9,1,1.1, "a", "Z" ] ) 466 | .if('range').not().in( [ 0.9,1.1, "a", "Z" ] ) 467 | .if('hash').contains('ins') 468 | .if('hash').not().contains('Z') 469 | .if('hash').begins_with('ins') 470 | .if('hash').not().begins_with('update') 471 | 472 | .update({ 473 | status: 'updated', 474 | remove_me_on_update: undefined, 475 | }) 476 | .table($tableName) 477 | .where('hash').eq('insert_or_update') 478 | .where('range').eq(2) 479 | .update({ 480 | status: 'updated', 481 | remove_me_on_update: undefined, 482 | }) 483 | 484 | .write(function( err, data ) { 485 | 486 | if (err) 487 | throw err; 488 | 489 | DynamoDB 490 | .batch() 491 | .table($tableName) 492 | .get({hash: 'insert_or_update', range: 1,}) 493 | .get({hash: 'insert_or_update', range: 2,}) 494 | //.get({hash: 'insert', range: 3,}) 495 | .consistent_read() 496 | .read(function( err, data ) { 497 | if (err) 498 | throw err; 499 | 500 | //console.log(JSON.stringify(data,null,"\t")) 501 | 502 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 1 })[0] , { 503 | hash: "insert_or_update", 504 | range: 1, 505 | list: [1], 506 | map: {b: true}, 507 | status: "updated", 508 | // remove_me_on_update <- should not exist because was removed by setting it to undefined 509 | }) 510 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 2 })[0] , { 511 | hash: "insert_or_update", 512 | range: 2, 513 | list: [2], 514 | map: {b: true}, 515 | status: "updated", 516 | // remove_me_on_update <- should not exist because was removed by setting it to undefined 517 | }) 518 | 519 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 520 | 521 | }) 522 | 523 | }) 524 | }) 525 | 526 | 527 | it('.transact().if().update() - test for "ExpressionAttributeValues must not be empty" ', function(done) { 528 | 529 | DynamoDB 530 | .transact() 531 | .table($tableName) 532 | .where('hash').eq('insert_or_update') 533 | .where('range').eq(1) 534 | .update({ 535 | list: undefined, 536 | }) 537 | 538 | .write(function( err, data ) { 539 | 540 | if (err) 541 | throw err; 542 | 543 | DynamoDB 544 | .batch() 545 | .table($tableName) 546 | .get({hash: 'insert_or_update', range: 1,}) 547 | .consistent_read() 548 | .read(function( err, data ) { 549 | if (err) 550 | throw err; 551 | 552 | //console.log(JSON.stringify(data,null,"\t")) 553 | 554 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 1 })[0] , { 555 | hash: "insert_or_update", 556 | range: 1, 557 | map: {b: true}, 558 | status: "updated", 559 | //list: <- should not exist because was removed by setting it to undefined 560 | }) 561 | 562 | 563 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 564 | 565 | }) 566 | 567 | }) 568 | }) 569 | 570 | 571 | it('.transact().delete()', function(done) { 572 | 573 | DynamoDB 574 | .transact() 575 | .table($tableName) 576 | .where('hash').eq('insert_or_update') 577 | .where('range').eq(1) 578 | 579 | .if('inexisintg_attribute').not().exists() 580 | .if('status').exists() 581 | .if('status').eq('updated') 582 | .if('range').eq(1) 583 | .if('range').ge(1) 584 | .if('range').gt(0.9) 585 | .if('range').le(1) 586 | .if('range').lt(1.1) 587 | .if('range').ne(null) 588 | .if('range').between(0.9,1.1) 589 | .if('range').not().between(2,7) 590 | .if('range').in( [ 0.9,1,1.1, "a", "Z" ] ) 591 | .if('range').not().in( [ 0.9,1.1, "a", "Z" ] ) 592 | .if('hash').contains('ins') 593 | .if('hash').not().contains('Z') 594 | .if('hash').begins_with('ins') 595 | .if('hash').not().begins_with('update') 596 | 597 | .delete() 598 | .write(function( err, data ) { 599 | 600 | 601 | if (err) 602 | throw err; 603 | 604 | DynamoDB 605 | .table($tableName) 606 | .where('hash').eq('insert_or_update') 607 | .where('range').eq(1) 608 | .consistent_read() 609 | .get(function( err, data ) { 610 | if (err) 611 | throw err; 612 | 613 | // console.log(JSON.stringify(data,null,"\t")) 614 | 615 | assert.deepEqual( data, {}) 616 | 617 | 618 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 619 | 620 | }) 621 | 622 | }) 623 | }) 624 | 625 | 626 | it('.transact().if().insert_or_replace()', function(done) { 627 | 628 | DynamoDB 629 | .transact() 630 | //.table($tableName).insert_or_replace({hash: 'insert_or_replace', range: 1, number: 1}) 631 | //.table($tableName).insert_or_replace({hash: 'insert_or_replace', range: 2, bool: true}) 632 | .table($tableName) 633 | .if('status').eq("updated") 634 | .insert_or_replace({hash: 'insert', range: 3, bool: true}) 635 | .write(function( err, data ) { 636 | //console.log(err,data) 637 | if (err) 638 | throw err; 639 | 640 | done() 641 | }) 642 | }) 643 | 644 | 645 | it('.transact().if().replace()', function(done) { 646 | 647 | DynamoDB 648 | .transact() 649 | .table($tableName).if('status').eq('replaced').replace({hash: 'insert', range: 1, status: 'if_replaced' }) 650 | //.table($tableName).replace({hash: 'insert', range: 2, status: 'replaced' }) 651 | //.table($tableName).replace({hash: 'insert', range: 3, status: 'replaced', insert_number: 99 }) 652 | .write(function( err, data ) { 653 | // console.log(err,data) 654 | 655 | if (err) 656 | throw err; 657 | 658 | DynamoDB 659 | .batch() 660 | .table($tableName) 661 | .get({hash: 'insert', range: 1,}) 662 | .get({hash: 'insert', range: 2,}) 663 | .get({hash: 'insert', range: 3,}) 664 | .consistent_read() 665 | .read(function( err, data ) { 666 | if (err) 667 | throw err; 668 | 669 | //console.log(JSON.stringify(data,null,"\t")) 670 | 671 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 1 })[0] , { 672 | hash: "insert", 673 | range: 1, 674 | status: 'if_replaced' 675 | }) 676 | // assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 2 })[0] , { 677 | // hash: "insert", 678 | // range: 2, 679 | // status: 'replaced' 680 | // 681 | // 682 | // }) 683 | // 684 | // assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 3 })[0] , { 685 | // hash: "insert", 686 | // range: 3, 687 | // status: 'replaced', 688 | // insert_number: 99 689 | // }) 690 | 691 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 692 | 693 | }) 694 | 695 | }) 696 | }) 697 | 698 | it('.transact().if().insert_or_update()', function(done) { 699 | 700 | DynamoDB 701 | .transact() 702 | .table($tableName) 703 | //.if('status').eq('updated') 704 | //.insert_or_update({hash: 'insert_or_update', range: 1, status: 'if_insert_or_update', list: [1], map: { b: true } }) 705 | 706 | .table($tableName) 707 | .if('status').eq('updated') 708 | .insert_or_update({hash: 'insert_or_update', range: 2, status: 'if_insert_or_update', list: [2], map: { b: true } }) 709 | 710 | //.table($tableName).insert_or_update({hash: 'insert', range: 3, status: 'updated' , list: [3], map: { b: true } }) 711 | .write(function( err, data ) { 712 | 713 | if (err) 714 | throw err; 715 | 716 | DynamoDB 717 | .batch() 718 | .table($tableName) 719 | .get({hash: 'insert_or_update', range: 1,}) 720 | .get({hash: 'insert_or_update', range: 2,}) 721 | .get({hash: 'insert', range: 3,}) 722 | .consistent_read() 723 | .read(function( err, data ) { 724 | if (err) 725 | throw err; 726 | 727 | // console.log(JSON.stringify(data,null,"\t")) 728 | 729 | // assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 1 })[0] , { 730 | // hash: "insert_or_update", 731 | // range: 1, 732 | // list: [1], 733 | // map: {b: true}, 734 | // status: "inserted" 735 | // }) 736 | // assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 2 })[0] , { 737 | // hash: "insert_or_update", 738 | // range: 2, 739 | // list: [2], 740 | // map: {b: true}, 741 | // status: "inserted" 742 | // }) 743 | // 744 | // assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 3 })[0] , { 745 | // hash: "insert", 746 | // range: 3, 747 | // list: [3], 748 | // map: {b: true}, 749 | // status: "updated", 750 | // insert_number: 99 751 | // }) 752 | 753 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 754 | 755 | }) 756 | 757 | }) 758 | }) 759 | 760 | 761 | it('.transact().if().update()', function(done) { 762 | 763 | DynamoDB 764 | .transact() 765 | // .table($tableName) 766 | // .where('hash').eq('insert_or_update') 767 | // .where('range').eq(1) 768 | // .update({ status: 'updated', }) 769 | .table($tableName) 770 | .if('status').eq('if_insert_or_update') 771 | .where('hash').eq('insert_or_update') 772 | .where('range').eq(2) 773 | .update({ status: 'if_updated', }) 774 | 775 | .write(function( err, data ) { 776 | 777 | if (err) 778 | throw err; 779 | 780 | DynamoDB 781 | .batch() 782 | .table($tableName) 783 | .get({hash: 'insert_or_update', range: 1,}) 784 | .get({hash: 'insert_or_update', range: 2,}) 785 | //.get({hash: 'insert', range: 3,}) 786 | .consistent_read() 787 | .read(function( err, data ) { 788 | if (err) 789 | throw err; 790 | 791 | //console.log(JSON.stringify(data,null,"\t")) 792 | 793 | // assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 1 })[0] , { 794 | // hash: "insert_or_update", 795 | // range: 1, 796 | // list: [1], 797 | // map: {b: true}, 798 | // status: "updated" 799 | // }) 800 | assert.deepEqual( data[$tableName].filter(function(d) {return d.range === 2 })[0] , { 801 | hash: "insert_or_update", 802 | range: 2, 803 | list: [2], 804 | map: {b: true}, 805 | status: "if_updated" 806 | }) 807 | 808 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 809 | 810 | }) 811 | 812 | }) 813 | }) 814 | 815 | 816 | it('.transact().if().delete()', function(done) { 817 | 818 | DynamoDB 819 | .transact() 820 | .table($tableName) 821 | .if('status').eq('if_updated') 822 | .where('hash').eq('insert_or_update') 823 | .where('range').eq(2) 824 | .delete() 825 | .write(function( err, data ) { 826 | 827 | 828 | if (err) 829 | throw err; 830 | 831 | DynamoDB 832 | .table($tableName) 833 | .where('hash').eq('insert_or_update') 834 | .where('range').eq(2) 835 | .consistent_read() 836 | .get(function( err, data ) { 837 | if (err) 838 | throw err; 839 | 840 | // console.log(JSON.stringify(data,null,"\t")) 841 | 842 | assert.deepEqual( data, {}) 843 | 844 | 845 | setTimeout(done, 4000 ) // try cooldown to prevent Internal Server Error ???? 846 | 847 | }) 848 | 849 | }) 850 | }) 851 | 852 | }) 853 | -------------------------------------------------------------------------------- /test/tests/999-deleteTable.js: -------------------------------------------------------------------------------- 1 | 2 | describe('client.deleteTable', function () { 3 | it('waiting for table to become ACTIVE after index delete', function(done) { 4 | var $existInterval = setInterval(function() { 5 | DynamoDB 6 | .client 7 | .describeTable({ 8 | TableName: $tableName 9 | }, function(err, data) { 10 | if (err) { 11 | throw err 12 | } else { 13 | //process.stdout.write("."); 14 | //console.log(data.Table) 15 | if (data.Table.TableStatus === 'ACTIVE') { 16 | clearInterval($existInterval) 17 | done() 18 | } 19 | } 20 | }) 21 | }, 3000) 22 | }) 23 | it('delete table ' + $tableName, function(done) { 24 | DynamoDB 25 | .client 26 | .describeTable({ 27 | TableName: $tableName 28 | }, function(err, data) { 29 | if (err) { 30 | if (err.code === 'ResourceNotFoundException') 31 | done() 32 | else 33 | throw 'could not describe table' 34 | } else { 35 | DynamoDB 36 | .client 37 | .deleteTable({ 38 | TableName: $tableName 39 | }, function(err, data) { 40 | if (err) 41 | throw 'delete failed' 42 | else 43 | done() 44 | }) 45 | } 46 | }) 47 | }); 48 | it('waiting for table to delete', function(done) { 49 | var $existInterval = setInterval(function() { 50 | DynamoDB 51 | .client 52 | .describeTable({ 53 | TableName: $tableName 54 | }, function(err, data) { 55 | 56 | if (err && err.code === 'ResourceNotFoundException') { 57 | clearInterval($existInterval) 58 | return done() 59 | } 60 | if (err) { 61 | clearInterval($existInterval) 62 | throw err 63 | } 64 | 65 | }) 66 | }, 1000) 67 | }) 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | }) 77 | -------------------------------------------------------------------------------- /webpack.browser.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | const browserConfig = { 5 | node: false, 6 | performance: { 7 | hints: false, 8 | }, 9 | mode: 'production', 10 | // devtool: 11 | target: 'web', 12 | context: path.resolve(__dirname, 'src'), 13 | optimization: { 14 | minimize: true, 15 | // minimizer: [ 16 | // new TerserPlugin({ 17 | // cache: false, 18 | // //test: /\.js(\?.*)?$/i, 19 | // test: /\.min\.js$/ 20 | // }), 21 | // ], 22 | }, 23 | plugins: [ 24 | ], 25 | entry: { 26 | 'dynamodbjs': path.resolve(__dirname, './lib/dynamodb.js'), 27 | 'dynamodbjs.min': path.resolve(__dirname, './lib/dynamodb.js') 28 | }, 29 | output: { 30 | path: path.resolve(__dirname, 'dist'), 31 | filename: '[name].js', 32 | library: '@awspilot/dynamodb', 33 | 34 | 35 | libraryTarget: 'umd', 36 | umdNamedDefine: true, // Important 37 | globalObject: 'this', // default=window 38 | }, 39 | 40 | externals: { 41 | "aws-sdk": { 42 | commonjs: 'aws-sdk', 43 | commonjs2: 'aws-sdk', 44 | root: 'AWS' 45 | }, 46 | }, 47 | resolve: { 48 | fallback: { 49 | fs: false, // do not include a polyfill for fs 50 | tls: false, 51 | net: false, 52 | path: false, 53 | zlib: false, 54 | http: false, 55 | https: false, 56 | stream: false, 57 | crypto: false, 58 | } 59 | }, 60 | module: { 61 | rules: [ 62 | { 63 | test: /\.js$/, 64 | exclude: /node_modules/, 65 | use: [ 66 | { 67 | loader: "ifdef-loader", 68 | options: { 69 | BROWSER: true, 70 | //BUNDLE_AWS_SDK: false, 71 | BUILD_WITH_SQL: true, 72 | } 73 | }, 74 | {loader: 'babel-loader'}, 75 | ] 76 | } 77 | ] 78 | } 79 | } 80 | 81 | module.exports = [ browserConfig ]; 82 | --------------------------------------------------------------------------------