[
  {
    "path": ".eslintignore",
    "content": "externs"
  },
  {
    "path": ".eslintrc",
    "content": "{\n    \"rules\": {\n        \"no-console\": 0,\n        \"linebreak-style\": [\n            2,\n            \"unix\"\n        ],\n        \"semi\": [\n            2,\n            \"never\"\n        ]\n    },\n    \"env\": {\n        \"node\": true\n    },\n    \"extends\": \"eslint:recommended\"\n}"
  },
  {
    "path": ".gitignore",
    "content": "lib-cov\n*.seed\n*.log\n*.csv\n*.dat\n*.out\n*.pid\n*.gz\n\npids\nlogs\nresults\n\nnode_modules\nnpm-debug.log\n.DS_Store\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nsudo: false\nnode_js:\n  - \"6\"\n  - \"8\"\n  - \"10\"\n  - \"11\"\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2013 The Obvious Corporation.\nhttp://obvious.com/\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n\n-------------------------------------------------------------------------\n                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "README.md",
    "content": "# Dynamite [![Build Status](https://travis-ci.org/Medium/dynamite.svg?branch=master)](https://travis-ci.org/Medium/dynamite)\n\nDynamite is a promise-based DynamoDB client. It was created to address performance issues in our previous DynamoDB client. Dynamite will almost always comply with the latest DynamoDB spec on Amazon.\n\n## Installation\n\n    $ npm install dynamite\n\n\n## Running Tests\n\nEnsure that all of the required node modules are installed in the Dynamite directory by first running:\n\n\t$ npm install\n\nThe tests will be run against a `LocalDynamo` service. Currently, there is no way to change the port without modifying the connection code in `test/utils/TestUtils.js`. To run the tests:\n\n\t$ npm test\n\n## Creating a Client\n\n```js\nvar Dynamite = require('dynamite')\n\nvar options = {\n  region: 'us-east-1',\n  accessKeyId: 'xxx',\n  secretAccessKey: 'xxx'\n}\n\nvar client = new Dynamite.Client(options)\n```\n\nOptions requires all of:\n\n* region\n* accessKeyId\n* secretAccessKey\n\nIf a `region` key is not provided in the `options` hash but a `endpoint` key is present, Dynamite will try to infer the region from the `host` key.\n\nOptions can also optionally take a hash with a key `dbClient` which points to an object that implements the AWS SDK interface for node.js.\n\n#### Optional Options Keys\n\n* `sslEnabled`: a boolean to turn ssl on or off for the connection.\n* `endPoint`: the address of the DynamoDB instance to try to communicate with.\n* `retryHandler`: a `function(method, table, response)` that will be triggered if Dynamite needs to retry a command.\n\n### Foreword: Kew and You\n\nAll functions return [Kew](https://github.com/Medium/kew) promises on `execute()`. These functions will all then take the form:\n\n```js\nclient.fn(params)\n  .execute()\n  .then(function(){\n    // handle success\n  })\n  .fail(function(e) {\n    // handle failure\n  })\n  .fin(function() {\n    // when all is said and done\n  })\n```\n\nTherefore, these docs will focus more on function signatures and assume that the developer using those functions will comply with the Kew API in turn.\n\n## Tables\n\n### Creating a Table\n\nTable creation is part of the database's concerns and thus doesn't have its own pretty API built into Dynamite. A snippet successfully creating a table that is compliant with the 2012 DynamoDB spec can be found in `test/utils/TestUtils.js`.\n\n\n### Describing a Table\n\nTables can have descriptions. Retrieve them with:\n\n```js\nclient.describeTable('table-name')\n```\n\n## Conditions\n\nConditions ensure that certain properties of the item are either absent or equal to a certain value before allowing whatever operation to which they were supplied to mutate the item. They become very useful when items should only be updated if they are missing a field or are of the wrong value. There currently exist two kinds of conditions: `expectAttributeEquals` and `expectAttributeAbsent`. Every operation has particular behaviors when conditions are or are not met.\n\nAdding conditions to an operation is fairly trivial:\n\n```js\nvar conditions = client.newConditionBuilder()\n  .expectAttributeEquals('age', 29)\n\nclient.fn('some-table')\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    // handle the operation output\n  })\n```\n\nThere is also a helper method for building conditions from a JSON object.\n\n```js\nvar conditions = client.conditions({age: 29})\nclient.fn('some-table')\n  .withCondition(conditions)\n  .execute()\n```\n\nIf a condition fails, the promise will be rejected with a conditional error,\nwhich you can detect with the `isConditionalError` method\n\n```js\nclient.fn('some-table')\n  .withCondition(client.conditions({age: 29})\n  .execute()\n  .fail(function (e) {\n    if (!client.isConditionalError(e)) {\n      throw new Error('Unexpected age; conditional check failed')\n    } else {\n      throw e\n    }\n  })\n```\n\nCatching all conditional errors is a common idiom, so there is a\n`throwUnlessConditionalError` helper method for this case.\n\n```js\nclient.fn('some-table')\n  .withCondition(client.conditions({age: 29})\n  .execute()\n  .fail(client.throwUnlessConditionalError)\n```\n\n## Getting an Item From a Table\n\n```js\nclient.getItem('user-table')\n  .setHashKey('userId', 'userA')\n  .setRangeKey('column', '@')\n  .execute()\n  .then(function(data) {\n    // data.result: the resulting object\n  })\n```\n\nIf an item does not exist, `data.result` will be `undefined`.\n\n### Getting Select Attributes\n\n```js\nclient.getItem('user-table')\n  .setHashKey('userId', 'userA')\n  .setRangeKey('column', '@')\n  .selectAttributes(['userId', 'column'])\n  .execute()\n  .then(function(data) {\n    // data.result: the resulting object\n    //              only the attributes passed into selectAttributes()\n    //              appear as keys in data.result\n  })\n```\n\n\n\n### Batch Get\n\nThe batch get API allows you to request multiple items with specific primary keys, from different\ntables, in a single fetch.\n\n```js\nclient.newBatchGetBuilder()\n  .requestItems('user', [{'userId': 'userA', 'column': '@'}, {'userId': 'userB', 'column': '@'}])\n  .requestItems('phones', [{'userId': 'userA', 'column': 'phone1'}, {'userId': 'userB', 'column': 'phone1'}])\n  .execute()\n```\n\n`requestItems` can be called multiple times, with a table name and an array of objects representing\nprimary keys, in the form `{hashKey: 123, rangeKey: 456}`.\n\n\n\n## Putting an Item Into a Table\n\nItems are handled as JavaScript Objects by the client. These are then converted into an AWS specific format and sent off. The only accepted types of data that can be stored in DynamoDB are Strings, Numbers, and Sets (Arrays). Sets can contain either only Numbers or Strings.\n\n```js\nclient.putItem('user-table', {\n  userId: 'userA',\n  column: '@',\n  age: 30,\n  company: 'Medium',\n  nickNames: ['Ev', 'Evan'],\n  postIds: [1, 2, 3]\n})\n```\n\n### Overrides\n\nIf an item with the same hash and range keys as the one that is being inserted, the old item will be replaced with the item that is being put in its place.\n\n```js\n// initialData = [{userId: 'userA', column: '@', age: 27]\n\nclient.putItem('user-table', {\n  userId: 'userA',\n  column: '@',\n  height: 72\n})\n```\n\nIf the item above were to be retrieved from the table `user-table`, then age would be undefined and a new key `height` would be available.\n\n### Conditional Writes\n\n#### expectAttributeEquals\n\nThe item will only be replaced if the field `field` in the item is equal to the param `value`. If the item does not exist in the table, or the condition is not met, the request will fail.\n\n#### expectAttributeAbsent\n\nThe item will only be replaced if the field `field` is not set in the item in the table. If the item does not exist in the table, then the item will be written to the table. If the field `field` exists for the item in the table, the request will fail.\n\n\n## Deleting Items From a Table\n\nIf the hash key and range key match an item, it will be deleted. Upon success, the function returns the previous attributes and values of the deleted item.\n\n```js\nclient.deleteItem('user-table')\n  .setHashKey('userId', 'userA')\n  .setRangeKey('column', '@')\n  .execute()\n  .then(function (data) {\n    // data.result will contain the origin item attributes and their corresponding values\n  })\n```\n\n### Conditional Deletes\n\n#### expectAttributeEquals\n\nIf an item does not exist, then the request will fail. Otherwise, if the condition is met, the item will be deleted.\n\n#### expectAttributeAbsent\n\nIf an item does not exist, then the request will fail. Otherwise, if the condition is met, the item will be deleted.\n\n\n## Updating an Item\n\nThere are three methods available to modify columns for items: `putAttribute(field, value)`, `deleteAttribute(field)`, and `addToAttribute(field, value)`.\n\nIf an item does not exist, the update query will create the item and update its attributes accordingly.\n\nIf a value is updated on an attribute that does not exist, the attribute will be added to the item and set to the `value` passed to `putsAttribute(field, value)`. If an attribute does not exist and it's value is incremented, that attribute will be added to the item and it's value will be set to the `value` passed to `addToAttribute(field, value)`. If an attribute is deleted and it does not exist, the operation becomes a nonsense operation and has no effect on the item.\n\nPutting empty attributes causes the whole update query to fail.\n\n```js\n// initialData = [{userId: 'userA', column: '@', age: 27, weight: 180]\n\nclient.newUpdateBuilder('user-table')\n  .setHashKey('userId', 'userA')\n  .setRangeKey('column', '@')\n  .enableUpsert()\n  .putAttribute('age', 30)\n  .addToAttribute('age', 1)\n  .deleteAttribute('weight')\n  .putAttribute('height', 72)\n  .execute()\n  .then(function (data) {\n    // data.result == {userId: 'userA', column: '@', age: 31, height: 72}\n  })\n```\n\n#### Conditional Updates\n\nConditions should be added with `withCondition` before any update commands.\n\n##### expectAttributeEquals\n\nIf the item does not exist, the update query will fail.\n\n##### expectAttributeAbsent\n\nIf the item does not exist, the update query will create the item and update its attributes accordingly.\n\n## Querying a Table\n\nAmazon features [extensive documentation](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html) describing querying and scanning in great detail.\n\nA Query operation searches only primary key attribute values and supports a subset of comparison operators on key attribute values to refine the search process. A query returns all of the item data for the matching primary keys (all of each item's attributes) up to 1 MB of data per query operation. A Query operation always returns results, but can return empty results.\n\nA Query operation seeks the specified composite primary key, or range of keys, until one of the following events occur:\n\n+ The result set is exhausted.\n+ The number of items retrieved reaches the value of the Limit parameter, if specified.\n+ The amount of data retrieved reaches the maximum result set size limit of 1 MB.\n\n### Usage\n\nOur initial data set:\n\n```js\n[\n  {\"postId\": \"post1\", \"column\": \"@\", \"title\": \"This is my post\", \"content\": \"And here is some content!\", \"tags\": ['foo', 'bar']},\n  {\"postId\": \"post1\", \"column\": \"/comment/timestamp/002123\", \"comment\": \"this is slightly later\"},\n  {\"postId\": \"post1\", \"column\": \"/comment/timestamp/010000\", \"comment\": \"where am I?\"},\n  {\"postId\": \"post1\", \"column\": \"/comment/timestamp/001111\", \"comment\": \"HEYYOOOOO\"},\n  {\"postId\": \"post1\", \"column\": \"/comment/timestamp/001112\", \"comment\": \"what's up?\"},\n  {\"postId\": \"post1\", \"column\": \"/canEdit/user/AAA\", \"userId\": \"AAA\"}\n]\n```\n\nQuerying all items whose postId is `post1`:\n\n```js\nclient.newQueryBuilder('comments-table')\n  .setHashKey('postId', 'post1')\n  .execute()\n  .then(function (data) {\n    // data.result is an array of posts whose hash key is `post1`\n  })\n```\n\nThe result of the query will be a DynamoResult object with a `result` property for the result set.\n\nDynamoResult also has two methods:\n\n+ hasNext(): boolean\n\nReturns whether there are remaining results for this query.\n\n+ next(): Promise.&lt;DynamoResult&gt;\n\nExecutes a new query that fetches the next page of results.\n\nThere are also a variety of methods that refine and restrict the returned set of results that operate on the indexed range key, which in our sample case is `column`.\n\n#### getCount()\n\nGet the count of the number of items, not the actual items themselves.\n\n```js\nclient.newQueryBuilder('comments-table')\n  .setHashKey('postId', 'post1')\n  .getCount()\n```\n\n#### scanForward()\n\nDemand that items be returned in ascending ASCII or numerical value. This is the default.\n\n#### scanBackward()\n\nDemand that items be returned in descending ASCII or numerical value.\n\n#### setStartKey(key)\n\nStart the query at a specified hash key. Useful when your request is returned in chunks and subsequent chunks need to be retrieved after the current batch is processed.\n\nWhen partial results are returned, the `LastEvaluatedKey` can be passed in as an argument to `setStartKey()` on the next query to get the next section of results.\n\nIn general, calling setStartKey directly is discouraged in favor of using the `next()` method described above.\n\n#### setLimit(max)\n\nReturn at most `max` items. Note that if the response will be larger than 1mb, then at most 1mb of data is returned, and the next batch of items needs to be queried while specifying that the query start at the `LastEvaluatedKey`. That key is returned with the results of the current query.\n\n#### indexBeginsWith(range_key, key_part)\n\nReturn only items where the range key begins with `key_part`. For instance, retrieve all comments for posts with a, in our case unique, hash key of `post1`.\n\n```js\nclient.newQueryBuilder('comments-table')\n  .setHashKey('postId', 'post1')\n  .indexBeginsWith('column', '/comment/')\n```\n\n#### indexBetween(range_key, key_part_start, key_part_end)\n\nReturn only items whose range key is \"between\" the start and end keys. The range key will be compared to the start and end keys in a lexicographic manner. So 'b' is \"between\" 'a' and 'c'.\n\nRetrieve all comments for posts with the hash key `post1` up until the `009999` timestamp:\n\n```js\nclient.newQueryBuilder('comments-table')\n  .setHashKey('postId', 'post1')\n  .indexBetween('column', '/comment/', '/comment/timestamp/009999')\n```\n\n#### indexLessThan(range_key, value)\n#### indexLessThanEqual(range_key, value)\n#### indexGreaterThan(range_key, value)\n#### indexGreaterThanEqual(range_key, value)\n#### indexEqual(range_key, value)\n\nReturn all items whose range keys comply with the afore-listed operations.\n\n#### selectAttributes(attributes[])\n\nReturned items will be stripped of all attributes except their hash key, range key, and the provided array of strings `attributes`.\n\n## Scanning A Table\n\nAmazon features [extensive documentation](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html) describing querying and scanning in great detail.\n\nA Scan operation examines every item in the table. You can specify filters to apply to the results to refine the values returned to you, after the scan has finished. Amazon DynamoDB puts a 1 MB limit on the scan (the limit applies before the results are filtered). A Scan can result in no table data meeting the filter criteria.\n\nScan supports a specific set of comparison operators. For information about each comparison operator available for scan operations, go to the API entry for Scan in the Amazon DynamoDB API Reference.\n\n### Usage\n\nOur initial data set:\n\n```js\n[\n  {\"userId\": \"c\", \"column\": \"@\", \"post\": \"3\", \"email\": \"1@medium.com\"},\n  {\"userId\": \"b\", \"column\": \"@\", \"post\": \"0\", \"address\": \"800 Market St. SF, CA\"},\n  {\"userId\": \"a\", \"column\": \"@\", \"post\": \"5\", \"email\": \"3@medium\"},\n  {\"userId\": \"d\", \"column\": \"@\", \"post\": \"2\", \"twitter\": \"haha\"},\n  {\"userId\": \"e\", \"column\": \"@\", \"post\": \"2\", \"twitter\": \"hoho\"},\n  {\"userId\": \"f\", \"column\": \"@\", \"post\": \"4\", \"description\": \"Designer\", \"email\": \"h@w.com\"},\n  {\"userId\": \"h\", \"column\": \"@\", \"post\": \"6\", \"tags\": ['foo', 'bar']}\n]\n```\n\nA simple scan looks like this:\n\n```js\nclient.newScanBuilder('user-table')\n  .execute()\n  .then(function (data) {\n    // data.result contains all of the users\n  })\n```\n\nIf your dataset contains more than 1 MB of data, the `data` that is returned will contain a `LastEvaluatedKey` key that will tell you what the last evaluated key for the scan was, so you can start the next `scan` there by passing the `LastEvaluatedKey` to `setStartKey(key)`.\n\n#### .filterAttributeEquals(field, value)\n\nInclude items whose `field` equals `value`.\n\n```js\nclient.newScanBuilder('user-table')\n  .filterAttributeEquals('twitter', 'haha')\n  .execute()\n  .then(function (data) {\n    // data.result #=> [{\"userId\": \"d\", \"column\": \"@\", \"post\": \"2\", \"twitter\": \"haha\"}]\n  })\n```\n\nThe other `filterAttribute*` functions are used in the exact same way.\n\n#### .filterAttributeNotEquals(field, value)\n\nInclude items whose `field` does not equal `value`.\n\n#### .filterAttributeLessThanEqual(field, value)\n\nInclude items whose `field` is less than or equal to `value`.\n\n#### .filterAttributeLessThan(field, value)\n\nInclude items whose `field` is less than `value`.\n\n#### .filterAttributeGreaterThanEqual(field, value)\n\nInclude items whose `field` is greater than or equal to `value`.\n\n#### .filterAttributeGreaterThan(field, value)\n\nInclude items whose `field` is greater than `value`.\n\n#### .filterAttributeNotNull(field)\n\nInclude items whose `field` is not `null`, or doesn't exist.\n\n#### .filterAttributeContains(field, value)\n\nInclude items whose `field` contains `value`.\n\nIf an item's `field` attribute is a string, `filterAttributeContains` will search for `value` in that field's value. If an item's `field` attribute is a set, `filterAttributeContains` will search for `value` in that set.\n\n#### .filterAttributeNotContains(field, value)\n\nInclude items whose `field` does not contain `value`. Essentially the inverse of `filterAttributeContains`.\n\n#### .filterAttributeBeginsWith(field, value)\n\nInclude items whose `field` attribute begins with `value`.\n\n#### .filterAttributeBetween(field, lower, upper)\n\nInclude items whose `field` attribute's value is between `lower` and `upper`, exclusive.\n\n#### .filterAttributeIn(field, array_of_values)\n\nFilter out rows where field is not one of the values in `array_of_values`.\n"
  },
  {
    "path": "dynamite.js",
    "content": "module.exports = {\n  Client: require('./lib/Client'),\n  FakeDynamo: require('./lib/FakeDynamo'),\n  ConditionBuilder: require('./lib/ConditionBuilder')\n}\n"
  },
  {
    "path": "externs/aws-sdk.js",
    "content": "\n// Require an event emitter, because some of these apis return emitters.\nvar EventEmitter = require('events').EventEmitter\n\nvar awsResponse = {\n  CapacityUnits: 0,\n  UnprocessedKeys: []\n}\n\nvar queryResponse = {\n  TableName: null,\n  AttributesToGet: [],\n  Limit: 1,\n  ConsistentRead: true,\n  Count: true,\n  HashKeyValue: {\n    S: '',\n    N: 1,\n    B: 'x',\n    SS: [],\n    NS: [],\n    BS: []\n  },\n  RangeKeyCondition: {\n    AttributeValueList: [],\n    ComparisonOperator: null\n  },\n  ScanIndexForward: true,\n  ExclusiveStartKey: {\n    HashKeyElement: {S: ''},\n    RangeKeyElement: {S: ''}\n  }\n}\n\n/** @constructor */\nfunction DynamoDB() {}\n\nmodule.exports = {\n  /** @constructor */\n  DynamoDB: DynamoDB,\n\n  config: {\n    update: function (options) {}\n  }\n}\n"
  },
  {
    "path": "lib/BatchGetItemBuilder.js",
    "content": "var DynamoRequest = require('./DynamoRequest')\nvar Builder = require('./Builder')\nvar Q = require('kew')\nvar util = require('util')\n\n// TODO(nick): Add an iterative API.\n\n/** @const */\nvar BATCH_LIMIT = 100\n\n/**\n * @param {Object} options\n * @constructor\n * @extends {Builder}\n */\nfunction BatchGetItemBuilder(options) {\n  Builder.call(this, options)\n  this._tableAttributes = {}\n  this._tableConsistentRead = {}\n  this._tableKeys = {}\n}\nutil.inherits(BatchGetItemBuilder, Builder)\n\n\n/**\n * Specify if we should do consistent read on a certain table.\n *\n * @param {string} table The name of the table to configure\n * @param {boolean} consistentRead Indicate if we need to do consistent read on the\n *                  given table.\n * @return {BatchGetItemBuilder}\n */\nBatchGetItemBuilder.prototype.setConsistentReadForTable = function (table, consistentRead) {\n  this._tableConsistentRead[table] = consistentRead\n  return this\n}\n\n\n/**\n * Request items from a certain table.\n *\n * @param {string} table The name of the table to request items from\n * @param {Array.<Object>} keys A set of primary keys to fetch.\n * @return {BatchGetItemBuilder}\n */\nBatchGetItemBuilder.prototype.requestItems = function (table, keys) {\n  if (!this._tableKeys[table]) this._tableKeys[table] = []\n  this._tableKeys[table].push.apply(this._tableKeys[table], keys)\n  return this\n}\n\n\nBatchGetItemBuilder.prototype.execute = function () {\n  var promises = []\n  var count = 0\n  var batchTableKeys = {}\n\n  // Divide the items into batches, BATCH_LIMIT (100) items each.\n  for (var tableName in this._tableKeys) {\n    var keys = this._tableKeys[tableName]\n    batchTableKeys[tableName] = []\n    for (var i = 0; i < keys.length; i++) {\n      if (count === BATCH_LIMIT) {\n        promises.push(this._getAllItems(this._buildDynamoRequest(batchTableKeys)))\n        batchTableKeys = {}\n        batchTableKeys[tableName] = []\n        count = 0\n      }\n      count += 1\n      batchTableKeys[tableName].push(keys[i])\n    }\n  }\n  if (count > 0) promises.push(this._getAllItems(this._buildDynamoRequest(batchTableKeys)))\n  var builder = this\n\n  return Q.all(promises)\n    .then(function (batches) {\n      var all = batches[0]\n      for (var i = 1; i < batches.length; i++) builder._mergeTwoBatches(all, batches[i])\n      return all\n    })\n    .then(this.prepareOutput.bind(this))\n    .fail(this.emptyResults)\n}\n\n\n/**\n * Return an object that represents the request data, for the purpose of\n * logging/debugging.\n *\n * @return {Object} Information about this request\n */\nBatchGetItemBuilder.prototype.toObject = function () {\n  return {\n    options: this._options,\n    attributes: this._tableAttributes,\n    consistent: this._tableConsistentRead,\n    items: this._tableKeys\n  }\n}\n\n\nBatchGetItemBuilder.prototype._buildDynamoRequest = function (keys) {\n  return new DynamoRequest(this.getOptions())\n    .setBatchTableAttributes(this._tablePrefix, this._tableAttributes)\n    .setBatchTableConsistent(this._tablePrefix, this._tableConsistentRead)\n    .setBatchRequestItems(this._tablePrefix, keys)\n    .returnConsumedCapacity()\n    .build()\n}\n\n\n/**\n * Merge two batches of responses into one batch.\n *\n * @param{Object} batch One batch data returned from Dynamo.\n * @param{Object} anotherBatch Another batch data returned from Dynamo\n */\nBatchGetItemBuilder.prototype._mergeTwoBatches = function (batch, anotherBatch) {\n  for (var tableName in anotherBatch.Responses)  {\n    if (!(tableName in batch.Responses)) {\n      batch.Responses[tableName] = {Items: [], ConsumedCapacityUnits: 0}\n    }\n    var items = batch.Responses[tableName]\n    items.push.apply(items, anotherBatch.Responses[tableName])\n    batch.Responses[tableName].ConsumedCapacityUnits +=\n      anotherBatch.Responses[tableName].ConsumedCapacityUnits\n  }\n}\n\n\n/**\n * Get all the items and handle the Dynamo API limit size limit.\n *\n * @param {Object.<string, Array.<Object>>} queryData A map from table name to\n *        requested keys in Dynamo API format.\n * @return {Q.Promise.<Object>} The object is a typical Dynamo response.\n */\nBatchGetItemBuilder.prototype._getAllItems = function (queryData) {\n  var builder = this\n\n  return this.request(\"batchGetItem\", queryData)\n    .then(function (data) {\n      var unprocessedKeys = data.UnprocessedKeys\n      if (unprocessedKeys && Object.keys(unprocessedKeys).length > 0) {\n        // If there are unprocessed keys, keep fetching and append the results to\n        // the current results.\n        return builder._getAllItems(\n          new DynamoRequest(builder.getOptions())\n            .setRequestItems(unprocessedKeys)\n            .returnConsumedCapacity()\n            .build())\n          .then(function (moreData) {\n            builder._mergeTwoBatches(data, moreData)\n            data.UnprocessedKeys = {}\n            return data\n          })\n      } else {\n        return data\n      }\n    })\n    .failBound(this.convertErrors, null, {data: queryData, isWrite: false})\n}\n\n\nmodule.exports = BatchGetItemBuilder\n"
  },
  {
    "path": "lib/Builder.js",
    "content": "var util = require('util')\nvar Q = require('kew')\nvar errors = require('./errors')\nvar DynamoResponse = require('./DynamoResponse')\n\n/**\n * @constructor\n * @param {Object} options\n */\nfunction Builder(options) {\n  this._options = options || {}\n  this._retryHandler = this._options.retryHandler\n}\n\nBuilder.prototype.setHashKey = function (keyName, keyVal) {\n  this._hashKey = {\n    name: keyName,\n    val: keyVal\n  }\n  return this\n}\n\nBuilder.prototype.setRangeKey = function (keyName, keyVal) {\n  this._rangeKey = {\n    name: keyName,\n    val: keyVal\n  }\n  return this\n}\n\nBuilder.prototype.getRetryHandler = function () {\n  return this._retryHandler\n}\n\nBuilder.prototype.setRetryHandler = function (retryHandler) {\n  this._retryHandler = retryHandler\n  return this\n}\n\nBuilder.prototype.getOptions = function () {\n  return this._options\n}\n\nBuilder.prototype.setDatabase = function (db) {\n  this._db = db\n  return this\n}\n\nBuilder.prototype.setConsistent = function (isConsistent) {\n  this._isConsistent = isConsistent\n  return this\n}\n\nBuilder.prototype.consistentRead = function () {\n  return this.setConsistent(true)\n}\n\nBuilder.prototype.getPrefix = function () {\n  return this._tablePrefix\n}\n\nBuilder.prototype.setPrefix = function (prefix) {\n  this._tablePrefix = prefix\n  return this\n}\n\nBuilder.prototype.setTable = function (table) {\n  this._table = table\n  return this\n}\n\nBuilder.prototype.setLimit = function (limit) {\n  this._limit = limit\n  return this\n}\n\nBuilder.prototype.scanForward = function () {\n  this._shouldScanForward = true\n  return this\n}\n\nBuilder.prototype.scanBackward = function () {\n  this._shouldScanForward = false\n  return this\n}\n\nBuilder.prototype.getCount = function () {\n  this._isCount = true\n  return this\n}\n\nBuilder.prototype.withFilter = function (filter) {\n  if (!this._filters) this._filters = []\n  if (filter) this._filters.push(filter)\n  return this\n}\n\nBuilder.prototype.withCondition = function (condition) {\n  if (!this._conditions) this._conditions = []\n  if (condition) this._conditions.push(condition)\n  return this\n}\n\nBuilder.prototype.selectAttributes = function (attributes) {\n  if (!attributes) return this\n  if (!Array.isArray(attributes)) attributes = Array.prototype.slice.call(arguments, 0)\n  this._attributes = attributes\n  return this\n}\n\n/** @this {*} */\nBuilder.prototype.emptyResults = function (e) {\n  if (e.message === 'Requested resource not found') return {results:[]}\n  throw e\n}\n\n/** @this {*} */\nBuilder.prototype.emptyResult = function (e) {\n  if (e.message === 'Requested resource not found') return {results:null}\n  throw e\n}\n\nBuilder.prototype.request = function (method, data) {\n  if (this._options.logQueries) {\n    this.logQuery(method, data)\n  }\n\n  var defer = Q.defer()\n\n  if (!this._db.isFakeDynamo) {\n    delete data._requestBuilder\n  }\n\n  var req = this._db[method](data, defer.makeNodeResolver())\n  var startedAt = Date.now()\n\n  var retryHandler = this.getRetryHandler()\n  var table = this._table\n\n  var processingStartedAt, byteLength, requestLatencyMs\n\n  // FakeDynamo doesn't return a request object\n  if (req && req.on) {\n    req.on('httpDone', function (res) {\n      var now = Date.now()\n      processingStartedAt = now\n      requestLatencyMs = now - startedAt\n      if (res && res.httpResponse) {\n        byteLength = res.httpResponse.headers && res.httpResponse.headers['content-length']\n      }\n    })\n\n    if (retryHandler) {\n      req.on('retry', function (res) {\n        retryHandler(method, table, res)\n      })\n    }\n  }\n\n  return defer.then(function (output) {\n    output.ByteLength = byteLength\n    output.ProcessingStartedAt = processingStartedAt\n    output.RequestLatencyMs = requestLatencyMs\n    return output\n  })\n}\n\nBuilder.prototype.logQuery = function (method, data) {\n  var cyanBold, cyan, reset\n  cyanBold = '\\u001b[1;36m'\n  cyan     = '\\u001b[0;36m'\n  reset    = '\\u001b[0m'\n  console.info(cyanBold + method + cyan)\n  console.info(util.inspect(data, {depth: null}))\n  console.info(reset)\n}\n\nBuilder.prototype.prepareOutput = function (output) {\n  return new DynamoResponse(this._tablePrefix, output, null)\n}\n\nBuilder.prototype.convertErrors = function (context, err) {\n  // Errors in Dynamo response are JSON objects like this:\n  // {\n  //   \"message\":\"Attribute found when none expected.\",\n  //   \"code\":\"ConditionalCheckFailedException\",\n  //   \"name\":\"ConditionalCheckFailedException\",\n  //   \"statusCode\":400,\n  //   \"retryable\":false\n  // }\n  //\n  // More at http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ErrorHandling.html\n  //\n  // To be more reliable, we check both err.name and err.code.\n  // Dynamo doc specifies the value of \"code\", so the error object\n  // should have \"code\" assigned. The node.js SDK assigns \"code\"\n  // to \"name\". \"name\" is the standard attribute of javascript Error\n  // object, so we double-check it.\n  var data = context.data\n  var isWrite = !!context.isWrite\n\n  switch (err.code || err.name) {\n    case 'ConditionalCheckFailedException':\n      throw new errors.ConditionalError(data, err.message, err.requestId)\n    case 'ProvisionedThroughputExceededException':\n      throw new errors.ProvisioningError(data, err.message, isWrite, err.requestId)\n    case 'ValidationException':\n      throw new errors.ValidationError(data, err.message, isWrite, err.requestId)\n    default:\n      throw err\n  }\n}\n\nmodule.exports = Builder\n"
  },
  {
    "path": "lib/Client.js",
    "content": "var assert = require('assert')\nvar AWS = require('aws-sdk')\n\nvar AWSName = require('./common').AWSName\nvar ConditionBuilder = require('./ConditionBuilder')\nvar DeleteItemBuilder = require('./DeleteItemBuilder')\nvar DescribeTableBuilder = require('./DescribeTableBuilder')\nvar BatchGetItemBuilder = require('./BatchGetItemBuilder')\nvar GetItemBuilder = require('./GetItemBuilder')\nvar PutItemBuilder = require('./PutItemBuilder')\nvar QueryBuilder = require('./QueryBuilder')\nvar ScanBuilder = require('./ScanBuilder')\nvar UpdateBuilder = require('./UpdateBuilder')\nvar errors = require('./errors')\n\n/**\n * Creates an instance of Client which can be used to access Dynamo.\n *\n * The must-have information in 'options' are:\n *  - region\n *  - accessKeyId\n *  - secretAccessKey\n *\n * If region does not present, we try to infer it from other keys, e.g., 'host',\n * which is mainly for the backward compatibility with some old code that use\n * Dynamite.\n *\n * Dynamite current supports only the 2011 version of the API. It does not\n * concern the user of Dynamite though, because we do not expose the low level\n * APIs to Dynamite users.\n *\n * @constructor\n * @param {{dbClient:Object, host:string, region:string,\n *          accessKeyId:string, secretAccessKey:string, prefix: string,\n *          logQueries: boolean, retryHandler: Function}}\n *     options map which can be used to either configure accesss to Amazon's DynamoDB\n *     service using a host/region, accessKeyId, and secrectAccessKey or can provide\n *     a dbClient object which implements the interface per the AWS SDK for node.js.\n */\nfunction Client(options) {\n  this._prefix = options.prefix || ''\n\n  this._commonOptions = {\n    // whether to log out queries\n    logQueries: !!options.logQueries\n  }\n  if (options.dbClient) {\n    this.db = options.dbClient\n  } else {\n    if (!('region' in options)) {\n      // If the options do not contain a 'region' key, we will try to refer\n      // it from the values of other keys, e.g., 'host'.\n      //\n      // 'region' is what we need to initialize a database instance in the\n      // AWS SDK.\n      if ('endpoint' in options) {\n        var endpoint = options['endpoint']\n        for (var i = 0; i < AWSName.REGION.length; i++) {\n          if (endpoint.indexOf(AWSName.REGION[i]) >= 0) {\n            options['region'] = AWSName.REGION[i]\n            break\n          }\n        }\n      }\n    }\n\n    options['apiVersion'] = AWSName.API_VERSION_2012\n    AWS.config.update(options)\n    this.db = new AWS.DynamoDB()\n  }\n}\n\nClient.prototype.describeTable = function (table) {\n  return new DescribeTableBuilder(this._commonOptions)\n    .setDatabase(this.db)\n    .setPrefix(this._prefix)\n    .setTable(table)\n}\n\nClient.prototype.newQueryBuilder = function (table) {\n  return new QueryBuilder(this._commonOptions)\n    .setDatabase(this.db)\n    .setPrefix(this._prefix)\n    .setTable(table)\n}\n\nClient.prototype.newBatchGetBuilder = function () {\n  return new BatchGetItemBuilder(this._commonOptions)\n    .setDatabase(this.db)\n    .setPrefix(this._prefix)\n}\n\nClient.prototype.newUpdateBuilder = function (table) {\n  assert.equal(arguments.length, 1, \"newUpdateBuilder(table) only takes table name as an arg\")\n  return new UpdateBuilder(this._commonOptions)\n    .setDatabase(this.db)\n    .setPrefix(this._prefix)\n    .setTable(table)\n}\n\nClient.prototype.newScanBuilder = function (table) {\n  assert.equal(arguments.length, 1, \"newScanBuilder(table) only takes table name as an arg\")\n  return new ScanBuilder(this._commonOptions)\n    .setDatabase(this.db)\n    .setPrefix(this._prefix)\n    .setTable(table)\n}\n\nClient.prototype.getItem = function (table) {\n  assert.equal(arguments.length, 1, \"getItem(table) only takes table name as an arg\")\n  return new GetItemBuilder(this._commonOptions)\n    .setDatabase(this.db)\n    .setPrefix(this._prefix)\n    .setTable(table)\n}\n\nClient.prototype.deleteItem = function (table) {\n  assert.equal(arguments.length, 1, \"deleteItem(table) only takes table name as an arg\")\n  return new DeleteItemBuilder(this._commonOptions)\n    .setDatabase(this.db)\n    .setPrefix(this._prefix)\n    .setTable(table)\n}\n\nClient.prototype.putItem = function (table, item) {\n  assert.equal(arguments.length, 2, \"putItem(table, item) did not have 2 arguments\")\n  return new PutItemBuilder(this._commonOptions)\n    .setDatabase(this.db)\n    .setPrefix(this._prefix)\n    .setTable(table)\n    .setItem(item)\n}\n\nClient.prototype.andConditions = function (conditions) {\n  ConditionBuilder.validateConditions(conditions)\n  return ConditionBuilder.andConditions(conditions)\n}\n\nClient.prototype.orConditions = function (conditions) {\n  return ConditionBuilder.orConditions(conditions)\n}\n\nClient.prototype.notCondition = function (condition) {\n  return ConditionBuilder.notCondition(condition)\n}\n\nClient.prototype.newConditionBuilder = function () {\n  return new ConditionBuilder()\n}\n\n\n/**\n * Returns a condition builder that guarantees that an item matches an expected\n * state. Keys that have null, undefined, or empty string values are expected\n * to not be present in the item being queried.\n *\n * @param {Object} obj A map of keys to verify.\n * @return {ConditionBuilder}\n */\nClient.prototype.conditions = function (obj) {\n  var conditionBuilder = this.newConditionBuilder()\n  for (var key in obj) {\n    if (typeof obj[key] === 'undefined' || obj[key] === null || obj[key] === '') {\n      conditionBuilder.expectAttributeAbsent(key)\n    } else {\n      conditionBuilder.expectAttributeEquals(key, obj[key])\n    }\n  }\n  return conditionBuilder\n}\n\n\n/**\n * Returns true if error is a Dynamo error indicating the table is throttled\n * @param {Error} e\n * @return {boolean}\n */\nClient.isProvisioningError = function (e) {\n  return e instanceof errors.ProvisioningError\n}\n\n\n/**\n * Returns true if error is a Dynamo error indicating the table is throttled\n * @param {Error} e\n * @return {boolean}\n */\nClient.prototype.isProvisioningError = Client.isProvisioningError\n\n\n/**\n * Returns true if error is a Dynamo error indicating a validation error\n * @param {Error} e\n * @return {boolean}\n */\nClient.isValidationError = function (e) {\n  return e instanceof errors.ValidationError\n}\n\n\n/**\n * Returns true if error is a Dynamo error indicating a validation error\n * @param {Error} e\n * @return {boolean}\n */\nClient.prototype.isValidationError = Client.isValidationError\n\n\n/**\n * Returns true if error is a Dynamo error indicating a condition wasn't met.\n * @param {Error} e\n * @return {boolean}\n */\nClient.isConditionalError = function (e) {\n  return e instanceof errors.ConditionalError\n}\n\n\n/**\n * Returns true if error is a Dynamo error indicating a condition wasn't met.\n * @param {Error} e\n * @return {boolean}\n */\nClient.prototype.isConditionalError = Client.isConditionalError\n\n\n/**\n * Returns false if the error indicates a condition failed, otherwise the error\n * is re-thrown.\n * @param {Error} e\n * @return {boolean}\n */\nClient.throwUnlessConditionalError = function (e) {\n  if (!Client.isConditionalError(e)) throw e\n  return false\n}\n\n\n/**\n * Returns false if the error indicates a condition failed, otherwise the error\n * is re-thrown. It is safe to use this without binding it.\n * @param {Error} e\n * @return {boolean}\n */\nClient.prototype.throwUnlessConditionalError = Client.throwUnlessConditionalError\n\nmodule.exports = Client\n"
  },
  {
    "path": "lib/ConditionBuilder.js",
    "content": "var assert = require('assert')\nvar typ = require('typ')\nvar typeUtil = require('./typeUtil')\nvar util = require('util')\n\n/**\n * @constructor\n */\nfunction ConditionBuilder() {\n  this._exprs = []\n}\n\n/**\n * Creates a new Conditional expression with an operator (AND, EQ, etc)\n * @param {Op} op One of the ops defined in the Op enum.\n * @param {Array<ConditionExpr>} args The arguments to the op.\n * @return {ConditionExpr}\n */\nConditionBuilder.prototype._op = function (op, args) {\n  if (!op) throw new Error('Missing op')\n  return new ConditionExprOp(op, args)\n}\n\n/**\n * Creates a new Conditional expression that evaluates to an attribute name\n * @param {string} name An attribute name\n * @return {ConditionExpr}\n */\nConditionBuilder.prototype._attr = function (name) {\n  return new ConditionExprAttr(name)\n}\n\n/**\n * Creates a new Conditional expression that evaluates to a value\n * @param {*} val Any literal value representable in a dynamodb expression\n * @return {ConditionExpr}\n */\nConditionBuilder.prototype._val = function (val) {\n  return new ConditionExprVal(val)\n}\n\n/**\n * Returns this condition builder as a ConditionExpr, the internal representation\n * of a condition.\n * @return {ConditionExpr}\n */\nConditionBuilder.prototype._asExpr = function () {\n  return this._op(Op.AND, this._exprs)\n}\n\n/**\n * @param {Array.<ConditionBuilder>} conditions\n * @return {ConditionBuilder}\n */\nConditionBuilder.andConditions = function (conditions) {\n  ConditionBuilder.validateConditions(conditions)\n  var builder = new ConditionBuilder()\n  builder._exprs.push(builder._op(Op.AND, conditions.map(function (c) {\n    return c._asExpr()\n  })))\n  return builder\n}\n\n/**\n * @param {Array.<ConditionBuilder>} conditions\n * @return {ConditionBuilder}\n */\nConditionBuilder.orConditions = function (conditions) {\n  ConditionBuilder.validateConditions(conditions)\n  var builder = new ConditionBuilder()\n  builder._exprs.push(builder._op('OR', conditions.map(function (c) {\n    return c._asExpr()\n  })))\n  return builder\n}\n\n/**\n * @param {ConditionBuilder} condition\n * @return {ConditionBuilder}\n */\nConditionBuilder.notCondition = function (condition) {\n  var conditions = [condition]\n  ConditionBuilder.validateConditions(conditions)\n  var builder = new ConditionBuilder()\n  builder._exprs.push(builder._op('NOT', [condition._asExpr()]))\n  return builder\n}\n\nConditionBuilder.validateConditions = function (conditions) {\n  assert.ok(Array.isArray(conditions), 'Expected array')\n  for (var i = 0; i < conditions.length; i++) {\n      var condition = conditions[i]\n    assert.ok(condition instanceof ConditionBuilder,\n              'Expected ConditionBuilder')\n  }\n}\n\n/**\n * @param {Object} data\n * @param {string} fieldName The fieldName on `data`\n * @param {Array<ConditionBuilder>} conditions\n * @param {{count: number}} nameMutex\n * @return {ConditionBuilder}\n */\nConditionBuilder.populateExpressionField = function (data, fieldName, conditions, nameMutex) {\n  ConditionBuilder.validateConditions(conditions)\n\n  var junction = ConditionBuilder.andConditions(conditions)\n  junction.assignUniqueNames(nameMutex)\n\n  if (!data.ExpressionAttributeNames) {\n    data.ExpressionAttributeNames = {}\n  }\n  typeUtil.extendAttributeNames(data.ExpressionAttributeNames, junction.buildAttributeNames())\n\n  if (!data.ExpressionAttributeValues) {\n    data.ExpressionAttributeValues = {}\n  }\n  typeUtil.extendAttributeValues(data.ExpressionAttributeValues, junction.buildAttributeValues())\n\n  data[fieldName] = junction.buildExpression()\n  return junction\n}\n\n/**\n * @param {Object} nameMutex\n */\nConditionBuilder.prototype.assignUniqueNames = function (nameMutex) {\n  this._asExpr().assignUniqueNames(nameMutex)\n}\n\n/** @return {Object} */\nConditionBuilder.prototype.buildAttributeNames = function () {\n  var result = []\n  this._asExpr().appendAttributeNames(result)\n  return typeUtil.buildAttributeNames(result)\n}\n\n/** @return {Object} */\nConditionBuilder.prototype.buildAttributeValues = function () {\n  var result = {}\n  this._asExpr().appendAttributeValues(result)\n  return result\n}\n\n/**\n * @return {string} String suitable for FilterExpression and KeyExpression\n * @see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.SpecifyingConditions.html#ConditionExpressionReference.Syntax\n */\nConditionBuilder.prototype.buildExpression = function () {\n  return this._asExpr().buildExpression()\n}\n\n/**\n * Iterate through all the expressions. Intended for FakeDynamo\n * @param {function(Object)} callback\n */\nConditionBuilder.prototype.visitExpressionsPostOrder = function (callback) {\n  this._exprs.forEach(function (expr) {\n    expr.visitPostOrder(callback)\n  })\n}\n\n/**\n * Get the appropriate comparator function to compare the passed in values against\n * @return {function(Object): boolean}\n */\nConditionBuilder.prototype.buildFilterFn = function () {\n  var builder = this\n  return function (item) {\n    return !!builder._asExpr().evaluate(item)\n  }\n}\n\nConditionBuilder.prototype.expectAttributeEqual =\nConditionBuilder.prototype.expectAttributeEquals =\nConditionBuilder.prototype.filterAttributeEquals = function (key, val) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val)) throw new Error(\"Val must be defined\")\n  this._exprs.push(this._op('EQ', [this._attr(key), this._val(val)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeEqualsAttribute = function (key1, key2) {\n  if (typ.isNullish(key1)) throw new Error(\"Key1 must be defined\")\n  if (typ.isNullish(key2)) throw new Error(\"Key2 must be defined\")\n  this._exprs.push(this._op('EQ', [this._attr(key1), this._attr(key2)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeNotEquals = function (key, val) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val)) throw new Error(\"Val must be defined\")\n  this._exprs.push(this._op('NE', [this._attr(key), this._val(val)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeNotEqualsAttribute = function (key1, key2) {\n  if (typ.isNullish(key1)) throw new Error(\"Key1 must be defined\")\n  if (typ.isNullish(key2)) throw new Error(\"Key2 must be defined\")\n  this._exprs.push(this._op('NE', [this._attr(key1), this._attr(key2)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeLessThanEqual = function (key, val) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val)) throw new Error(\"Val must be defined\")\n  this._exprs.push(this._op('LE', [this._attr(key), this._val(val)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeLessThanEqualAttribute = function (key1, key2) {\n  if (typ.isNullish(key1)) throw new Error(\"Key1 must be defined\")\n  if (typ.isNullish(key2)) throw new Error(\"Key2 must be defined\")\n  this._exprs.push(this._op('LE', [this._attr(key1), this._attr(key2)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeLessThan = function (key, val) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val)) throw new Error(\"Val must be defined\")\n  this._exprs.push(this._op('LT', [this._attr(key), this._val(val)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeLessThanAttribute = function (key1, key2) {\n  if (typ.isNullish(key1)) throw new Error(\"Key1 must be defined\")\n  if (typ.isNullish(key2)) throw new Error(\"Key2 must be defined\")\n  this._exprs.push(this._op('LT', [this._attr(key1), this._attr(key2)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeGreaterThanEqual = function (key, val) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val)) throw new Error(\"Val must be defined\")\n  this._exprs.push(this._op('GE', [this._attr(key), this._val(val)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeGreaterThanEqualAttribute = function (key1, key2) {\n  if (typ.isNullish(key1)) throw new Error(\"Key1 must be defined\")\n  if (typ.isNullish(key2)) throw new Error(\"Key2 must be defined\")\n  this._exprs.push(this._op('GE', [this._attr(key1), this._attr(key2)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeGreaterThan = function (key, val) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val)) throw new Error(\"Val must be defined\")\n  this._exprs.push(this._op('GT', [this._attr(key), this._val(val)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeGreaterThanAttribute = function (key1, key2) {\n  if (typ.isNullish(key1)) throw new Error(\"Key1 must be defined\")\n  if (typ.isNullish(key2)) throw new Error(\"Key2 must be defined\")\n  this._exprs.push(this._op('GT', [this._attr(key1), this._attr(key2)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeNotNull = function (key) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  this._exprs.push(this._op('NOT_NULL', [this._attr(key)]))\n  return this\n}\n\nConditionBuilder.prototype.expectAttributeAbsent =\nConditionBuilder.prototype.filterAttributeNull = function (key) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  this._exprs.push(this._op('NULL', [this._attr(key)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeContains = function (key, val) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val)) throw new Error(\"Val must be defined\")\n  this._exprs.push(this._op('CONTAINS', [this._attr(key), this._val(val)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeNotContains = function (key, val) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val)) throw new Error(\"Val must be defined\")\n  this._exprs.push(this._op('NOT_CONTAINS', [this._attr(key), this._val(val)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeBeginsWith = function (key, val) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val)) throw new Error(\"Val must be defined\")\n  this._exprs.push(this._op('BEGINS_WITH', [this._attr(key), this._val(val)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeBetween = function (key, val1, val2) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val1)) throw new Error(\"Val 1 must be defined\")\n  if (typ.isNullish(val2)) throw new Error(\"Val 2 must be defined\")\n  this._exprs.push(this._op('BETWEEN', [this._attr(key), this._val(val1), this._val(val2)]))\n  return this\n}\n\nConditionBuilder.prototype.filterAttributeIn = function (key, vals) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(vals)) throw new Error(\"Vals must be defined\")\n  this._exprs.push(this._op('IN', [this._attr(key)].concat(vals.map(this._val.bind(this)))))\n  return this\n}\n\n/**\n * Represents an abstract conditional expression internally.\n *\n * May contain zero or more arguments, which are also ConditionExprs.\n *\n * @param {Array<ConditionExpr>} args\n * @constructor\n */\nfunction ConditionExpr(args) {\n  this.args = args || []\n  this.uniqueName = ''\n}\n\n/**\n * Iterate through all the expressions\n * @param {function(Object)} callback\n */\nConditionExpr.prototype.visitPostOrder = function (callback) {\n  this.args.forEach(function (arg) {\n    arg.visitPostOrder(callback)\n  })\n  callback(this)\n}\n\n/**\n * @param {Object} nameMutex\n */\nConditionExpr.prototype.assignUniqueNames = function (nameMutex) {\n  if (!nameMutex.count) {\n    nameMutex.count = 1\n  }\n  this.visitPostOrder(function (expr) {\n    expr.uniqueName = 'C' + nameMutex.count++\n  })\n}\n\n/**\n * Appends the attribute names to the given array.\n */\nConditionExpr.prototype.appendAttributeNames = function (result) {\n  this.args.forEach(function (arg) {\n    arg.appendAttributeNames(result)\n  })\n}\n\n/**\n * Appends the attribute values to the given object.\n */\nConditionExpr.prototype.appendAttributeValues = function (result) {\n  this.args.forEach(function (arg) {\n    arg.appendAttributeValues(result)\n  })\n}\n\n/**\n * Builds an expression for consumption by the AWS expression API\n */\nConditionExpr.prototype.buildExpression = function () {\n  throw new Error('Expected buildExpression impl')\n}\n\n/**\n * Client-side evaluation.\n */\nConditionExpr.prototype.evaluate = function () {\n  throw new Error('Expected evaluate impl')\n}\n\n/**\n * Represents a conditional expression with 1 or more arguments.\n * @param {Op} op One of the ops defined in the Op enum.\n * @param {Array<ConditionExpr>} args The arguments to the op.\n * @constructor\n */\nfunction ConditionExprOp(op, args) {\n  ConditionExpr.call(this, args)\n  this.op = op\n}\nutil.inherits(ConditionExprOp, ConditionExpr)\n\n/**\n * Builds an expression for arg i, asserting that the arg exists\n */\nConditionExprOp.prototype.buildArgExpr = function (i) {\n  if (i >= this.args.length) throw new Error('Operator ' + this.op + ' expected arg at position ' + i)\n  return this.args[i].buildExpression()\n}\n\n/** @override */\nConditionExprOp.prototype.buildExpression = function () {\n  var operator = this.op\n  switch (operator) {\n    case Op.NOT:\n      return '(NOT ' + this.buildArgExpr(0) + ')'\n    case Op.AND:\n      if (this.args.length == 0) return ''\n      if (this.args.length == 1) return this.buildArgExpr(0)\n\n      var andExprs = this.args.map(function (arg) {\n        return arg.buildExpression()\n      }, this)\n      return '(' + andExprs.join(' AND ') + ')'\n    case Op.OR:\n      if (this.args.length == 0) return ''\n      if (this.args.length == 1) return this.buildArgExpr(0)\n\n      var orExprs = this.args.map(function (arg) {\n        return arg.buildExpression()\n      }, this)\n      return '(' + orExprs.join(' OR ') + ')'\n    case Op.BEGINS_WITH:\n      return 'begins_with(' + this.buildArgExpr(0) + ', ' + this.buildArgExpr(1) + ')'\n    case Op.EQ:\n      return '(' + this.buildArgExpr(0) + ' = ' + this.buildArgExpr(1) + ')'\n    case Op.NE:\n      return '(' + this.buildArgExpr(0) + ' <> ' + this.buildArgExpr(1) + ')'\n    case Op.LE:\n      return '(' + this.buildArgExpr(0) + ' <= ' + this.buildArgExpr(1) + ')'\n    case Op.LT:\n      return '(' + this.buildArgExpr(0) + ' < ' + this.buildArgExpr(1) + ')'\n    case Op.GE:\n      return '(' + this.buildArgExpr(0) + ' >= ' + this.buildArgExpr(1) + ')'\n    case Op.GT:\n      return '(' + this.buildArgExpr(0) + ' > ' + this.buildArgExpr(1) + ')'\n    case Op.BETWEEN:\n      return '(' + this.buildArgExpr(0) + ' BETWEEN ' + this.buildArgExpr(1) + ' AND ' + this.buildArgExpr(2) + ')'\n    case Op.IN:\n      var values = this.args.slice(1).map(function (arg) {\n        return arg.buildExpression()\n      }, this)\n      return '(' + this.buildArgExpr(0) + ' IN (' + values + '))'\n    case Op.NOT_CONTAINS:\n      return '(attribute_exists(' + this.buildArgExpr(0) + ') AND NOT contains(' + this.buildArgExpr(0) + ', ' + this.buildArgExpr(1) + '))'\n    case Op.CONTAINS:\n      return 'contains(' + this.buildArgExpr(0) + ', ' + this.buildArgExpr(1) + ')'\n    case Op.NULL:\n      return 'attribute_not_exists(' + this.buildArgExpr(0) + ')'\n    case Op.NOT_NULL:\n      return 'attribute_exists(' + this.buildArgExpr(0) + ')'\n    default:\n      throw new Error('Invalid comparison operator \\'' + operator + '\\'')\n  }\n}\n\n/**\n * Evaluate the value for arg i, asserting that the arg exists\n */\nConditionExprOp.prototype.evalArg = function (i, item) {\n  if (i >= this.args.length) throw new Error('Operator ' + this.op + ' expected arg at position ' + i)\n  return this.args[i].evaluate(item)\n}\n\n/**\n * Evaluate the value for arg i, asserting that the arg exists\n */\nConditionExprOp.prototype.evalArgs = function (item) {\n  return this.args.map(function (arg) {\n    return arg.evaluate(item)\n  })\n}\n\n/** @override */\nConditionExprOp.prototype.evaluate = function (item) {\n  var argExprs = this.args\n  var operator = this.op\n  switch (operator) {\n    case Op.NOT:\n      return !this.evalArg(0, item)\n    case Op.AND:\n      return this.evalArgs(item).every(function (val) {\n        return val\n      })\n    case Op.OR:\n      return this.evalArgs(item).some(function (val) {\n        return val\n      })\n    case Op.BEGINS_WITH:\n      return item && this.evalArg(0, item).indexOf(this.evalArg(1, item)) === 0\n    case Op.EQ:\n      return item && this.evalArg(0, item) == this.evalArg(1, item)\n    case Op.NE:\n      return item && this.evalArg(0, item) != this.evalArg(1, item)\n    case Op.LE:\n      return item && this.evalArg(0, item) <= this.evalArg(1, item)\n    case Op.LT:\n      return item && this.evalArg(0, item) < this.evalArg(1, item)\n    case Op.GE:\n      return item && this.evalArg(0, item) >= this.evalArg(1, item)\n    case Op.GT:\n      return this.evalArg(0, item) > this.evalArg(1, item)\n    case Op.BETWEEN:\n      return item && this.evalArg(0, item) >= this.evalArg(1, item) && this.evalArg(0, item) <= this.evalArg(2, item)\n    case Op.IN:\n      return item && this.evalArgs(item).slice(1).indexOf(this.evalArg(0, item)) != -1\n    case Op.NOT_CONTAINS:\n      return item && this.evalArg(0, item).indexOf(this.evalArg(1, item)) == -1\n    case Op.CONTAINS:\n      return item && this.evalArg(0, item).indexOf(this.evalArg(1, item)) != -1\n    case Op.NULL:\n      return !item || !item.hasOwnProperty(argExprs[0].name)\n    case Op.NOT_NULL:\n      return item && item.hasOwnProperty(argExprs[0].name)\n    default:\n      throw new Error('Invalid comparison operator \\'' + operator + '\\'')\n  }\n\n}\n\n/**\n * Represents a value in a conditional expression.\n * @param {*} val Any literal value representable in a dynamodb expression\n * @constructor\n */\nfunction ConditionExprVal(val) {\n  ConditionExpr.call(this, [])\n  this.val = typeUtil.valueToObject(val)\n}\nutil.inherits(ConditionExprVal, ConditionExpr)\n\n/** @override */\nConditionExprVal.prototype.buildExpression = function () {\n  return ':V' + this.uniqueName\n}\n\n/** @override */\nConditionExprVal.prototype.appendAttributeValues = function (result) {\n  result[this.buildExpression()] = this.val\n}\n\n/** @override */\nConditionExprVal.prototype.evaluate = function () {\n  return typeUtil.objectToValue(this.val)\n}\n\n/**\n * Represents an item attribute in a conditional expression.\n * @param {string} name An attribute name\n * @constructor\n */\nfunction ConditionExprAttr(name) {\n  ConditionExpr.call(this, [])\n  this.name = name\n}\nutil.inherits(ConditionExprAttr, ConditionExpr)\n\n/** @override */\nConditionExprAttr.prototype.buildExpression = function () {\n  return typeUtil.getAttributeAlias(this.name)\n}\n\n/** @override */\nConditionExprAttr.prototype.evaluate = function (item) {\n  return item[this.name]\n}\n\n/** @override */\nConditionExprAttr.prototype.appendAttributeNames = function (result) {\n  result.push(this.name)\n}\n\n/**\n * An enum of all the possible Ops in our ConditionExpr syntax tree.\n */\nvar Op = {\n  NOT: 'NOT',\n  AND: 'AND',\n  OR: 'OR',\n  BEGINS_WITH: 'BEGINS_WITH',\n  EQ: 'EQ',\n  NE: 'NE',\n  LE: 'LE',\n  LT: 'LT',\n  GE: 'GE',\n  GT: 'GT',\n  BETWEEN: 'BETWEEN',\n  IN: 'IN',\n  NOT_CONTAINS: 'NOT_CONTAINS',\n  CONTAINS: 'CONTAINS',\n  NULL: 'NULL',\n  NOT_NULL: 'NOT_NULL'\n}\n\nmodule.exports = ConditionBuilder\n"
  },
  {
    "path": "lib/DeleteItemBuilder.js",
    "content": "var DynamoRequest = require('./DynamoRequest')\nvar DynamoResponse = require('./DynamoResponse')\nvar Builder = require('./Builder')\n\n/**\n * @param {Object} options\n * @constructor\n * @extends {Builder}\n */\nfunction DeleteItemBuilder(options) {\n  Builder.call(this, options)\n}\nrequire('util').inherits(DeleteItemBuilder, Builder)\n\n/** @override */\nDeleteItemBuilder.prototype.prepareOutput = function (output) {\n  output.UpdatedAttributes = null\n  return new DynamoResponse(this.getPrefix(), output, null)\n}\n\nDeleteItemBuilder.prototype.execute = function () {\n  var req = new DynamoRequest(this.getOptions())\n    .setTable(this._tablePrefix, this._table)\n    .returnConsumedCapacity()\n    .setHashKey(this._hashKey, true)\n    .setExpected(this._conditions)\n    .setReturnValues('ALL_OLD')\n\n  if (this._rangeKey) req.setRangeKey(this._rangeKey, true)\n\n  var queryData = req.build()\n\n  return this.request(\"deleteItem\", queryData)\n    .then(this.prepareOutput.bind(this))\n    .failBound(this.convertErrors, null, {data: queryData, isWrite: true})\n}\n\nmodule.exports = DeleteItemBuilder\n"
  },
  {
    "path": "lib/DescribeTableBuilder.js",
    "content": "var DynamoRequest = require('./DynamoRequest')\nvar Builder = require('./Builder')\n\n/**\n * @param {Object} options\n * @constructor\n * @extends {Builder}\n */\nfunction DescribeTableBuilder(options) {\n  Builder.call(this, options)\n}\nrequire('util').inherits(DescribeTableBuilder, Builder)\n\nDescribeTableBuilder.prototype.execute = function () {\n  var queryData = new DynamoRequest(this.getOptions())\n    .setTable(this._tablePrefix, this._table)\n    .build()\n\n  return this.request(\"describeTable\", queryData)\n    .failBound(this.convertErrors, null, {data: queryData, isWrite: false})\n    .clearContext()\n}\n\nmodule.exports = DescribeTableBuilder\n"
  },
  {
    "path": "lib/DynamoRequest.js",
    "content": "var ConditionBuilder = require('./ConditionBuilder')\nvar UpdateExpressionBuilder = require('./UpdateExpressionBuilder')\nvar typeUtil = require('./typeUtil')\n\n\n/**\n * @param {Object} options\n * @constructor\n */\nfunction DynamoRequest(options) {\n  this._options = options || {}\n  this.data = {_requestBuilder: this}\n\n  this._nameMutex = {count: 0}\n  this._keyConditionBuilder = null\n  this._filterBuilder = null\n  this._conditionBuilder = null\n  this._updateExpressionBuilder = null\n}\n\nDynamoRequest.prototype.setRequestItems = function (keys) {\n  this.data.RequestItems = keys\n  return this\n}\n\n\nDynamoRequest.prototype.setTable = function (prefix, table) {\n  this.data.TableName = (prefix ? prefix : '') + table\n  return this\n}\n\n/**\n * For putItem requests, the item we want to write to DynamoDB\n * @param {Object} item\n * @return {DynamoRequest}\n */\nDynamoRequest.prototype.setItem = function (item) {\n  if (item) {\n    this.data.Item = typeUtil.packObjectOrArray(item)\n  }\n  return this\n}\n\nDynamoRequest.prototype.setParallelScan = function (segment, totalSegments) {\n  if (typeof segment != 'undefined' && totalSegments) {\n    this.data.Segment = segment\n    this.data.TotalSegments = totalSegments\n  }\n  return this\n}\n\n/**\n * @return {DynamoRequest}\n */\nDynamoRequest.prototype.returnConsumedCapacity = function () {\n  this.data.ReturnConsumedCapacity = 'TOTAL'\n  return this\n}\n\n\n/**\n * @param {!Object} attributeUpdates\n * @return {DynamoRequest}\n */\nDynamoRequest.prototype.setUpdates = function (attributeUpdates) {\n  if (attributeUpdates) {\n    this._updateExpressionBuilder = UpdateExpressionBuilder.populateUpdateExpression(\n        this.data, attributeUpdates, this._nameMutex)\n  }\n  return this\n}\n\nDynamoRequest.prototype.setReturnValues = function (returnValues) {\n  if (returnValues) {\n    this.data.ReturnValues = returnValues\n  }\n  return this\n}\n\n/**\n * @param {Array.<ConditionBuilder>} conditions An array of conditions, possibly null to indicate\n *     no conditions.\n * @return {DynamoRequest}\n */\nDynamoRequest.prototype.setKeyConditions = function (conditions) {\n  if (conditions) {\n    this._keyConditionBuilder = ConditionBuilder.populateExpressionField(\n        this.data, 'KeyConditionExpression', conditions, this._nameMutex)\n  }\n  return this\n}\n\n/**\n * @param {Array.<ConditionBuilder>} conditions An array of conditions, possibly null to indicate\n *     no conditions.\n * @return {DynamoRequest}\n */\nDynamoRequest.prototype.setQueryFilter = function (conditions) {\n  if (conditions) {\n    this._filterBuilder = ConditionBuilder.populateExpressionField(\n        this.data, 'FilterExpression', conditions, this._nameMutex)\n  }\n  return this\n}\n\n/**\n * @param {Array.<ConditionBuilder>} conditions An array of conditions, possibly null to indicate\n *     no conditions.\n * @return {DynamoRequest}\n */\nDynamoRequest.prototype.setScanFilter = function (conditions) {\n  if (conditions) {\n    this._filterBuilder = ConditionBuilder.populateExpressionField(\n        this.data, 'FilterExpression', conditions, this._nameMutex)\n  }\n  return this\n}\n\n/**\n * @param {Array.<ConditionBuilder>} conditions An array of conditions, possibly null to indicate\n *     no conditions.\n * @return {DynamoRequest}\n */\nDynamoRequest.prototype.setExpected = function (conditions) {\n  if (conditions) {\n    this._conditionBuilder = ConditionBuilder.populateExpressionField(\n        this.data, 'ConditionExpression', conditions, this._nameMutex)\n  }\n  return this\n}\n\nDynamoRequest.prototype.setConsistent = function (isConsistent) {\n  this.data.ConsistentRead = !!isConsistent\n  return this\n}\n\n/**\n * For query and scan requests, the number of items to iterate over (before the filter)\n * @param {number} limit\n * @return {DynamoRequest}\n */\nDynamoRequest.prototype.setLimit = function (limit) {\n  if (limit) {\n    this.data.Limit = limit\n  }\n  return this\n}\n\nDynamoRequest.prototype.setHashKey = function (key) {\n  this.data.Key = {}\n  if (!key) throw new Error('A hash key is required')\n\n  this.data.Key[key.name] = typeUtil.valueToObject(key.val)\n\n  return this\n}\n\nDynamoRequest.prototype.setRangeKey = function (key) {\n  if (!this.data.Key) throw new Error('The hash key must be set first')\n  if (!key) throw new Error('A range key is required')\n\n  this.data.Key[key.name] = typeUtil.valueToObject(key.val)\n\n  return this\n}\n\nDynamoRequest.prototype.setStartKey = function (key) {\n  if (key) {\n    this.data.ExclusiveStartKey = typeUtil.packObjectOrArray(key)\n  }\n  return this\n}\n\nDynamoRequest.prototype.selectAttributes = function (attributes) {\n  if (attributes) {\n    if (!this.data.ExpressionAttributeNames) {\n      this.data.ExpressionAttributeNames = {}\n    }\n    typeUtil.extendAttributeNames(this.data.ExpressionAttributeNames, typeUtil.buildAttributeNames(attributes))\n\n    this.data.ProjectionExpression = attributes.map(function (attr) {\n      return typeUtil.getAttributeAlias(attr)\n    }).join(',')\n  }\n  return this\n}\n\nDynamoRequest.prototype.setIndexName = function (indexName) {\n  if(indexName) {\n    this.data.IndexName = indexName\n  }\n  return this\n}\n\n\nDynamoRequest.prototype.setBatchTableAttributes = function (tablePrefix, attributes) {\n  this._setPerTableValue(tablePrefix, attributes, 'AttributesToGet')\n  return this\n}\n\n\nDynamoRequest.prototype.setBatchTableConsistent = function (tablePrefix, isConsistentValues) {\n  this._setPerTableValue(tablePrefix, isConsistentValues, 'ConsistentRead')\n  return this\n}\n\n\nDynamoRequest.prototype._setPerTableValue =  function (tablePrefix, values, propertyName) {\n  if (values) {\n    if (!this.data.RequestItems) this.data.RequestItems = {}\n    for (var key in values) {\n      var tableName = (tablePrefix || '') + key\n      if (!this.data.RequestItems[tableName]) this.data.RequestItems[tableName] = {}\n      this.data.RequestItems[tableName][propertyName] = values[key]\n    }\n  }\n}\n\n\n/**\n * Takes a map items to Dynamo request format. requestItems is an object containing an array\n * of Primary Keys for each table.  For example:\n *\n * { userTable : [{userId: '1234', column: '@'} ... ]}\n */\nDynamoRequest.prototype.setBatchRequestItems = function (tablePrefix, requestItems) {\n  if (!this.data.RequestItems) this.data.RequestItems = {}\n  for (var tableName in requestItems) {\n    if (!Array.isArray(requestItems[tableName])) {\n      throw new Error('RequestedItems not an array, for table=' + tableName)\n    }\n    var tableNameWithPrefix = (tablePrefix || '') + tableName\n    if (!this.data.RequestItems[tableNameWithPrefix]) this.data.RequestItems[tableNameWithPrefix] = {}\n    if (!this.data.RequestItems[tableNameWithPrefix].Keys) this.data.RequestItems[tableNameWithPrefix].Keys = []\n    for (var i = 0; i < requestItems[tableName].length; i++) {\n      var keys = requestItems[tableName][i]\n      var dynamoKeys = {}\n      for (var key in keys) dynamoKeys[key] = typeUtil.valueToObject(keys[key])\n      this.data.RequestItems[tableNameWithPrefix].Keys.push(dynamoKeys)\n    }\n  }\n  return this\n}\n\n\nDynamoRequest.prototype.scanForward = function (isForward) {\n  this.data.ScanIndexForward = typeof isForward === 'undefined' || isForward\n  return this\n}\n\nDynamoRequest.prototype.getCount = function () {\n  this.data.Select = \"COUNT\"\n  return this\n}\n\nDynamoRequest.prototype.build = function () {\n  // Dynamo doesn't like it when alias objects are empty.\n  if (this.data.ExpressionAttributeNames &&\n      !Object.keys(this.data.ExpressionAttributeNames).length) {\n    delete this.data.ExpressionAttributeNames\n  }\n\n  if (this.data.ExpressionAttributeValues &&\n      !Object.keys(this.data.ExpressionAttributeValues).length) {\n    delete this.data.ExpressionAttributeValues\n  }\n\n  // Dynamo doesn't like it when conditions are empty\n  if (!this.data.KeyConditionExpression) {\n    delete this.data.KeyConditionExpression\n  }\n\n  if (!this.data.FilterExpression) {\n    delete this.data.FilterExpression\n  }\n\n  if (!this.data.ConditionExpression) {\n    delete this.data.ConditionExpression\n  }\n\n  if (!this.data.UpdateExpression) {\n    delete this.data.UpdateExpression\n  }\n\n  return this.data\n}\n\nmodule.exports = DynamoRequest\n"
  },
  {
    "path": "lib/DynamoResponse.js",
    "content": "// Copyright 2013. The Obvious Corporation.\n\nvar typeUtil = require('./typeUtil')\n\n/**\n * @param {?string} tablePrefix\n * @param {Object} output Response JSON\n * @param {?function(Object, number?): Promise} repeatWithStartKey Make the same query with a different start key.\n *     Only valid for Query/Scan results.\n * @constructor\n */\nvar DynamoResponse = function (tablePrefix, output, repeatWithStartKey) {\n  /** @private {?function(Object, number?): Promise} */\n  this._repeatWithStartKey = repeatWithStartKey\n\n  this.ConsumedCapacityUnits = output.ConsumedCapacityUnits\n  this.Count = output.Count\n  this.ProcessingStartedAt = output.ProcessingStartedAt\n  this.RequestLatencyMs = output.RequestLatencyMs\n  this.ByteLength = output.ByteLength\n\n  this.LastEvaluatedKey = undefined\n  if (output.LastEvaluatedKey) {\n    this.LastEvaluatedKey = typeUtil.unpackObjectOrArray(output.LastEvaluatedKey)\n  }\n\n  // For batchGet\n  this.UnprocessedKeys = undefined\n\n  var table\n  if (output.UnprocessedKeys) {\n    var unprocessed = {}\n    for (table in output.UnprocessedKeys) {\n      unprocessed[table] = typeUtil.unpackObjectOrArray(output.UnprocessedKeys[table].Keys)\n    }\n    this.UnprocessedKeys = unprocessed\n  }\n\n  this.ConsumedCapacity = undefined\n  if (output.ConsumedCapacity) {\n    if (!Array.isArray(output.ConsumedCapacity)) {\n      output.ConsumedCapacity = [output.ConsumedCapacity]\n    }\n    var capacity = {}\n    output.ConsumedCapacity.forEach(function (outputCapacity) {\n      capacity[getOriginalTableName(tablePrefix, outputCapacity.TableName)] = outputCapacity.CapacityUnits\n    })\n    this.ConsumedCapacity = capacity\n  }\n\n  this.result = undefined\n\n  // for Query and Scan, 'result' is {Array.<Object>}\n  if (output.Items) {\n    this.result = typeUtil.unpackObjectOrArray(output.Items)\n\n  // for GetItem, 'result' is {Object}\n  } else if (output.Item) {\n    this.result = typeUtil.unpackObjectOrArray(output.Item)\n\n  // for DeleteItem, PutItem and UpdateItem, 'result' is {Object}\n  } else if (output.UpdatedAttributes || output.Attributes) {\n    this.result = typeUtil.unpackObjectOrArray(output.UpdatedAttributes)\n    this.previous = typeUtil.unpackObjectOrArray(output.Attributes)\n\n  // for BatchGetItem, 'result' is {Object.<string, Object>}\n  } else if (output.Responses) {\n    this.result = {}\n    for (table in output.Responses) {\n      var origTableName = getOriginalTableName(tablePrefix, table)\n      this.result[origTableName] = typeUtil.unpackObjectOrArray(output.Responses[table])\n    }\n  }\n  return this\n}\n\n/**\n * @return {boolean} If the query or scan has more results.\n */\nDynamoResponse.prototype.hasNext = function () {\n  return !!(this.LastEvaluatedKey && this._repeatWithStartKey)\n}\n\n/**\n * @param {?number} opt_limit The number of items to check\n * @return {Promise.<DynamoResponse>}\n */\nDynamoResponse.prototype.next = function (opt_limit) {\n  if (!this.hasNext()) throw new Error('No more results')\n  return this._repeatWithStartKey(/** @type {Object} */ (this.LastEvaluatedKey), opt_limit)\n}\n\nfunction getOriginalTableName (tablePrefix, tableName) {\n  return tablePrefix ? tableName.substr(tablePrefix.length) : tableName\n}\n\nmodule.exports = DynamoResponse\n"
  },
  {
    "path": "lib/FakeDynamo.js",
    "content": "var Q = require('kew')\nvar typ = require('typ')\nvar typeUtil = require('./typeUtil')\nvar localUpdater = require('./localUpdater')\n\nvar util = require('util')\n\n/** @const */\nvar MAX_GET_BATCH_ITEM_SIZE = 100\n\n/** @const */\nvar MAX_KEY_SIZE = 1024\n\nfunction forEachKeyCondition(data, callback) {\n  return data._requestBuilder._keyConditionBuilder.visitExpressionsPostOrder(function (expr) {\n    if (expr.op && expr.args[0] && expr.args[0].name) {\n      callback(expr, expr.args[0].name)\n    }\n  })\n}\n\nfunction getKeyConditionByName(data, name) {\n  var result = null\n  forEachKeyCondition(data, function (condition, key) {\n    if (key == name) {\n      result = condition\n    }\n  })\n  return result\n}\n\nfunction forEachFilterCondition(data, callback) {\n  if (!data._requestBuilder._filterBuilder) return\n\n  return data._requestBuilder._filterBuilder.visitExpressionsPostOrder(function (expr) {\n    if (expr.op && expr.args[0] && expr.args[0].name) {\n      callback(expr, expr.args[0].name)\n    }\n  })\n}\n\nfunction getKeyConditionFn(data) {\n  return data._requestBuilder._keyConditionBuilder.buildFilterFn()\n}\n\nfunction getFilterFn(data) {\n  var filterBuilder = data._requestBuilder._filterBuilder\n  return filterBuilder ? filterBuilder.buildFilterFn() : function() { return true }\n}\n\nfunction getConditionFn(data) {\n  var builder = data._requestBuilder._conditionBuilder\n  return builder ? builder.buildFilterFn() : function() { return true }\n}\n\n/**\n * @param {string} name\n * @param {string} message\n * @constructor\n * @extends {Error}\n */\nfunction DynamoError(name, message) {\n  this.name = name\n  this.code = name\n  this.message = message\n}\nutil.inherits(DynamoError, Error)\n\n/**\n * Fake table which can be used to store mock data and run queries\n *\n * @constructor\n * @param {string} name table name\n */\nfunction FakeTable(name) {\n  this.name = name\n  this.primaryKey = {\n    hash: null,\n    range: null\n  }\n\n  this.gsiDefinitions = []\n\n  this.data = {}\n\n  /** @private {number} */\n  this._maxResultSetSize = MAX_GET_BATCH_ITEM_SIZE\n}\n\n/**\n * Set a hard upper limit at the amount of results that queries and scans on this table can return.\n * @param {number} maxResultSetSize\n */\nFakeTable.prototype.setMaxResultSetSize = function (maxResultSetSize) {\n  this._maxResultSetSize = maxResultSetSize\n}\n\n/**\n * Set data for the mock table\n *\n * @param {!Object} data\n * @return {FakeTable} the mock table instance\n */\nFakeTable.prototype.setData = function(data) {\n  this.data = data\n  return this\n}\n\n/**\n * Get all data from the mock table\n *\n * @return {Object} data\n */\nFakeTable.prototype.getData = function() {\n  return this.data\n}\n\n/**\n * Set the hash attribute from the primary key for this table\n *\n * @param {string} name the name of the attribute\n * @param {string} type the type of the attribute ('S', 'N', etc.)\n * @return {FakeTable} the current FakeTable instance\n */\nFakeTable.prototype.setHashKey = function(name, type) {\n  this.primaryKey.hash = {\n    name: name,\n    type: type\n  }\n\n  return this\n}\n\n/**\n * Set the range attribute from the primary key for this table\n *\n * @param {string} name the name of the attribute\n * @param {string} type the type of the attribute ('S', 'N', etc.)\n * @return {FakeTable} the current FakeTable instance\n */\nFakeTable.prototype.setRangeKey = function(name, type) {\n  this.primaryKey.range = {\n    name: name,\n    type: type\n  }\n\n  return this\n}\n\n/**\n * Set GSI definitions for this table.\n *\n * @param {Array.<{hash:({name:(string), type:(string)}), ?range:({name:(string), type:(string)})}>} gsiDefinitions\n * @return {FakeTable} the current FakeTable instance\n */\nFakeTable.prototype.setGsiDefinitions = function(gsiDefinitions) {\n  this.gsiDefinitions = gsiDefinitions\n\n  return this\n}\n\n/**\n * Run a query against the mock data\n *\n * @param {Object} data\n * @return {Q.Promise}\n */\nFakeTable.prototype.query = function(data) {\n  var indexedKeyName\n  if (data.IndexName) {\n    // Global Secondary Index if the index name has three or more\n    // parts (separated by '-')\n    var indexParts = data.IndexName.split('-')\n    var probableGSIIndex = indexParts.length >= 3\n    if (probableGSIIndex) {\n      return this._queryGlobalSecondaryIndex(data)\n    }\n\n    // Extract the range key for Local Secondary Indexes\n    forEachKeyCondition(data, function (condition, key) {\n      if (key !== this.primaryKey.hash.name) {\n        indexedKeyName = key\n      }\n    }.bind(this))\n  } else { // no index specified, mock query original table\n    indexedKeyName = this.primaryKey.range.name\n  }\n\n  this._validateFilterKeys(data, this.primaryKey.hash.name, indexedKeyName)\n\n  // retrieve the hashkey\n  var hash = data.HashKeyValue ||\n             getKeyConditionByName(data, this.primaryKey.hash.name).args[1].evaluate()\n\n  var indexedKeyValues = data.IndexName ? this._getIndexedKeyValuesForHash(hash, indexedKeyName) : this._getRangeKeyValuesForHash(hash)\n\n  // maybe add some stuff for secondary indices like\n  // http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html\n  // var indexName = data.IndexName || 'primary'\n\n  var keyConditionFn = getKeyConditionFn(data)\n  var queryFilterFn = getFilterFn(data)\n\n  var results = []\n  var isBackward = data.ScanIndexForward === false\n  for (var i = 0; i < indexedKeyValues.length; i++) {\n    var j = isBackward ? indexedKeyValues.length - 1 - i : i\n    var currentRows\n    if (data.IndexName) {\n      currentRows = this._getItemsByIndex({\n        hash: hash,\n        indexedKey: indexedKeyValues[j]\n      }, indexedKeyName)\n    } else {\n      currentRows = [this._getItemByKey({\n        hash: hash,\n        range: indexedKeyValues[j]\n      })]\n    }\n\n    currentRows.forEach(function (currentRow) {\n      if (keyConditionFn(currentRow) && queryFilterFn(currentRow)) {\n        // TODO(artem): if no range key is specified, then all of the items\n        // with the same hash key need to be returned\n        results.push(currentRow)\n      }\n    })\n  }\n\n  return this._formatPagedResults(results, data)\n}\n\n/**\n * Query the mock data as if it respects Global Secondary Index.\n *\n * It does not matter, for the purposes of FakeDynamo, which KeyCondition is\n * the hash key or range key. We simply do a scan to find all matches for both\n * conditions.\n */\nFakeTable.prototype._queryGlobalSecondaryIndex = function(data) {\n  var indexParts = data.IndexName.split('-')\n  // Try to find a GSI that matches both hash and range keys\n  var pickedGsi = this.gsiDefinitions.find(function (gsi) {\n      return gsi.range && indexParts.indexOf(gsi.hash.name) >= 0 &&\n        indexParts.indexOf(gsi.range.name) >= 0\n  })\n\n  // If not found, try to find a GSI that only matches the hash key\n  if (!pickedGsi) {\n    pickedGsi = this.gsiDefinitions.find(function (gsi) {\n      return indexParts.indexOf(gsi.hash.name) >= 0\n    })\n  }\n\n  if (!pickedGsi) {\n    throw new Error('No gsi found for ' + data.IndexName)\n  }\n\n  var hashKey = pickedGsi.hash.name\n  var rangeKey = pickedGsi.range && pickedGsi.range.name\n  this._validateFilterKeys(data, hashKey, rangeKey)\n\n  // store keys, values, and conditions from data for easy access in scan\n  var keyConditionFn = getKeyConditionFn(data)\n  var queryFilterFn = getFilterFn(data)\n\n  var results = []\n  for (var primary_key in this.data) {\n    for (var range_key in this.data[primary_key]) {\n      var currentRow = this.data[primary_key][range_key]\n\n      if (keyConditionFn(currentRow) && queryFilterFn(currentRow)) {\n        results.push(currentRow)\n      }\n    }\n  }\n\n  // sort by the range key of the GSI (if there is any)\n  if (rangeKey) {\n    results.sort(function (a, b) {\n      return data.ScanIndexForward ?\n        a[rangeKey] - b[rangeKey] :\n        b[rangeKey] - a[rangeKey]\n    })\n  }\n\n  return this._formatPagedResults(results, data)\n}\n\n/**\n * Run a scan against the mock data\n *\n * @param {Object} data\n * @return {Q.Promise}\n */\nFakeTable.prototype.scan = function(data) {\n  var sortedData = this._sortDataByPrimaryKey()\n  var scanFilterFn = getFilterFn(data)\n\n  var results = []\n  for (var primary_key in sortedData) {\n    var currentRow = sortedData[primary_key]\n    if (scanFilterFn(currentRow)) {\n      results.push(currentRow)\n    }\n  }\n\n  return this._formatPagedResults(results, data)\n}\n\n\n/**\n * Filter and format the results of a query or scan.\n * @param {Array} results\n * @param {Object} queryData\n * @return {Q.Promise.<Object>}\n */\nFakeTable.prototype._formatPagedResults = function (results, queryData) {\n  var filteredResults = this._filterByExclusiveStartKey(results, queryData)\n\n  var limit = Math.min(queryData.Limit || Infinity, this._maxResultSetSize)\n  var ret = {}\n\n  if (filteredResults.length > limit) {\n    ret.LastEvaluatedKey = this._createLastEvaluatedKey(filteredResults[limit - 1])\n    filteredResults.length = limit\n  }\n\n  // If the client only requires the count, return the only the count\n  if (queryData.Select === 'COUNT') {\n    ret.Count = filteredResults.length\n    return Q.resolve(ret)\n  }\n\n  var attributes = this._getAttributesFromData(queryData)\n  ret.Items = typeUtil.packObjectOrArray(filteredResults, attributes)\n  ret.ConsumedCapacity = this._getCapacityBlob(ret.Items.length)\n  return Q.resolve(ret)\n}\n\n\n/**\n * @param {Array} sortedDbData\n * @param {Object} queryData\n * @return {Array} A filtered data array.\n */\nFakeTable.prototype._filterByExclusiveStartKey = function (sortedDbData, queryData) {\n  var hashKeyName = this.primaryKey.hash.name\n  var rangeKeyName = this.primaryKey.range.name\n  var exclusiveStartKey = queryData.ExclusiveStartKey ?\n      typeUtil.unpackObjectOrArray(queryData.ExclusiveStartKey) : undefined\n  if (!exclusiveStartKey) {\n    return sortedDbData\n  }\n\n  var startHashKey = exclusiveStartKey[hashKeyName]\n  var startRangeKey = exclusiveStartKey[rangeKeyName]\n  var matchIndex = -1\n\n  sortedDbData.forEach(function (item, i) {\n    var itemHashKey = item[hashKeyName]\n    var itemRangeKey = item[rangeKeyName]\n    if (itemHashKey == startHashKey && itemRangeKey == startRangeKey) {\n      matchIndex = i\n    }\n  })\n\n  return sortedDbData.slice(matchIndex + 1)\n}\n\n\n/**\n * @param {Object} item\n * @return {Object|undefined}\n */\nFakeTable.prototype._createLastEvaluatedKey = function (item) {\n  var primaryKeyAttribute = {}\n  primaryKeyAttribute[this.primaryKey.hash.name] = true\n  primaryKeyAttribute[this.primaryKey.range.name] = true\n  return typeUtil.packObjectOrArray(item, primaryKeyAttribute)\n}\n\n\n/**\n * Put an item into the mock data\n *\n * @param {Object} data\n * @return {Q.Promise}\n */\nFakeTable.prototype.putItem = function(data) {\n  var key = this._extractKey(data)\n\n  var item = this._getItemByKey(key)\n  this._checkExpected(getConditionFn(data), item)\n\n  // create the item to store\n  var obj = {}\n  for (var field in data.Item) {\n    obj[field] = typeUtil.objectToValue(data.Item[field])\n  }\n\n  // store the item\n  this._putItemAtKey(key, obj)\n\n\n  // done (ALL_OLD only returns ConsumedCapacity)\n  return Q.resolve({\n    Attributes: typeUtil.packObjectOrArray(item),\n    ConsumedCapacity: this._getCapacityBlob(1)\n  })\n}\n\n/**\n * Update an item in the mock data\n *\n * @param {Object} data\n * @return {Q.Promise}\n */\nFakeTable.prototype.updateItem = function(data) {\n  var key = this._extractKey(data)\n  var item = this._getItemByKey(key)\n\n  // run the conditional if it exists\n  this._checkExpected(getConditionFn(data), item)\n\n  var itemExists = !!item\n\n  // if the item doesn't exist, create a temp item which we may save later\n  if (!itemExists) {\n    item = {}\n    item[this.primaryKey.hash.name] = key.hash\n    if (this.primaryKey.range.name) {\n      item[this.primaryKey.range.name] = key.range\n    }\n  }\n\n  var oldItem = typeUtil.packObjectOrArray(item)\n  var newItem = localUpdater.update(oldItem, data._requestBuilder._updateExpressionBuilder._attributes)\n\n  // store the item\n  this._putItemAtKey(key, typeUtil.unpackObjectOrArray(newItem))\n\n  // done (ALL_OLD only returns ConsumedCapacity)\n  return Q.resolve({\n    Attributes: itemExists ? oldItem : null,\n    ConsumedCapacity: this._getCapacityBlob(1)\n  })\n}\n\n/**\n * Delete an item from the mock data\n *\n * @param {Object} data\n * @return {Q.Promise}\n */\nFakeTable.prototype.deleteItem = function(data) {\n  var key = this._extractKey(data)\n  var item = this._getItemByKey(key)\n  this._checkExpected(getConditionFn(data), item)\n  this._putItemAtKey(key, undefined)\n\n  // done (ALL_OLD only returns ConsumedCapacity)\n  return Q.resolve({\n    Attributes: typeUtil.packObjectOrArray(item),\n    ConsumedCapacity: this._getCapacityBlob(1)\n  })\n}\n\n/**\n * Get an item from the mock data\n *\n * @param {Object} data\n * @return {Q.Promise}\n */\nFakeTable.prototype.getItem = function(data) {\n  var key = this._extractKey(data)\n  var item = this._getItemByKey(key)\n  var attributes = this._getAttributesFromData(data)\n\n  return Q.resolve({\n    Item: typeUtil.packObjectOrArray(item, attributes),\n    ConsumedCapacity: this._getCapacityBlob(1)\n  })\n}\n\n/**\n * Get a JSON blob for the consumed capacity data.\n */\nFakeTable.prototype._getCapacityBlob = function(n) {\n  return {\n    CapacityUnits: n,\n    TableName: this.name\n  }\n}\n\n/**\n * Retrieve list of selected attributes from request data.\n * If data.AttributesToGet is falsy, that implies that all attributes\n * should be returned.\n *\n * @param {Object} data\n * @return {Object|undefined} map of attribute names to a boolean true value\n */\nFakeTable.prototype._getAttributesFromData = function(data) {\n  if (data.AttributesToGet) {\n    var attributes = {}\n    for (var i = 0; i < data.AttributesToGet.length; i++) {\n      attributes[data.AttributesToGet[i]] = true\n    }\n    return attributes\n  } else return undefined // Todo (gianni): this seems a fragile indicator to me\n}\n\n/**\n * For mutating operations, check if the conditional is valid\n *\n * @param {function(Object)} filterFn A filter function that returns false if the item is filtered, true if allowed.\n * @param {Object|undefined} currentData current data for the data being changed\n */\nFakeTable.prototype._checkExpected = function(filterFn, currentData) {\n  if (!filterFn(currentData)) {\n    throw new DynamoError('ConditionalCheckFailedException', 'Values are different than expected.')\n  }\n}\n\n/**\n * Throw an error if a key is too big.\n *\n * @param {{hash:(number|string), range:(number|string)}} key\n */\nFakeTable.prototype._validateKeySize = function (key) {\n  var size = Buffer.byteLength(String(key.hash), 'utf8')\n  if (this.primaryKey.range) {\n    size += Buffer.byteLength(String(key.range), 'utf8')\n  }\n  if (size > MAX_KEY_SIZE) {\n    throw new DynamoError('ValidationException',\n        'One or more parameter values were invalid: ' +\n        'Aggregated size of all range keys has exceeded the size limit of 1024 bytes')\n  }\n}\n\n/**\n * Throw an error if a filter expression uses a primary key\n *\n * @param {Object} data\n * @param {string} partitionKey\n * @param {?string} opt_sortKey\n */\nFakeTable.prototype._validateFilterKeys = function (data, partitionKey, opt_sortKey) {\n  forEachFilterCondition(data, function (condition, key) {\n    if (key === partitionKey || key === opt_sortKey) {\n      throw new DynamoError(\n        'ValidationException',\n        'Filter Expression can only contain non-primary key attributes: Primary key attribute: ' + key\n      )\n    }\n  })\n}\n\n/**\n * Get an item by its key\n *\n * @param {{hash:(number|string), range:(number|string)}} key\n * @return {Object|undefined} object\n */\nFakeTable.prototype._getItemByKey = function(key) {\n  this._validateKeySize(key)\n\n  if (!this.data[key.hash]) return undefined\n  return this.primaryKey.range ? this.data[key.hash][key.range] : this.data[key.hash]\n}\n\n/**\n * Gets an item by the Range Key (LSI)\n */\nFakeTable.prototype._getItemsByIndex = function(key, indexedKey) {\n  this._validateKeySize(key)\n\n  if (!this.data[key.hash]) return undefined\n  var items = []\n  for (var rangeKey in this.data[key.hash]) {\n    if (this.data[key.hash][rangeKey][indexedKey] === key.indexedKey) {\n      items.push(this.data[key.hash][rangeKey])\n    }\n  }\n  return items\n}\n\n/**\n * Put an item at its key\n *\n * @param {{hash:(number|string), range:(number|string)}} key\n * @param {*} obj\n */\nFakeTable.prototype._putItemAtKey = function(key, obj) {\n  this._validateKeySize(key)\n\n  if (this.primaryKey.range) {\n    if (!this.data[key.hash]) this.data[key.hash] = {}\n    if (!obj) {\n      delete this.data[key.hash][key.range]\n    } else {\n      this.data[key.hash][key.range] = obj\n    }\n  } else {\n    if (!obj) {\n      delete this.data[key.hash]\n    } else {\n      this.data[key.hash] = obj\n    }\n  }\n}\n\n/**\n * Retrieve all range keys for a specified hash key\n *\n * @param {string} hash\n * @return {Array.<string>}\n */\nFakeTable.prototype._getRangeKeyValuesForHash = function(hash) {\n  if (!this.data[hash]) return []\n  var keys = Object.keys(this.data[hash])\n  this._sortKeys(keys)\n  return keys\n}\n\n/**\n * Retrieve all attribute values for a specified hash key and attribute\n *\n * @param {string} hash\n * @return {Array.<string>}\n */\nFakeTable.prototype._getIndexedKeyValuesForHash = function(hash, attr) {\n  if (!this.data[hash]) return []\n  if (!attr || attr === this.primaryKey.range.name) {\n    return this._getRangeKeyValuesForHash(hash)\n  }\n  var keys = []\n  for (var key in this.data[hash]) {\n    var indexedKey = this.data[hash][key][attr]\n    if (keys.indexOf(indexedKey) < 0) {\n      keys.push(indexedKey)\n    }\n  }\n  this._sortKeys(keys)\n  return keys\n}\n\n/**\n * Sorts the array of keys in place. This method assumes that all the keys are of the same type.\n */\nFakeTable.prototype._sortKeys = function (keys) {\n  if (keys.length > 0 && !isNaN(keys[0])) {\n    keys.sort(function(a, b) { return a - b })\n  } else {\n    keys.sort()\n  }\n}\n\n/**\n * Extract a range key from request data\n *\n * @param {Object} data request data\n * @return {{hash:(number|string), range:(number|string)}}\n */\nFakeTable.prototype._extractKey = function(data) {\n  var key = {}\n\n  if (data.Key) {\n    key.hash = data.Key[this.primaryKey.hash.name][this.primaryKey.hash.type]\n    if (this.primaryKey.range) {\n      key.range = data.Key[this.primaryKey.range.name][this.primaryKey.range.type]\n    }\n  } else {\n    key.hash = data.Item[this.primaryKey.hash.name][this.primaryKey.hash.type]\n    if (this.primaryKey.range) {\n      key.range = data.Item[this.primaryKey.range.name][this.primaryKey.range.type]\n    }\n  }\n\n  return key\n}\n\n/**\n * Provides an array of the original data sorted by the Primary key, used as a Hash key here.\n */\nFakeTable.prototype._sortDataByPrimaryKey = function() {\n  var sortedData = []\n  var hashKeys = Object.keys(this.data).sort()\n  for (var i = 0; i < hashKeys.length; i++) {\n    var rangeKeys = Object.keys(this.data[hashKeys[i]]).sort()\n    for (var j = 0; j < rangeKeys.length; j++) {\n      sortedData.push(this.data[hashKeys[i]][rangeKeys[j]])\n    }\n  }\n  return sortedData\n}\n\n/**\n * Get a partial descirption of the table, format as specified by aws docs.\n * @return {Object} table description\n */\nFakeTable.prototype.describeTable = function() {\n  return {\n    'Table': {\n      'AttributeDefinitions': [\n        {'AttributeName': this.primaryKey.hash.name, 'AttributeType': this.primaryKey.hash.type},\n        {'AttributeName': this.primaryKey.range.name, 'AttributeType': this.primaryKey.range.type}\n      ],\n      'KeySchema': [\n        {'AttributeName': this.primaryKey.hash.name, 'KeyType': 'HASH'},\n        {'AttributeName': this.primaryKey.range.name, 'KeyType': 'RANGE'}\n      ],\n      'TableName': this.name,\n      'TableStatus': 'ACTIVE',\n      'ProvisionedThroughput': {\n        'ReadCapacityUnits': 1,\n        'WriteCapacityUnits': 1\n      }\n    }\n  }\n}\n\n/**\n * Create a Fake Dynamo client\n * @constructor\n */\nfunction FakeDynamo() {\n  this.tables = {}\n  this.resetStats()\n\n  this.isFakeDynamo = true\n}\n\n\n/**\n * Get stats counters\n */\nFakeDynamo.prototype.getStats = function() {\n  return this.stats\n}\n\n/**\n * Reset stats counters\n */\nFakeDynamo.prototype.resetStats = function() {\n  this.stats = {\n    putItem: 0,\n    getItem: 0,\n    deleteItem: 0,\n    updateItem: 0,\n    query: 0,\n    batchGetItem: 0,\n    batchGetItemCount: []\n  }\n}\n\n/**\n * Create a new table in the Fake Dynamo client\n *\n * @param {string} name table name\n * @return {FakeTable} a mock table\n */\nFakeDynamo.prototype.createTable = function(name) {\n  return this.tables[name] = new FakeTable(name)\n}\n\n/**\n * Get a mock table if it exists\n *\n * @param {string} name\n * @return {FakeTable} the mock table\n */\nFakeDynamo.prototype.getTable = function(name) {\n  if (!this.tables[name]) throw new Error('Table \"' + name + '\" has not been mocked!')\n  return this.tables[name]\n}\n\nfunction performOp(operation) {\n  return function (data, callback) {\n    try {\n      this.stats[operation] += 1\n      this.getTable(data.TableName)[operation](data)\n        .fail(function (e) {\n          callback(e)\n        })\n        .then(function (data) {\n          callback(null, data)\n        })\n    } catch (e) {\n      callback(e)\n    }\n  }\n}\n\nFakeDynamo.prototype.putItem = performOp('putItem')\nFakeDynamo.prototype.getItem = performOp('getItem')\nFakeDynamo.prototype.deleteItem = performOp('deleteItem')\nFakeDynamo.prototype.updateItem = performOp('updateItem')\nFakeDynamo.prototype.query = performOp('query')\nFakeDynamo.prototype.scan = performOp('scan')\n\n/**\n * Respond a batchGetItem request.\n *\n * @param {Object} data The request data\n * @return {Q.Promise.<Object>} the fetched items in Dynamo format\n */\nFakeDynamo.prototype.batchGetItem = function (data, callback) {\n  var promises = []\n  var resp = {Responses: {}, UnprocessedKeys: {}, ConsumedCapacity: []}\n  this.stats.batchGetItem += 1\n\n  try {\n    // the number of fetched object across all tables.\n    var count = 0\n    for (var tableName in data.RequestItems) {\n      var keys = data.RequestItems[tableName].Keys\n\n      // Check for duplicates\n      var keySet = {}\n      keys.forEach(function (k) {\n        keySet[JSON.stringify(k)] = true\n      })\n      if (Object.keys(keySet).length != keys.length) {\n        throw new DynamoError('ValidationException', 'Provided list of item keys contains duplicates')\n      }\n\n      var table = this.getTable(tableName)\n      var limit = Math.min(table._maxResultSetSize, MAX_GET_BATCH_ITEM_SIZE)\n      var countPerTable = 0\n      for (var i = 0; i < keys.length; i++) {\n        countPerTable++\n        count++\n        if (count <= limit) {\n          promises.push(\n            table.getItem({Item: keys[i], AttributesToGet: data.RequestItems[tableName].AttributesToGet})\n              .then(function (data) {\n                if (!(tableName in resp.Responses)) {\n                  resp.Responses[tableName] = []\n                }\n                if (!typ.isNullish(data.Item)) {\n                  resp.Responses[tableName].push(data.Item)\n                }\n              })\n          )\n        } else {\n          if (!(tableName in resp.UnprocessedKeys)) {\n            resp.UnprocessedKeys[tableName] = {Keys: []}\n          }\n          resp.UnprocessedKeys[tableName].Keys.push(keys[i])\n        }\n      }\n      resp.ConsumedCapacity.push(table._getCapacityBlob(countPerTable))\n    }\n    this.stats.batchGetItemCount.push(count)\n\n    return Q.all(promises)\n      .fail(function (e) {\n        callback(e)\n      })\n      .then(function () {\n        callback(null, resp)\n      })\n  } catch (e) {\n    callback(e)\n  }\n}\n\n/**\n * Get the partial description of a table, format as specified by aws docs.\n * @param  {Object} tableData data related to the table\n * @return {Q.Promise.<Object>} description of the named table\n */\nFakeDynamo.prototype.describeTable = function (tableData, callback) {\n  var table = this.tables[tableData.TableName]\n  if (!table) callback(new Error('No such table in FakeDynamo: ' + tableData.TableName))\n  return callback(null, table.describeTable())\n}\n\nmodule.exports = FakeDynamo\n"
  },
  {
    "path": "lib/GetItemBuilder.js",
    "content": "var DynamoRequest = require('./DynamoRequest')\nvar Builder = require('./Builder')\n\n/**\n * @param {Object} options\n * @constructor\n * @extends {Builder}\n */\nfunction GetItemBuilder(options) {\n  Builder.call(this, options)\n}\nrequire('util').inherits(GetItemBuilder, Builder)\n\nGetItemBuilder.prototype.execute = function () {\n  var req = new DynamoRequest(this.getOptions())\n    .setTable(this._tablePrefix, this._table)\n    .returnConsumedCapacity()\n    .setConsistent(this._isConsistent)\n    .setHashKey(this._hashKey, true)\n    .selectAttributes(this._attributes)\n\n  if (this._rangeKey) req.setRangeKey(this._rangeKey, true)\n\n  var queryData = req.build()\n\n  return this.request(\"getItem\", queryData)\n    .then(this.prepareOutput.bind(this))\n    .fail(this.emptyResult)\n    .failBound(this.convertErrors, null, {data: queryData, isWrite: false})\n}\n\nmodule.exports = GetItemBuilder\n"
  },
  {
    "path": "lib/PutItemBuilder.js",
    "content": "var typ = require('typ')\nvar DynamoRequest = require('./DynamoRequest')\nvar DynamoResponse = require('./DynamoResponse')\nvar Builder = require('./Builder')\nvar typeUtil = require('./typeUtil')\nvar errors = require('./errors')\n\n/**\n * @param {Object} options\n * @constructor\n * @extends {Builder}\n */\nfunction PutItemBuilder(options) {\n  Builder.call(this, options)\n\n  /** @private {string} */\n  this._returnValues = PutItemBuilder.RETURN_VALUES.ALL_OLD\n}\nrequire('util').inherits(PutItemBuilder, Builder)\n\nPutItemBuilder.RETURN_VALUES = {\n  NONE: 'NONE',\n  ALL_OLD: 'ALL_OLD',\n  UPDATED_OLD: 'UPDATED_OLD',\n  ALL_NEW: 'ALL_NEW',\n  UPDATED_NEW: 'UPDATED_NEW'\n}\n\nPutItemBuilder.prototype.setReturnValues = function (val) {\n  if (!PutItemBuilder.RETURN_VALUES[val]) {\n    throw new errors.InvalidReturnValuesError(val)\n  }\n\n  this._returnValues = val\n  return this\n}\n\nPutItemBuilder.prototype.setItem = function (item) {\n  for (var key in item) {\n    if (typ.isNullish(item[key])) {\n      throw new Error(\"Field '\" + key + \"' on item must not be null or undefined\")\n    }\n  }\n  this._item = item\n  return this\n}\n\n/** @override */\nPutItemBuilder.prototype.prepareOutput = function (output) {\n  if (this._returnValues !== 'NONE') {\n    output.UpdatedAttributes = typeUtil.packObjectOrArray(this._item)\n  }\n  return new DynamoResponse(this.getPrefix(), output, null)\n}\n\nPutItemBuilder.prototype.execute = function () {\n  var queryData = new DynamoRequest(this.getOptions())\n    .setTable(this._tablePrefix, this._table)\n    .returnConsumedCapacity()\n    .setItem(this._item)\n    .setExpected(this._conditions)\n    .setReturnValues(this._returnValues)\n    .build()\n\n  return this.request(\"putItem\", queryData)\n    .then(this.prepareOutput.bind(this))\n    .failBound(this.convertErrors, null, {data: queryData, isWrite: true})\n}\n\nmodule.exports = PutItemBuilder\n"
  },
  {
    "path": "lib/QueryBuilder.js",
    "content": "var typ = require('typ')\nvar ConditionBuilder = require('./ConditionBuilder')\nvar DynamoRequest = require('./DynamoRequest')\nvar DynamoResponse = require('./DynamoResponse')\nvar Builder = require('./Builder')\nvar util = require('util')\nvar IndexNotExistError = require('./errors').IndexNotExistError\n\n/**\n * @param {Object} options\n * @constructor\n * @extends {Builder}\n */\nfunction QueryBuilder(options) {\n  Builder.call(this, options)\n\n  /** @private {!ConditionBuilder} */\n  this._keyConditions = new ConditionBuilder()\n}\nutil.inherits(QueryBuilder, Builder)\n\n/**\n * If this query runs on a local index or global index, set a\n * function that can generate an index name based on query\n * conditions.\n *\n * @param {function(string, string): string} fn The generator function\n */\nQueryBuilder.prototype.setIndexNameGenerator = function (fn) {\n  this._indexNameGenerator = fn\n  return this\n}\n\nQueryBuilder.prototype.setHashKey = function (name, val) {\n  this._hashKeyName = name\n  this._keyConditions.filterAttributeEquals(name, val)\n  return this\n}\n\nQueryBuilder.prototype.setIndexRangeKeyWithoutCondition = function (name) {\n  this._rangeKeyName = name\n  return this\n}\n\nQueryBuilder.prototype.indexBeginsWith = function (name, prefix) {\n  this._rangeKeyName = name\n  this._keyConditions.filterAttributeBeginsWith(name, prefix)\n  return this\n}\n\nQueryBuilder.prototype.indexEqual =\nQueryBuilder.prototype.indexEquals = function (name, val) {\n  this._rangeKeyName = name\n  this._keyConditions.filterAttributeEquals(name, val)\n  return this\n}\n\nQueryBuilder.prototype.indexLessThanEqual =\nQueryBuilder.prototype.indexLessThanEquals = function (name, val) {\n  this._rangeKeyName = name\n  this._keyConditions.filterAttributeLessThanEqual(name, val)\n  return this\n}\n\nQueryBuilder.prototype.indexLessThan = function (name, val) {\n  this._rangeKeyName = name\n  this._keyConditions.filterAttributeLessThan(name, val)\n  return this\n}\n\nQueryBuilder.prototype.indexGreaterThanEqual =\nQueryBuilder.prototype.indexGreaterThanEquals = function (name, val) {\n  this._rangeKeyName = name\n  this._keyConditions.filterAttributeGreaterThanEqual(name, val)\n  return this\n}\n\nQueryBuilder.prototype.indexGreaterThan = function (name, val) {\n  this._rangeKeyName = name\n  this._keyConditions.filterAttributeGreaterThan(name, val)\n  return this\n}\n\nQueryBuilder.prototype.indexBetween = function (name, val1, val2) {\n  this._rangeKeyName = name\n  this._keyConditions.filterAttributeBetween(name, val1, val2)\n  return this\n}\n\nQueryBuilder.prototype.setStartKey = function (key) {\n  this._startKey = key\n  return this\n}\n\n/**\n * Set the index name of this query.\n *\n * @param {string} indexName\n */\nQueryBuilder.prototype.setIndexName = function (indexName) {\n  this._indexName = indexName\n  return this\n}\n\n/** @override */\nQueryBuilder.prototype.prepareOutput = function (output) {\n  return new DynamoResponse(\n      this.getPrefix(), output, this._repeatWithStartKey.bind(this))\n}\n\n/**\n * @param {Object} nextKey\n * @param {?number} opt_limit The number of items to check\n * @return {Q.Promise.<DynamoResponse>}\n * @private\n */\nQueryBuilder.prototype._repeatWithStartKey = function (nextKey, opt_limit) {\n  if (!typ.isNullish(opt_limit)) {\n    this.setLimit(opt_limit)\n  }\n  return this.setStartKey(nextKey).execute()\n}\n\nQueryBuilder.prototype.execute = function () {\n  var query = new DynamoRequest(this.getOptions())\n    .setTable(this._tablePrefix, this._table)\n    .returnConsumedCapacity()\n    .setQueryFilter(this._filters)\n    .setConsistent(this._isConsistent)\n    .setIndexName(this._indexName)\n    .setKeyConditions([this._keyConditions])\n    .setStartKey(this._startKey)\n    .selectAttributes(this._attributes, true)\n    .scanForward(this._shouldScanForward)\n    .setLimit(this._limit)\n\n  if (this._isCount) query.getCount()\n\n  if (this._rangeKeyCondition) {\n    query.setRangeKey.apply(query, this._rangeKeyCondition)\n  }\n\n  if (this._indexNameGenerator) {\n    var indexName = this._indexNameGenerator(this._hashKeyName, this._rangeKeyName)\n    if (!indexName) {\n      throw new IndexNotExistError(this._hashKeyName, this._rangeKeyName)\n    }\n    query.setIndexName(indexName)\n  }\n\n  var queryData = query.build()\n\n  return this.request('query', queryData)\n    .then(this.prepareOutput.bind(this))\n    .fail(this.emptyResults)\n    .failBound(this.convertErrors, null, {data: queryData, isWrite: false})\n}\n\nmodule.exports = QueryBuilder\n"
  },
  {
    "path": "lib/ScanBuilder.js",
    "content": "var typ = require('typ')\nvar DynamoRequest = require('./DynamoRequest')\nvar DynamoResponse = require('./DynamoResponse')\nvar Builder = require('./Builder')\nvar IndexNotExistError = require('./errors').IndexNotExistError\n\n/**\n * @param {Object} options\n * @constructor\n * @extends {Builder}\n */\nfunction ScanBuilder(options) {\n  Builder.call(this, options)\n}\nrequire('util').inherits(ScanBuilder, Builder)\n\n/**\n * If this scan runs on a local index or global index, set a\n * function that can generate an index name based on query\n * conditions.\n *\n * @param {function(string, string): string} fn The generator function\n */\nScanBuilder.prototype.setIndexNameGenerator = function (fn) {\n  this._indexNameGenerator = fn\n  return this\n}\n\n\nScanBuilder.prototype.setStartKey = function (key) {\n  this._startKey = key\n  return this\n}\n\n/**\n * @param {number} segment\n * @param {number} totalSegments\n * @return {ScanBuilder}\n */\nScanBuilder.prototype.setParallelScan = function (segment, totalSegments) {\n  this._segment = segment\n  this._totalSegments = totalSegments\n  return this\n}\n\n/** @override */\nScanBuilder.prototype.prepareOutput = function (output) {\n  return new DynamoResponse(\n      this.getPrefix(), output, this._repeatWithStartKey.bind(this))\n}\n\n/**\n * @param {Object} nextKey\n * @param {?number} opt_limit The number of items to check\n * @return {Q.Promise.<DynamoResponse>}\n * @private\n */\nScanBuilder.prototype._repeatWithStartKey = function (nextKey, opt_limit) {\n  if (!typ.isNullish(opt_limit)) {\n    this.setLimit(opt_limit)\n  }\n  return this.setStartKey(nextKey).execute()\n}\n\nScanBuilder.prototype.execute = function () {\n  var query = new DynamoRequest(this.getOptions())\n    .setTable(this._tablePrefix, this._table)\n    .returnConsumedCapacity()\n    .setScanFilter(this._filters)\n    .setLimit(this._limit)\n    .setStartKey(this._startKey)\n    .selectAttributes(this._attributes)\n    .setParallelScan(this._segment, this._totalSegments)\n\n  if (this._indexNameGenerator) {\n    var rangeKeyName = this._rangeKey ? this._rangeKey.name : ''\n    var indexName = this._indexNameGenerator(this._hashKey.name, rangeKeyName)\n    if (!indexName) {\n      throw new IndexNotExistError(this._hashKey.name, this._rangeKey.name)\n    }\n    query.setIndexName(indexName)\n  }\n\n  var queryData = query.build()\n\n  return this.request(\"scan\", queryData)\n    .then(this.prepareOutput.bind(this))\n    .fail(this.emptyResults)\n    .failBound(this.convertErrors, null, {data: queryData, isWrite: false})\n}\n\nmodule.exports = ScanBuilder\n"
  },
  {
    "path": "lib/UpdateBuilder.js",
    "content": "var typ = require('typ')\nvar util = require('util')\nvar DynamoRequest = require('./DynamoRequest')\nvar DynamoResponse = require('./DynamoResponse')\nvar Builder = require('./Builder')\nvar localUpdater = require('./localUpdater')\nvar typeUtil = require('./typeUtil')\nvar errors = require('./errors')\n\n/**\n * @param {Object} options\n * @constructor\n * @extends {Builder}\n */\nfunction UpdateBuilder(options) {\n  Builder.call(this, options)\n\n  /** @private {!Object.<?>} */\n  this._attributeUpdates = {}\n\n  /** @private {boolean} */\n  this._enabledUpsert = false\n\n  /** @private {string} */\n  this._returnValues = UpdateBuilder.RETURN_VALUES.ALL_OLD\n\n  this._uniqueName = ''\n}\nutil.inherits(UpdateBuilder, Builder)\n\nUpdateBuilder.RETURN_VALUES = {\n  NONE: 'NONE',\n  ALL_OLD: 'ALL_OLD',\n  UPDATED_OLD: 'UPDATED_OLD',\n  ALL_NEW: 'ALL_NEW',\n  UPDATED_NEW: 'UPDATED_NEW'\n}\n\nUpdateBuilder.prototype.enableUpsert = function () {\n  this._enabledUpsert = true\n  return this\n}\n\nUpdateBuilder.prototype.setReturnValues = function (val) {\n  if (!UpdateBuilder.RETURN_VALUES[val]) {\n    throw new errors.InvalidReturnValuesError(val)\n  }\n\n  this._returnValues = val\n  return this\n}\n\n/**\n * @param {string} key\n * @param {boolean|number|string|Array.<number>|Array.<string>} val\n */\nUpdateBuilder.prototype.putAttribute = function (key, val) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val)) throw new Error(\"Val must be defined\")\n  this._attributeUpdates[key] = {\n    Value: typeUtil.valueToObject(val),\n    Action: 'PUT'\n  }\n  return this\n}\n\n/**\n * @param {string} key\n * @param {number|string|Array.<number>|Array.<string>} val\n */\nUpdateBuilder.prototype.addToAttribute = function (key, val) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val)) throw new Error(\"Val must be defined\")\n  this._attributeUpdates[key] = {\n    Value: typeUtil.valueToObject(val),\n    Action: 'ADD'\n  }\n  return this\n}\n\n/**\n * @param {string} key\n * @param {number|string|Array.<number>|Array.<string>} val\n */\nUpdateBuilder.prototype.deleteFromAttribute = function (key, val) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  if (typ.isNullish(val)) throw new Error(\"Val must be defined\")\n\n  this._attributeUpdates[key] = {\n    Value: typeUtil.valueToObject(val),\n    Action: 'DELETE'\n  }\n  return this\n}\n\nUpdateBuilder.prototype.deleteAttribute = function (key) {\n  if (typ.isNullish(key)) throw new Error(\"Key must be defined\")\n  this._attributeUpdates[key] = {\n    Action: 'DELETE'\n  }\n  return this\n}\n\n/** @override */\nUpdateBuilder.prototype.prepareOutput = function (output) {\n  if (this._returnValues !== 'NONE') {\n    var attributes = output.Attributes\n    if (!attributes) {\n      attributes = {}\n      attributes[this._hashKey.name] = typeUtil.valueToObject(this._hashKey.val)\n      if (this._rangeKey) {\n        attributes[this._rangeKey.name] = typeUtil.valueToObject(this._rangeKey.val)\n      }\n    }\n\n    output.UpdatedAttributes = localUpdater.update(attributes, this._attributeUpdates)\n  }\n  return new DynamoResponse(this.getPrefix(), output, null)\n}\n\nUpdateBuilder.prototype.execute = function () {\n  var req = new DynamoRequest(this.getOptions())\n    .setTable(this._tablePrefix, this._table)\n    .returnConsumedCapacity()\n    .setHashKey(this._hashKey, true)\n    .setUpdates(this._attributeUpdates)\n    .setExpected(this._conditions)\n    .setReturnValues(this._returnValues)\n\n  if (this._rangeKey) req.setRangeKey(this._rangeKey, true)\n\n  var queryData = req.build()\n\n  if ((!this._conditions || !this._conditions.length) && !this._enabledUpsert) {\n    console.warn(\"Update issued without conditions or .enableUpsert() called\")\n    console.trace()\n  }\n  return this.request(\"updateItem\", queryData)\n    .then(this.prepareOutput.bind(this))\n    .failBound(this.convertErrors, null, {data: queryData, isWrite: true})\n}\n\nmodule.exports = UpdateBuilder\n"
  },
  {
    "path": "lib/UpdateExpressionBuilder.js",
    "content": "var typeUtil = require('./typeUtil')\n\n// The UpdateExpression API has different names for all the action types, but they're\n// semantically the same as the old actions.\nfunction getActionName(attr) {\n  switch (attr.Action) {\n    case 'PUT':\n      return 'SET'\n    case 'ADD':\n      return 'ADD'\n    case 'DELETE':\n      return attr.Value ? 'DELETE' : 'REMOVE'\n    default:\n      throw new Error('Unrecognized action ' + attr.Action)\n  }\n}\n\n/**\n * Translates old AttributeValueUpdate format into the new UpdateExpression format.\n *\n * @param {Object} attributes\n * @constructor\n */\nfunction UpdateExpressionBuilder(attributes) {\n  this._attributes = attributes\n  this._uniqueName = ''\n}\n\n/**\n * @param {Object} data\n * @param {Array<UpdateExpressionBuilder>} attributes\n * @param {{count: number}} nameMutex\n * @return {UpdateExpressionBuilder}\n */\nUpdateExpressionBuilder.populateUpdateExpression = function (data, attributes, nameMutex) {\n  var builder = new UpdateExpressionBuilder(attributes)\n  builder._assignUniqueNames(nameMutex)\n\n  if (!data.ExpressionAttributeNames) {\n    data.ExpressionAttributeNames = {}\n  }\n  typeUtil.extendAttributeNames(data.ExpressionAttributeNames, builder.buildAttributeNames())\n\n  if (!data.ExpressionAttributeValues) {\n    data.ExpressionAttributeValues = {}\n  }\n  typeUtil.extendAttributeValues(data.ExpressionAttributeValues, builder.buildAttributeValues())\n\n  data.UpdateExpression = builder.buildExpression()\n  return builder\n}\n\n/**\n * @param {Object} nameMutex\n */\nUpdateExpressionBuilder.prototype._assignUniqueNames = function (nameMutex) {\n  if (!nameMutex.count) {\n    nameMutex.count = 1\n  }\n  this._uniqueName = 'U' + nameMutex.count++\n}\n\n/** @return {Object} */\nUpdateExpressionBuilder.prototype.buildAttributeNames = function () {\n  return typeUtil.buildAttributeNames(Object.keys(this._attributes))\n}\n\n/** @return {Object} */\nUpdateExpressionBuilder.prototype.buildAttributeValues = function () {\n  var result = {}\n  Object.keys(this._attributes).map(function (key) {\n    var attr = this._attributes[key]\n    var value = attr.Value\n    if (value) {\n      result[this._getValueAlias(key)] = value\n    }\n  }, this)\n  return result\n}\n\n/**\n * @see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ExpressionPlaceholders.html#ExpressionAttributeValues\n */\nUpdateExpressionBuilder.prototype._getValueAlias = function (key) {\n  if (!this._uniqueName) throw new Error('Names have not been assigned yet')\n  return ':V' + this._uniqueName + 'X' + key\n}\n\n/**\n * @return {string} String suitable for UpdateExpression\n * @see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.Modifying.html\n */\nUpdateExpressionBuilder.prototype.buildExpression = function () {\n  var keysByAction = {}\n  Object.keys(this._attributes).map(function (key) {\n    var attr = this._attributes[key]\n    var action = getActionName(attr)\n    if (!keysByAction[action]) {\n      keysByAction[action] = []\n    }\n    keysByAction[action].push(key)\n  }, this)\n\n  var groups = []\n  Object.keys(keysByAction).map(function (action) {\n    var keys = keysByAction[action]\n    groups.push(\n      action + ' ' +\n      keys.map(function (key) {\n        var attrAlias = typeUtil.getAttributeAlias(key)\n        var valueAlias = this._getValueAlias(key)\n        if (action == 'REMOVE') {\n          return attrAlias\n        } else if (action == 'SET') {\n          return attrAlias + ' = ' + valueAlias\n        } else if (action == 'ADD' || action == 'DELETE') {\n          return attrAlias + ' ' + valueAlias\n        } else {\n          throw new Error('Unrecognized action ' + action)\n        }\n      }, this).join(','))\n  }, this)\n\n  return groups.join(' ')\n}\n\nmodule.exports = UpdateExpressionBuilder\n"
  },
  {
    "path": "lib/common.js",
    "content": "var AWSName = {\n  REGION:  [\"us-east-1\",\n            \"us-west-1\",\n            \"us-west-2\",\n            \"eu-west-1\",\n            \"ap-northeast-1\",\n            \"ap-southeast-1\",\n            \"ap-southeast-2\",\n            \"sa-east-1\"],\n  API_VERSION_2011: \"2011-12-05\",\n  API_VERSION_2012: \"2012-08-10\"\n}\n\nmodule.exports = {\n  AWSName: AWSName\n}\n"
  },
  {
    "path": "lib/errors.js",
    "content": "// Copyright 2013. The Obvious Corporation.\n\nvar util = require('util')\n\n/**\n * @param {Object} data\n * @param {string} msg\n * @param {string} requestId\n * @constructor\n * @extends {Error}\n */\nfunction ConditionalError(data, msg, requestId) {\n  Error.captureStackTrace(this)\n  this.data = data || {}\n  this.message = 'The conditional request failed'\n  this.details = msg\n  this.table = data.TableName || 'unknown'\n  this.requestId = requestId\n}\nutil.inherits(ConditionalError, Error)\nConditionalError.prototype.type = 'ConditionalError'\nConditionalError.prototype.name = 'ConditionalError'\n\n\n/**\n * @param {Object} data\n * @param {string} msg\n * @param {boolean} isWrite\n * @param {string} requestId\n * @constructor\n * @extends {Error}\n */\nfunction ProvisioningError(data, msg, isWrite, requestId) {\n  Error.captureStackTrace(this)\n  this.data = data || {}\n  this.message = 'The level of configured provisioned throughput for the table was exceeded'\n  this.details = msg\n  this.table = data.TableName || 'unknown'\n  this.isWrite = isWrite\n  this.requestId = requestId\n}\nutil.inherits(ProvisioningError, Error)\nProvisioningError.prototype.type = 'ProvisioningError'\nProvisioningError.prototype.name = 'ProvisioningError'\n\n\n/**\n * @param {Object} data\n * @param {string} msg\n * @param {boolean} isWrite\n * @param {string} requestId\n * @constructor\n * @extends {Error}\n */\nfunction ValidationError(data, msg, isWrite, requestId) {\n  Error.captureStackTrace(this)\n  this.data = data || {}\n  this.message = msg\n  this.table = data.TableName || 'unknown'\n  this.isWrite = isWrite\n  this.requestId = requestId\n}\nutil.inherits(ValidationError, Error)\nValidationError.prototype.type = 'ValidationError'\nValidationError.prototype.name = 'ValidationError'\n\n/**\n * @param {string} hashKeyName\n * @param {string} rangeKeyName\n * @constructor\n * @extends {Error}\n */\nfunction IndexNotExistError(hashKeyName, rangeKeyName) {\n  Error.captureStackTrace(this)\n  this.hashKeyName = hashKeyName\n  this.rangeKeyName = rangeKeyName\n}\nutil.inherits(IndexNotExistError, Error)\nIndexNotExistError.prototype.type = 'IndexNotExistError'\nIndexNotExistError.prototype.name = 'IndexNotExistError'\n\n/**\n * @param {string} returnValues\n * @constructor\n * @extends {Error}\n */\nfunction InvalidReturnValuesError(returnValues) {\n  Error.captureStackTrace(this)\n  this.returnValues = returnValues\n}\nutil.inherits(InvalidReturnValuesError, Error)\nInvalidReturnValuesError.prototype.type = 'InvalidReturnValuesError'\nInvalidReturnValuesError.prototype.name = 'InvalidReturnValuesError'\n\nmodule.exports = {\n  ConditionalError: ConditionalError,\n  ProvisioningError: ProvisioningError,\n  ValidationError: ValidationError,\n  IndexNotExistError: IndexNotExistError,\n  InvalidReturnValuesError: InvalidReturnValuesError\n}\n"
  },
  {
    "path": "lib/localUpdater.js",
    "content": "// Copyright 2015 A Medium Corporation.\n\nvar typeUtil = require('./typeUtil')\n\n/**\n * @param {Object} oldItem\n * @return {Object}\n */\nfunction _cloneObject (oldItem) {\n  var newItem = {}\n  for (var key in oldItem) {\n    if (oldItem.hasOwnProperty(key)) {\n      newItem[key] = oldItem[key]\n    }\n  }\n\n  return newItem\n}\n\n/**\n * @param {Object} item\n * @param {string} field\n * @param {Object} update\n */\nfunction _processDeleteAction (item, field, update) {\n  // From the dynamo docs:\n  //\n  // \"If no value is specified, the attribute and its value are removed\n  // from the item. The data type of the specified value must match the\n  // existing value's data type.\n  //\n  // If a set of values is specified, then those values are subtracted\n  // from the old set. For example, if the attribute value was the set\n  // [a,b,c] and the DELETE action specified [a,c], then the final\n  // attribute value would be [b]. Specifying an empty set is an error.\"\n  if (typeUtil.objectIsEmpty(update.Value)) {\n    // delete a field if it exists\n    delete item[field]\n\n  } else if (typeUtil.objectIsNonEmptySet(update.Value)) {\n    // delete the items from the set if they exist\n    item[field] = typeUtil.deleteFromSet(item[field], update.Value)\n    if (!item[field]) delete item[field]\n\n  } else {\n    throw new Error('Trying to DELETE to a specified field from a non-set')\n  }\n}\n\n/**\n * @param {Object} item\n * @param {string} field\n * @param {Object} update\n */\nfunction _processPutAction (item, field, update) {\n  // Attribute values cannot be null. String and Binary type attributes must have\n  // lengths greater than zero. Set type attributes must not be empty. Requests\n  // with empty values will be rejected with a ValidationException exception.\n\n  if (!typeUtil.objectIsEmpty(update.Value)) {\n    // set the value of a field\n    item[field] = update.Value\n\n  } else {\n    throw new Error('Trying to PUT a field with an empty value')\n  }\n}\n\n/**\n * @param {Object} item\n * @param {string} field\n * @param {Object} update\n */\nfunction _processAddAction (item, field, update) {\n  if (typeUtil.objectIsNonEmptySet(update.Value)) {\n    // append to an array\n    item[field] = typeUtil.addToSet(item[field], update.Value)\n\n  } else if (typeUtil.objectToType(update.Value) == 'N') {\n    // increment a number\n    item[field] = typeUtil.addToNumber(item[field], update.Value)\n\n  } else {\n    throw new Error('Trying to ADD to a field which isnt an array or number')\n  }\n}\n\n/**\n * @param {Object} oldItem\n * @param {Object} updates\n * @return {Object}\n */\nfunction update (oldItem, updates) {\n  if (!oldItem) {\n    throw new Error('oldItem should not be falsy')\n  }\n  var newItem = _cloneObject(oldItem)\n\n  for (var field in updates) {\n    var update = updates[field]\n\n    // See http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html#DDB-UpdateItem-request-AttributeUpdates\n\n    if (update.Action === 'DELETE') {\n      _processDeleteAction(newItem, field, update)\n    } else if (update.Action === 'PUT') {\n      _processPutAction(newItem, field, update)\n    } else if (update.Action === 'ADD') {\n      _processAddAction(newItem, field, update)\n    }\n  }\n\n  return newItem\n}\n\nmodule.exports = {\n  update: update\n}\n"
  },
  {
    "path": "lib/reserved.js",
    "content": "/**\n * @fileoverview http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html\n */\n\nvar list = [\n  'ABORT',\n  'ABSOLUTE',\n  'ACTION',\n  'ADD',\n  'AFTER',\n  'AGENT',\n  'AGGREGATE',\n  'ALL',\n  'ALLOCATE',\n  'ALTER',\n  'ANALYZE',\n  'AND',\n  'ANY',\n  'ARCHIVE',\n  'ARE',\n  'ARRAY',\n  'AS',\n  'ASC',\n  'ASCII',\n  'ASENSITIVE',\n  'ASSERTION',\n  'ASYMMETRIC',\n  'AT',\n  'ATOMIC',\n  'ATTACH',\n  'ATTRIBUTE',\n  'AUTH',\n  'AUTHORIZATION',\n  'AUTHORIZE',\n  'AUTO',\n  'AVG',\n  'BACK',\n  'BACKUP',\n  'BASE',\n  'BATCH',\n  'BEFORE',\n  'BEGIN',\n  'BETWEEN',\n  'BIGINT',\n  'BINARY',\n  'BIT',\n  'BLOB',\n  'BLOCK',\n  'BOOLEAN',\n  'BOTH',\n  'BREADTH',\n  'BUCKET',\n  'BULK',\n  'BY',\n  'BYTE',\n  'CALL',\n  'CALLED',\n  'CALLING',\n  'CAPACITY',\n  'CASCADE',\n  'CASCADED',\n  'CASE',\n  'CAST',\n  'CATALOG',\n  'CHAR',\n  'CHARACTER',\n  'CHECK',\n  'CLASS',\n  'CLOB',\n  'CLOSE',\n  'CLUSTER',\n  'CLUSTERED',\n  'CLUSTERING',\n  'CLUSTERS',\n  'COALESCE',\n  'COLLATE',\n  'COLLATION',\n  'COLLECTION',\n  'COLUMN',\n  'COLUMNS',\n  'COMBINE',\n  'COMMENT',\n  'COMMIT',\n  'COMPACT',\n  'COMPILE',\n  'COMPRESS',\n  'CONDITION',\n  'CONFLICT',\n  'CONNECT',\n  'CONNECTION',\n  'CONSISTENCY',\n  'CONSISTENT',\n  'CONSTRAINT',\n  'CONSTRAINTS',\n  'CONSTRUCTOR',\n  'CONSUMED',\n  'CONTINUE',\n  'CONVERT',\n  'COPY',\n  'CORRESPONDING',\n  'COUNT',\n  'COUNTER',\n  'CREATE',\n  'CROSS',\n  'CUBE',\n  'CURRENT',\n  'CURSOR',\n  'CYCLE',\n  'DATA',\n  'DATABASE',\n  'DATE',\n  'DATETIME',\n  'DAY',\n  'DEALLOCATE',\n  'DEC',\n  'DECIMAL',\n  'DECLARE',\n  'DEFAULT',\n  'DEFERRABLE',\n  'DEFERRED',\n  'DEFINE',\n  'DEFINED',\n  'DEFINITION',\n  'DELETE',\n  'DELIMITED',\n  'DEPTH',\n  'DEREF',\n  'DESC',\n  'DESCRIBE',\n  'DESCRIPTOR',\n  'DETACH',\n  'DETERMINISTIC',\n  'DIAGNOSTICS',\n  'DIRECTORIES',\n  'DISABLE',\n  'DISCONNECT',\n  'DISTINCT',\n  'DISTRIBUTE',\n  'DO',\n  'DOMAIN',\n  'DOUBLE',\n  'DROP',\n  'DUMP',\n  'DURATION',\n  'DYNAMIC',\n  'EACH',\n  'ELEMENT',\n  'ELSE',\n  'ELSEIF',\n  'EMPTY',\n  'ENABLE',\n  'END',\n  'EQUAL',\n  'EQUALS',\n  'ERROR',\n  'ESCAPE',\n  'ESCAPED',\n  'EVAL',\n  'EVALUATE',\n  'EXCEEDED',\n  'EXCEPT',\n  'EXCEPTION',\n  'EXCEPTIONS',\n  'EXCLUSIVE',\n  'EXEC',\n  'EXECUTE',\n  'EXISTS',\n  'EXIT',\n  'EXPLAIN',\n  'EXPLODE',\n  'EXPORT',\n  'EXPRESSION',\n  'EXTENDED',\n  'EXTERNAL',\n  'EXTRACT',\n  'FAIL',\n  'FALSE',\n  'FAMILY',\n  'FETCH',\n  'FIELDS',\n  'FILE',\n  'FILTER',\n  'FILTERING',\n  'FINAL',\n  'FINISH',\n  'FIRST',\n  'FIXED',\n  'FLATTERN',\n  'FLOAT',\n  'FOR',\n  'FORCE',\n  'FOREIGN',\n  'FORMAT',\n  'FORWARD',\n  'FOUND',\n  'FREE',\n  'FROM',\n  'FULL',\n  'FUNCTION',\n  'FUNCTIONS',\n  'GENERAL',\n  'GENERATE',\n  'GET',\n  'GLOB',\n  'GLOBAL',\n  'GO',\n  'GOTO',\n  'GRANT',\n  'GREATER',\n  'GROUP',\n  'GROUPING',\n  'HANDLER',\n  'HASH',\n  'HAVE',\n  'HAVING',\n  'HEAP',\n  'HIDDEN',\n  'HOLD',\n  'HOUR',\n  'IDENTIFIED',\n  'IDENTITY',\n  'IF',\n  'IGNORE',\n  'IMMEDIATE',\n  'IMPORT',\n  'IN',\n  'INCLUDING',\n  'INCLUSIVE',\n  'INCREMENT',\n  'INCREMENTAL',\n  'INDEX',\n  'INDEXED',\n  'INDEXES',\n  'INDICATOR',\n  'INFINITE',\n  'INITIALLY',\n  'INLINE',\n  'INNER',\n  'INNTER',\n  'INOUT',\n  'INPUT',\n  'INSENSITIVE',\n  'INSERT',\n  'INSTEAD',\n  'INT',\n  'INTEGER',\n  'INTERSECT',\n  'INTERVAL',\n  'INTO',\n  'INVALIDATE',\n  'IS',\n  'ISOLATION',\n  'ITEM',\n  'ITEMS',\n  'ITERATE',\n  'JOIN',\n  'KEY',\n  'KEYS',\n  'LAG',\n  'LANGUAGE',\n  'LARGE',\n  'LAST',\n  'LATERAL',\n  'LEAD',\n  'LEADING',\n  'LEAVE',\n  'LEFT',\n  'LENGTH',\n  'LESS',\n  'LEVEL',\n  'LIKE',\n  'LIMIT',\n  'LIMITED',\n  'LINES',\n  'LIST',\n  'LOAD',\n  'LOCAL',\n  'LOCALTIME',\n  'LOCALTIMESTAMP',\n  'LOCATION',\n  'LOCATOR',\n  'LOCK',\n  'LOCKS',\n  'LOG',\n  'LOGED',\n  'LONG',\n  'LOOP',\n  'LOWER',\n  'MAP',\n  'MATCH',\n  'MATERIALIZED',\n  'MAX',\n  'MAXLEN',\n  'MEMBER',\n  'MERGE',\n  'METHOD',\n  'METRICS',\n  'MIN',\n  'MINUS',\n  'MINUTE',\n  'MISSING',\n  'MOD',\n  'MODE',\n  'MODIFIES',\n  'MODIFY',\n  'MODULE',\n  'MONTH',\n  'MULTI',\n  'MULTISET',\n  'NAME',\n  'NAMES',\n  'NATIONAL',\n  'NATURAL',\n  'NCHAR',\n  'NCLOB',\n  'NEW',\n  'NEXT',\n  'NO',\n  'NONE',\n  'NOT',\n  'NULL',\n  'NULLIF',\n  'NUMBER',\n  'NUMERIC',\n  'OBJECT',\n  'OF',\n  'OFFLINE',\n  'OFFSET',\n  'OLD',\n  'ON',\n  'ONLINE',\n  'ONLY',\n  'OPAQUE',\n  'OPEN',\n  'OPERATOR',\n  'OPTION',\n  'OR',\n  'ORDER',\n  'ORDINALITY',\n  'OTHER',\n  'OTHERS',\n  'OUT',\n  'OUTER',\n  'OUTPUT',\n  'OVER',\n  'OVERLAPS',\n  'OVERRIDE',\n  'OWNER',\n  'PAD',\n  'PARALLEL',\n  'PARAMETER',\n  'PARAMETERS',\n  'PARTIAL',\n  'PARTITION',\n  'PARTITIONED',\n  'PARTITIONS',\n  'PATH',\n  'PERCENT',\n  'PERCENTILE',\n  'PERMISSION',\n  'PERMISSIONS',\n  'PIPE',\n  'PIPELINED',\n  'PLAN',\n  'POOL',\n  'POSITION',\n  'PRECISION',\n  'PREPARE',\n  'PRESERVE',\n  'PRIMARY',\n  'PRIOR',\n  'PRIVATE',\n  'PRIVILEGES',\n  'PROCEDURE',\n  'PROCESSED',\n  'PROJECT',\n  'PROJECTION',\n  'PROPERTY',\n  'PROVISIONING',\n  'PUBLIC',\n  'PUT',\n  'QUERY',\n  'QUIT',\n  'QUORUM',\n  'RAISE',\n  'RANDOM',\n  'RANGE',\n  'RANK',\n  'RAW',\n  'READ',\n  'READS',\n  'REAL',\n  'REBUILD',\n  'RECORD',\n  'RECURSIVE',\n  'REDUCE',\n  'REF',\n  'REFERENCE',\n  'REFERENCES',\n  'REFERENCING',\n  'REGEXP',\n  'REGION',\n  'REINDEX',\n  'RELATIVE',\n  'RELEASE',\n  'REMAINDER',\n  'RENAME',\n  'REPEAT',\n  'REPLACE',\n  'REQUEST',\n  'RESET',\n  'RESIGNAL',\n  'RESOURCE',\n  'RESPONSE',\n  'RESTORE',\n  'RESTRICT',\n  'RESULT',\n  'RETURN',\n  'RETURNING',\n  'RETURNS',\n  'REVERSE',\n  'REVOKE',\n  'RIGHT',\n  'ROLE',\n  'ROLES',\n  'ROLLBACK',\n  'ROLLUP',\n  'ROUTINE',\n  'ROW',\n  'ROWS',\n  'RULE',\n  'RULES',\n  'SAMPLE',\n  'SATISFIES',\n  'SAVE',\n  'SAVEPOINT',\n  'SCAN',\n  'SCHEMA',\n  'SCOPE',\n  'SCROLL',\n  'SEARCH',\n  'SECOND',\n  'SECTION',\n  'SEGMENT',\n  'SEGMENTS',\n  'SELECT',\n  'SELF',\n  'SEMI',\n  'SENSITIVE',\n  'SEPARATE',\n  'SEQUENCE',\n  'SERIALIZABLE',\n  'SESSION',\n  'SET',\n  'SETS',\n  'SHARD',\n  'SHARE',\n  'SHARED',\n  'SHORT',\n  'SHOW',\n  'SIGNAL',\n  'SIMILAR',\n  'SIZE',\n  'SKEWED',\n  'SMALLINT',\n  'SNAPSHOT',\n  'SOME',\n  'SOURCE',\n  'SPACE',\n  'SPACES',\n  'SPARSE',\n  'SPECIFIC',\n  'SPECIFICTYPE',\n  'SPLIT',\n  'SQL',\n  'SQLCODE',\n  'SQLERROR',\n  'SQLEXCEPTION',\n  'SQLSTATE',\n  'SQLWARNING',\n  'START',\n  'STATE',\n  'STATIC',\n  'STATUS',\n  'STORAGE',\n  'STORE',\n  'STORED',\n  'STREAM',\n  'STRING',\n  'STRUCT',\n  'STYLE',\n  'SUB',\n  'SUBMULTISET',\n  'SUBPARTITION',\n  'SUBSTRING',\n  'SUBTYPE',\n  'SUM',\n  'SUPER',\n  'SYMMETRIC',\n  'SYNONYM',\n  'SYSTEM',\n  'TABLE',\n  'TABLESAMPLE',\n  'TEMP',\n  'TEMPORARY',\n  'TERMINATED',\n  'TEXT',\n  'THAN',\n  'THEN',\n  'THROUGHPUT',\n  'TIME',\n  'TIMESTAMP',\n  'TIMEZONE',\n  'TINYINT',\n  'TO',\n  'TOKEN',\n  'TOTAL',\n  'TOUCH',\n  'TRAILING',\n  'TRANSACTION',\n  'TRANSFORM',\n  'TRANSLATE',\n  'TRANSLATION',\n  'TREAT',\n  'TRIGGER',\n  'TRIM',\n  'TRUE',\n  'TRUNCATE',\n  'TTL',\n  'TUPLE',\n  'TYPE',\n  'UNDER',\n  'UNDO',\n  'UNION',\n  'UNIQUE',\n  'UNIT',\n  'UNKNOWN',\n  'UNLOGGED',\n  'UNNEST',\n  'UNPROCESSED',\n  'UNSIGNED',\n  'UNTIL',\n  'UPDATE',\n  'UPPER',\n  'URL',\n  'USAGE',\n  'USE',\n  'USER',\n  'USERS',\n  'USING',\n  'UUID',\n  'VACUUM',\n  'VALUE',\n  'VALUED',\n  'VALUES',\n  'VARCHAR',\n  'VARIABLE',\n  'VARIANCE',\n  'VARINT',\n  'VARYING',\n  'VIEW',\n  'VIEWS',\n  'VIRTUAL',\n  'VOID',\n  'WAIT',\n  'WHEN',\n  'WHENEVER',\n  'WHERE',\n  'WHILE',\n  'WINDOW',\n  'WITH',\n  'WITHIN',\n  'WITHOUT',\n  'WORK',\n  'WRAPPED',\n  'WRITE',\n  'YEAR',\n  'ZONE'\n]\n\nvar set = {}\nlist.forEach(function (w) {\n  set[w] = true\n})\n\nmodule.exports = {\n  list: list,\n  set: set\n}\n"
  },
  {
    "path": "lib/typeUtil.js",
    "content": "// Copyright 2013 The Obvious Corporation\n\n/**\n * @fileoverview Utility functions that convert plain javascript objects to\n *  Dynamo AttributeValue map (http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html)\n *  objects back and forth.\n */\n\nvar typ = require('typ')\nvar reserved = require('./reserved')\n\n/**\n * From http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html\n * - B: A Binary data type.\n * - BOOL: A Boolean data type.\n * - BS: A Binary Set data type.\n * - L: A List of attribute values.\n * - M: A Map of attribute values.\n * - N: A Number data type.\n * - NS: A Number Set data type.\n * - NULL: A Null data type.\n * - S: A String data type.\n * - SS: A String Set data type.\n *\n * @typedef {{\n *   B: (string|undefined),\n *   BOOL: (boolean|undefined),\n *   BS: (Array.<string>|undefined),\n *   M: (Object|undefined),\n *   L: (Array|undefined),\n *   N: (string|undefined),\n *   NS: (Array.<string>|undefined),\n *   NULL: (null|undefined),\n *   S: (string|undefined),\n *   SS: (Array.<string>|undefined)\n * }}\n */\nvar AWSAttributeValue\n\n/**\n * Convert Dynamo AttributeValue map object(s) to plain javascript object(s)\n *\n * @param {Object.<string,AWSAttributeValue>|Array.<Object.<string,AWSAttributeValue>>} object Dynamo AttributeValue map object(s)\n * @return {Object|Array.<Object>} plain javascript object(s)\n */\nfunction unpackObjectOrArray(object) {\n  if (typ.isNullish(object)) return object\n  if (Array.isArray(object)) return object.map(unpackObjectOrArray)\n\n  var item = {}\n  for (var key in object) {\n    item[key] = objectToValue(object[key])\n  }\n  return item\n}\n\n/**\n * Convert an object to a Dynamo AttributeValue map.\n *\n * @param {Object|Array.<Object>|undefined} object\n * @param {Object=} attributes an optional map of the attributes that need to convert.\n * @return {Object.<string,AWSAttributeValue>|Array.<Object.<string,AWSAttributeValue>>|null} The object in Dynamo AttributeValue map.\n */\nfunction packObjectOrArray(object, attributes) {\n  if (typ.isNullish(object)) return null\n  if (Array.isArray(object)) {\n    return object.map(function (obj) { return packObjectOrArray(obj, attributes) })\n  }\n\n  var newObj = {}\n  for (var key in object) {\n    if (attributes && !attributes[key]) continue\n    newObj[key] = valueToObject(object[key])\n  }\n  return newObj\n}\n\n/**\n * Convert a javascript primitive value to an AWS AttributeValue\n *\n * @param {boolean|number|string|Array} value\n * @return {AWSAttributeValue|null}\n */\nfunction valueToObject(value) {\n  var type = typeof value\n\n  switch (typeof value) {\n  case 'string':\n    return {S: value}\n  case 'boolean':\n    return {BOOL: Boolean(value)}\n  case 'number':\n    return {N: String(value)}\n  default:\n    if (Array.isArray(value)) {\n      var firstItemType = typeof value[0]\n\n      // check that all of the items are of the same type; that of the first element's\n      for (var i = 0; i < value.length; i++) {\n        if (typeof value[i] !== firstItemType) {\n          throw new Error('Inconsistent types in set! Expecting all types to be the same as the first element\\'s: ' + firstItemType)\n        }\n      }\n\n      if (firstItemType === 'string') {\n        return {SS: value}\n      } else if (firstItemType === 'number') {\n        var numArray = []\n        for (i = 0; i < value.length; i++) {\n          numArray.push(String(value[i]))\n        }\n\n        return {NS: numArray}\n      } else {\n        throw new Error('Invalid dynamo set value. Type: ' + firstItemType + ', Value: ' + value[0])\n      }\n    } else {\n      throw new Error('Invalid dynamo value. Type: ' + type + ', Value: ' + value)\n    }\n  }\n}\n\n/**\n * Get the type of an AWS AttributeValue\n * @param {!AWSAttributeValue} obj Dynamo AttributeValue.\n * @return {string}\n */\nfunction objectToType(obj) {\n  var objectType = Object.keys(obj)\n  if (objectType.length != 1) {\n    throw new Error('Expected only one key from Amazon object')\n  }\n\n  return objectType[0]\n}\n\n/**\n * Convert a Dynamo AttributeValue to a javascript primitive value\n *\n * @param {!AWSAttributeValue} obj\n * @return {string|number|Array.<string>|Array.<number>|boolean|Object} a javascript primitive value\n */\nfunction objectToValue(obj) {\n  switch (objectToType(obj)) {\n    case 'SS':\n      return (/** @type {Array.<string>} */(obj.SS))\n    case 'S':\n      return (/** @type {string} */(obj.S))\n    case 'BOOL':\n      return Boolean(obj.BOOL)\n    case 'NS':\n      return obj.NS.map(function (num) { return Number(num) })\n    case 'N':\n      return Number(obj.N)\n    case 'M':\n      var mapped = {}\n      for (var k in obj.M) {\n        mapped[k] = objectToValue(obj.M[k])\n      }\n      return mapped\n    case 'L':\n      return obj.L.map(objectToValue)\n    default:\n      throw new Error('Unexpected key: ' + objectToType(obj) + ' for attribute: ' + obj)\n  }\n}\n\n/**\n * @param {!AWSAttributeValue} obj\n * @return {boolean}\n */\nfunction objectIsEmpty(obj) {\n  return !obj || Object.keys(obj).length === 0\n}\n\n\n/**\n * @param {!AWSAttributeValue} obj\n * @return {boolean}\n */\nfunction objectIsNonEmptySet(obj) {\n  if (objectIsEmpty(obj)) return false\n\n  var type = objectToType(obj)\n  if (type != 'NS' && type != 'SS') return false\n\n  return Array.isArray(obj[type]) && obj[type].length > 0\n}\n\n/**\n * @param {!AWSAttributeValue} set\n * @param {!AWSAttributeValue} additions\n * @return {AWSAttributeValue}\n */\nfunction addToSet(set, additions) {\n  var type = objectToType(additions)\n  if (objectIsEmpty(set)) {\n    set = {}\n    set[type] = []\n  } else if (objectToType(set) === type) {\n    set = clone(set)\n  } else {\n    throw new Error('Type mismatch: type of set should match type of additions')\n  }\n\n  for (var i = 0; i < additions[type].length; i++) {\n    if (set[type].indexOf(additions[type][i]) == -1) {\n      set[type].push(additions[type][i])\n    }\n  }\n\n  return set\n}\n\n/**\n * @param {!AWSAttributeValue} set\n * @param {!AWSAttributeValue} deletions\n * @return {?AWSAttributeValue}\n */\nfunction deleteFromSet(set, deletions) {\n  var type = objectToType(deletions)\n  if (objectIsEmpty(set)) {\n    return null\n  } else if (objectToType(set) !== type) {\n    throw new Error('Type mismatch: type of set should match type of deletions')\n  }\n\n  set = clone(set)\n  for (var i = 0; i < deletions[type].length; i++) {\n    var idx = set[type].indexOf(deletions[type][i])\n    if (idx != -1) {\n      set[type].splice(idx, 1)\n    }\n  }\n\n  if (set[type].length) {\n    return set\n  } else {\n    return null\n  }\n}\n\n/**\n * @param {!AWSAttributeValue} number\n * @param {!AWSAttributeValue} addition\n * @return {AWSAttributeValue}\n */\nfunction addToNumber(number, addition) {\n  if (objectIsEmpty(number)) {\n    number = {'N': '0'}\n  } else {\n    number = clone(number)\n  }\n\n  if (objectToType(number) !== 'N' || objectToType(addition) !== 'N') {\n    throw new Error('Type mismatch: number and addition should both be numeric types')\n  }\n\n  number.N = String(Number(number.N) + Number(addition.N))\n\n  return number\n}\n\n/**\n * @param {!AWSAttributeValue} oldItem\n * @return {AWSAttributeValue}\n */\nfunction clone(oldItem) {\n  try {\n    var objectType = objectToType(oldItem)\n\n    var newItem = {}\n    if (Array.isArray(oldItem[objectType])) {\n      newItem[objectType] = oldItem[objectType].slice()\n    } else {\n      newItem[objectType] = oldItem[objectType]\n    }\n    return newItem\n  } catch (e) {\n    return {NULL:null}\n  }\n}\n\nvar VALID_ATTR_RE = /^[a-zA-Z][a-zA-Z0-9]*$/\n\n/**\n * @see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ExpressionPlaceholders.html#ExpressionAttributeNames\n * @return {string} The alias. May just be the key itself if an alias is not needed.\n */\nfunction getAttributeAlias(key) {\n  if (isReservedWord(key)) {\n    // If this is just a reserved word, use # + word\n    return '#' + key\n  }\n\n  if (!isAlphaNumeric(key)) {\n    // if this is not alphanumeric, hex-encode the string.\n    return '#' + new Buffer(key).toString('hex')\n  }\n\n  // otherwise, the key is valid in an expression\n  return key\n}\n\n/**\n * @return {boolean} True if this is a reserved word.\n */\nfunction isReservedWord(key) {\n  return (key.toUpperCase() in reserved.set)\n}\n\n/**\n * @return {boolean} True if this matches the alphanumeric regexp\n */\nfunction isAlphaNumeric(key) {\n  return VALID_ATTR_RE.test(key)\n}\n\n/**\n * @see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ExpressionPlaceholders.html#ExpressionAttributeNames\n * @return {boolean} True if we need an attribute alias.\n */\nfunction needsAttributeAlias(key) {\n  return isReservedWord(key) || !isAlphaNumeric(key)\n}\n\n/**\n * Given a list of attribute names, return an object suitable for ExpressionAttributeNames\n * @param {Array<string>} attrList\n * @return {Object}\n */\nfunction buildAttributeNames(attrList) {\n  var result = {}\n  attrList.map(function (key) {\n    if (needsAttributeAlias(key)) {\n      result[getAttributeAlias(key)] = key\n    }\n  }, this)\n  return result\n}\n\n/**\n * Extends the attribute names object with new names.\n */\nfunction extendAttributeNames(existingNames, newNames) {\n  for (var key in newNames) {\n    if (!existingNames[key]) {\n      existingNames[key] = newNames[key]\n    } else if (existingNames[key] != newNames[key]) {\n      throw new Error('Attribute name conflict ' + key)\n    }\n  }\n}\n\n/**\n * Extends the attribute values object with new values.\n */\nfunction extendAttributeValues(existingValues, newValues) {\n  for (var key in newValues) {\n    if (!existingValues[key]) {\n      existingValues[key] = newValues[key]\n    } else{\n      throw new Error('Attribute value conflict ' + key)\n    }\n  }\n}\n\nmodule.exports = {\n  AWSAttributeValue: AWSAttributeValue,\n\n  unpackObjectOrArray: unpackObjectOrArray,\n  packObjectOrArray: packObjectOrArray,\n  valueToObject: valueToObject,\n  objectToType: objectToType,\n  objectToValue: objectToValue,\n  objectIsEmpty: objectIsEmpty,\n  objectIsNonEmptySet: objectIsNonEmptySet,\n\n  getAttributeAlias: getAttributeAlias,\n  buildAttributeNames: buildAttributeNames,\n  extendAttributeNames: extendAttributeNames,\n  extendAttributeValues: extendAttributeValues,\n\n  addToSet: addToSet,\n  deleteFromSet: deleteFromSet,\n  addToNumber: addToNumber,\n  clone: clone\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"dynamite\",\n  \"description\": \"promise-based DynamoDB client\",\n  \"version\": \"1.0.0\",\n  \"homepage\": \"https://github.com/Medium/dynamite\",\n  \"license\": \"Apache-2.0\",\n  \"authors\": [\n    \"Jeremy Stanley <github@azulus.com> (https://github.com/azulus)\",\n    \"Jean Hsu <jean@medium.com> (https://github.com/jyhsu)\",\n    \"Jonathan Fuchs <jon@medium.com> (https://github.com/jfuchs)\",\n    \"Artem Titoulenko <artem.titoulenko@gmail.com> (https://github.com/ArtemTitoulenko)\",\n    \"Xiao Ma <xiao@medium.com> (https://github.com/x-ma)\",\n    \"Jamie Talbot <jamie@medium.com> (https://github.com/majelbstoat)\"\n  ],\n  \"keywords\": [\n    \"dynamite\",\n    \"dynamo\",\n    \"dynamodb\"\n  ],\n  \"main\": \"dynamite.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/Medium/dynamite.git\"\n  },\n  \"dependencies\": {\n    \"aws-sdk\": \"^2.368.0\",\n    \"kew\": \"git+https://github.com/Medium/kew#b8aaf9f\",\n    \"typ\": \"0.6.3\"\n  },\n  \"devDependencies\": {\n    \"eslint\": \"4.18.2\",\n    \"local-dynamo\": \"git+https://github.com/Medium/local-dynamo#4d8d3c0\",\n    \"nodeunit\": \"0.11.3\",\n    \"nodeunitq\": \"0.1.1\"\n  },\n  \"externDependencies\": {\n    \"aws-sdk\": \"./externs/aws-sdk.js\"\n  },\n  \"scripts\": {\n    \"test\": \"eslint . && ./node_modules/.bin/nodeunit test\"\n  }\n}\n"
  },
  {
    "path": "test/testBatchGetItem.js",
    "content": "// Copyright 2013 The Obvious Corporation.\n\nvar utils = require('./utils/testUtils.js')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\nvar Q = require('kew')\n\nfunction onError(err) {\n  console.error(err.stack)\n}\n\nvar userData = [\n  {'userId': 'userA', 'column': '@', 'age': '29'},\n  {'userId': 'userB', 'column': '@', 'age': '44'},\n  {'userId': 'userC', 'column': '@', 'age': '36'}\n]\n\nvar phoneData = [\n  {'userId': 'userA', 'column': 'phone1', 'number': '415-662-1234'},\n  {'userId': 'userA', 'column': 'phone2', 'number': '650-143-8899'},\n  {'userId': 'userB', 'column': 'phone1', 'number': '550-555-5555'}\n]\n\n// Generate many items that will exceed the batch get count limit\nvar manyData = []\nfor (var i = 0; i < 202; i++) {\n  manyData.push({'hashKey': 'id' + i, 'column': '@', 'data': 'small'})\n}\n\n// Generate big items that will exceed amount allowed to be returned.\nvar muchoData = []\nvar junk = new Array(62000).join('.')\nfor (i = 0; i < 101; i++) {\n  muchoData.push({'hashKey': 'id' + i, 'column': '@', 'data': junk})\n}\n\n// basic setup for the tests, creating record userA with range key @\nexports.setUp = function (done) {\n  this.db = utils.getMockDatabase()\n  this.client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n\n  var userTablePromise = utils.createTable(this.db, 'user', 'userId', 'column')\n    .thenBound(utils.initTable, null, {db: this.db, tableName: 'user', data: userData})\n\n  var phoneTablePromise = utils.createTable(this.db, 'phones', 'userId', 'column')\n    .thenBound(utils.initTable, null, {db: this.db, tableName: 'phones', data: phoneData})\n\n  var manyTablePromise = utils.createTable(this.db, 'pre_many', 'hashKey', 'column')\n    .thenBound(utils.initTable, null, {db: this.db, tableName: 'pre_many', data: manyData})\n\n  var muchoTablePromise = utils.createTable(this.db, 'mucho', 'hashKey', 'column')\n    .thenBound(utils.initTable, null, {db: this.db, tableName: 'mucho', data: muchoData})\n\n  Q.all([userTablePromise, phoneTablePromise, manyTablePromise, muchoTablePromise])\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  Q.all([\n    utils.deleteTable(this.db, 'user'),\n    utils.deleteTable(this.db, 'phones'),\n    utils.deleteTable(this.db, 'pre_many'),\n    utils.deleteTable(this.db, 'mucho')\n  ])\n  .fin(done)\n}\n\nbuilder.add(function testBatchGet(test) {\n  return this.client.newBatchGetBuilder()\n    .requestItems('user', [{'userId': 'userA', 'column': '@'}, {'userId': 'userB', 'column': '@'}])\n    .requestItems('phones', [{'userId': 'userA', 'column': 'phone1'}, {'userId': 'userB', 'column': 'phone1'}])\n    .execute()\n    .then(function (data) {\n      var ages = data.result.user.map(function (user) { return user.age })\n      test.deepEqual(ages, ['29', '44'])\n      var phones = data.result.phones.map(function (phone) { return phone.number })\n      test.deepEqual(phones, ['415-662-1234', '550-555-5555'])\n    })\n})\n\nbuilder.add(function testEmptyBatch(test) {\n  return this.client.newBatchGetBuilder()\n    .requestItems('user', [{'userId': 'userE', 'column': '@'}])\n    .execute()\n    .then(function (data) {\n      test.ok(Array.isArray(data.result.user), 'An array should be returned for requested tables')\n      test.equal(0, data.result.user.length, 'No items should have been returned')\n    })\n})\n\nbuilder.add(function testBatchGetMany(test) {\n  return this.client.newBatchGetBuilder()\n    .setPrefix('pre_')\n    .requestItems('many', manyData.map(function (o) { return {'hashKey': o.hashKey, 'column': '@'}}))\n    .execute()\n    .then(function (data) {\n      test.equal(202, data.result.many.length, 'All 202 items should be returned')\n      test.equal(0, Object.keys(data.UnprocessedKeys).length, 'There should be no unprocessed keys')\n    })\n})\n\nbuilder.add(function testBatchGetMucho(test) {\n  return this.client.newBatchGetBuilder()\n    .requestItems('mucho', muchoData.map(function (o) { return {'hashKey': o.hashKey, 'column': '@'}}))\n    .execute()\n    .then(function (data) {\n      test.equal(101, data.result.mucho.length, 'All 101 items should be returned')\n      test.equal(0, Object.keys(data.UnprocessedKeys), 'There should be no unprocessed keys')\n    })\n})\n\nbuilder.add(function testBatchGetBadKey(test) {\n  var client = this.client\n  return Q.fcall(\n    function () {\n      return client.newBatchGetBuilder()\n        .setPrefix('pre_')\n        .requestItems('many', manyData.map(function () { return {'hashKey': {}, 'column': '@'}}))\n        .execute()\n    })\n    .then(function () {\n        test.fail('Expected error')\n    })\n    .fail(function (err) {\n      if (err.message != 'Invalid dynamo value. Type: object, Value: [object Object]') throw err\n    })\n})\n"
  },
  {
    "path": "test/testConditions.js",
    "content": "// Copyright 2016 A Medium Corporation\n'use strict'\n\nvar utils = require('./utils/testUtils.js')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\n\nvar onError = console.error.bind(console)\nvar tableName = 'user'\nvar rawData = [\n  {userId: 'a', column: '@'},\n  {userId: 'b', column: '@', blacklistedAt: '1'},\n  {userId: 'c', column: '@', blacklistedAt: '1', unblacklistedAt: '2'},\n  {userId: 'd', column: '@', blacklistedAt: '3', unblacklistedAt: '2'},\n  {userId: 'e', column: '@', blacklistedAt: '4', unblacklistedAt: '4'}\n]\n\nvar db\nvar client\n\nexports.setUp = function (done) {\n  db = utils.getMockDatabase()\n  client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n  utils.createTable(db, tableName, 'userId', 'column')\n    .thenBound(utils.initTable, null, {\"db\": db, \"tableName\": tableName, \"data\": rawData})\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  utils.deleteTable(db, tableName)\n    .fin(done)\n}\n\nbuilder.add(function testEqualsAttribute(test) {\n  var filter = client.newConditionBuilder()\n    .filterAttributeEqualsAttribute('blacklistedAt', 'unblacklistedAt')\n  return assertUserIds(test, ['e'], filter)\n})\n\nbuilder.add(function testNotEqualsAttribute(test) {\n  var filter = client.newConditionBuilder()\n    .filterAttributeNotEqualsAttribute('blacklistedAt', 'unblacklistedAt')\n  return assertUserIds(test, ['a', 'b', 'c', 'd'], filter)\n})\n\nbuilder.add(function testLessThanAttribute(test) {\n  var filter = client.newConditionBuilder()\n    .filterAttributeLessThanAttribute('blacklistedAt', 'unblacklistedAt')\n  return assertUserIds(test, ['c'], filter)\n})\n\nbuilder.add(function testLessThanEqualAttribute(test) {\n  var filter = client.newConditionBuilder()\n    .filterAttributeLessThanEqualAttribute('blacklistedAt', 'unblacklistedAt')\n  return assertUserIds(test, ['c', 'e'], filter)\n})\n\nbuilder.add(function testGreaterThanAttribute(test) {\n  var filter = client.newConditionBuilder()\n    .filterAttributeGreaterThanAttribute('blacklistedAt', 'unblacklistedAt')\n  return assertUserIds(test, ['d'], filter)\n})\n\nbuilder.add(function testGreaterThanEqualAttribute(test) {\n  var filter = client.newConditionBuilder()\n    .filterAttributeGreaterThanEqualAttribute('blacklistedAt', 'unblacklistedAt')\n  return assertUserIds(test, ['d', 'e'], filter)\n})\n\nfunction assertUserIds(test, expectedUserIds, filter) {\n  return client.newScanBuilder(tableName)\n    .withFilter(filter)\n    .setLimit(10)\n    .execute()\n    .then(function (data) {\n      var userIds = data.result.map(function (r) {\n        return r.userId\n      })\n      test.deepEqual(expectedUserIds, userIds.sort())\n    })\n}\n"
  },
  {
    "path": "test/testDeleteItem.js",
    "content": "// Copyright 2013 The Obvious Corporation.\n\nvar utils = require('./utils/testUtils.js')\nvar typ = require('typ')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\n\nvar onError = console.error.bind(console)\nvar initialData = [{\"userId\": \"userA\", \"column\": \"@\", \"age\": \"29\"}]\n\n// basic setup for the tests, creating record userA with range key @\nexports.setUp = function (done) {\n  this.db = utils.getMockDatabase()\n  this.client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n  utils.createTable(this.db, \"user\", \"userId\", \"column\")\n    .thenBound(utils.initTable, null, {db: this.db, tableName: \"user\", data: initialData})\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  utils.deleteTable(this.db, \"user\")\n    .fin(done)\n}\n\n// check that an item can be deleted\nbuilder.add(function testDeleteExistingItem(test) {\n  var self = this\n\n  return this.client.deleteItem('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .execute()\n    .then(function () {\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item'], undefined, \"User should be deleted~~~\" + JSON.stringify(data))\n    })\n})\n\n// check that an item isn't inadvertently deleted when deleting another\nbuilder.add(function testDeleteNonexistingItem(test) {\n  var self = this\n\n  return this.client.deleteItem('user')\n    .setHashKey('userId', 'userB')\n    .setRangeKey('column', '@')\n    .execute()\n    .then(function () {\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(typ.isNullish(data['Item']), false, \"User should not be deleted\")\n    })\n})\n\n// check that an item matches a conditional when deleting\nbuilder.add(function testDeleteExistingItemWithConditional(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeEquals('column', '@')\n\n  return this.client.deleteItem('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .execute()\n    .then(function () {\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item'], undefined, \"User should be deleted\")\n    })\n})\n\n// check that an item matches an absent conditional when deleting\nbuilder.add(function testDeleteExistingItemWithConditionalAbsent(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeAbsent('height')\n\n  return this.client.deleteItem('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .execute()\n    .then(function () {\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item'], undefined, \"User should be deleted\")\n    })\n})\n\n// check that an item fails a conditional when deleting\nbuilder.add(function testDeleteExistingItemWithFailedConditional(test) {\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeEquals('column', 'bug')\n\n  return this.client.deleteItem('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .execute()\n    .then(function () {\n      test.fail(\"'testDeleteExistingItemWithFailedConditional' failed\")\n    })\n    .fail(this.client.throwUnlessConditionalError)\n})\n\n// check that an item fails an absent conditional when deleting\nbuilder.add(function testDeleteExistingItemWithFailedAbsentConditional(test) {\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeAbsent('age')\n\n  return this.client.deleteItem('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .execute()\n    .then(function () {\n      test.fail(\"'testDeleteExistingItemWithFailedAbsentConditional' failed\")\n    })\n    .fail(this.client.throwUnlessConditionalError)\n})\n\n// check that non-existent items can't be deleted if a conditional expects a value\nbuilder.add(function testDeleteNonexistingItemWithConditional(test) {\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeEquals('column', '@')\n\n  return this.client.deleteItem('user')\n    .setHashKey('userId', 'userB')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .execute()\n    .then(function () {\n      test.fail(\"'testDeleteNonexistingItemWithConditional' failed\")\n    })\n    .fail(this.client.throwUnlessConditionalError)\n})\n\n// check that non-existent items can't be deleted if a conditional expects a value\nbuilder.add(function testDeleteNonexistingItemWithConditionalAbsent(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeAbsent('column')\n\n  return this.client.deleteItem('user')\n    .setHashKey('userId', 'userB')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .execute()\n    .then(function () {\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(typ.isNullish(data['Item']), false, \"User should not be deleted\")\n    })\n})\n"
  },
  {
    "path": "test/testDescribeTable.js",
    "content": "// Copyright 2013 The Obvious Corporation.\n\nvar utils = require('./utils/testUtils.js')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\n\nvar onError = console.error.bind(console)\n\n// basic setup for the tests, creating record userA with range key @\nexports.setUp = function (done) {\n  this.db = utils.getMockDatabase()\n  this.client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n  utils.createTable(this.db, \"user\", \"userId\", \"column\")\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  utils.deleteTable(this.db, \"user\")\n    .fin(done)\n}\n\n// check that an item exists\nbuilder.add(function testSimpleDescribeTable(test) {\n  return this.client.describeTable(\"user\")\n    .execute()\n    .then(function (data) {\n      test.equal(data.Table.TableName, \"user\", \"Table name should be 'user'\")\n      test.equal(data.Table.KeySchema[0].AttributeName, \"userId\", \"Hash key name should be 'userId'\")\n      test.equal(data.Table.KeySchema[1].AttributeName, \"column\", \"Hash key name should be 'column'\")\n    })\n})\n"
  },
  {
    "path": "test/testFakeDynamo.js",
    "content": "// Copyright 2013 The Obvious Corporation\n\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\nvar Client = require('../lib/Client')\nvar FakeDynamo = require('../lib/FakeDynamo')\n\nvar userA = {\n  'userId': 'userA',\n  'column': '@',\n  'age': 29,\n  'luckyNumbers': [1, 3, 5]\n}\n\nvar db, client\nexports.setUp = function (done) {\n  db = new FakeDynamo()\n  client = new Client({dbClient: db})\n\n  var userTable = db.createTable('user')\n  userTable.setHashKey('userId', 'S')\n  userTable.setRangeKey('column', 'S')\n  userTable.setData(\n    JSON.parse(JSON.stringify({userA: {'@': userA}})))\n\n  var cookieTable = db.createTable('cookie')\n  cookieTable.setHashKey('cookieId', 'S')\n  cookieTable.setRangeKey('createdAt', 'S')\n\n  done()\n}\n\nexports.tearDown = function (done) {\n  done()\n}\n\nbuilder.add(function testConditionalUpdateFails(test) {\n  var conditions = client.newConditionBuilder()\n    .expectAttributeEquals('userId', 'gibberish')\n\n  return client.newUpdateBuilder('user')\n    .setHashKey('userId', 'gibberish')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .putAttribute('age', 30)\n    .execute()\n    .then(function () {\n      test.fail('Expected conditional error')\n    })\n    .fail(function (e) {\n      test.ok(client.isConditionalError(e))\n      test.ok(!!e.stack)\n      throw e\n    })\n    .fail(client.throwUnlessConditionalError)\n})\n\nbuilder.add(function testConditionalUpdateOk(test) {\n  var conditions = client.newConditionBuilder()\n    .expectAttributeEquals('userId', 'userA')\n\n  return client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .putAttribute('age', 30)\n    .execute()\n    .then(function () {\n      return client.getItem('user')\n        .setHashKey('userId', 'userA')\n        .setRangeKey('column', '@')\n        .execute()\n    })\n    .then(function (data) {\n      test.equal(data.result.age, 30, 'Age should match 30')\n    })\n})\n\nbuilder.add(function testAddToAttribute(test) {\n  var conditions = client.newConditionBuilder()\n    .expectAttributeEquals('userId', 'userA')\n\n  return client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .addToAttribute('luckyNumbers', [8])\n    .execute()\n    .then(function () {\n      return client.getItem('user')\n        .setHashKey('userId', 'userA')\n        .setRangeKey('column', '@')\n        .execute()\n    })\n    .then(function (data) {\n      data.result.luckyNumbers.sort()\n      test.deepEqual(data.result.luckyNumbers, [1, 3, 5, 8])\n    })\n})\n\nbuilder.add(function testBatchGetDupeKeys(test) {\n  // Real Dynamo throws an exception if a BatchGet has duplicate keys.\n  // Ruby FakeDynamo does not have this validation.\n  return client.newBatchGetBuilder()\n    .requestItems('user', [{'userId': 'userA', 'column': '@'},\n                           {'userId': 'userA', 'column': '@'}])\n    .execute()\n    .then(function () {\n      test.fail('Expected validation failure')\n    })\n    .fail(function (e) {\n      if (!/Provided list of item keys contains duplicates/.test(e.message)) {\n        throw e\n      }\n    })\n})\n\nbuilder.add(function testConditionalBuilderMethods(test) {\n  var expected = client.newConditionBuilder()\n    .expectAttributeEquals('userId', 'gibberish')\n    .expectAttributeAbsent('userId2')\n\n  var actual = client.conditions({userId: 'gibberish', 'userId2': null})\n  test.deepEqual(expected, actual)\n\n  test.done()\n})\n\nbuilder.add(function testScan(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '1', age: 27},\n        2: {userId: 'userA', column: '2', age: 28},\n        3: {userId: 'userA', column: '3', age: 29}\n    },\n    'userB': {\n        1: {userId: 'userB', column: '1', age: 29}\n    }\n  })\n  return client.newScanBuilder('user')\n      .execute()\n      .then(function (data) {\n        var result = data.result\n        test.deepEqual(result[0], {userId: 'userA', column: '1', age: 27})\n        test.deepEqual(result[1], {userId: 'userA', column: '2', age: 28})\n        test.deepEqual(result[2], {userId: 'userA', column: '3', age: 29})\n        test.deepEqual(result[3], {userId: 'userB', column: '1', age: 29})\n      })\n})\n\nbuilder.add(function testScanWithSimpleFilter(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '1', age: 27, name: 'Ringo'},\n        2: {userId: 'userA', column: '4', age: 28, name: 'George'},\n        3: {userId: 'userA', column: '3', age: 29, name: 'John'},\n        4: {userId: 'userA', column: '2', age: 30, name: 'Paul'}\n    }\n  })\n\n  var filter = client.newConditionBuilder()\n    .filterAttributeBeginsWith('name', 'Geo')\n\n  return client.newScanBuilder('user')\n    .withFilter(filter)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.length, 1)\n      test.deepEqual(data.result[0], {userId: 'userA', column: '4', age: 28, name: 'George'})\n    })\n})\n\nbuilder.add(function testScanWithComplexFilter(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '1', age: 27, name: 'Ringo'},\n        2: {userId: 'userA', column: '4', age: 28, name: 'George'},\n        3: {userId: 'userA', column: '3', age: 29, name: 'John'},\n        4: {userId: 'userA', column: '2', age: 30, name: 'Paul'}\n    }\n  })\n\n  var filter = client.andConditions([\n    client.newConditionBuilder().filterAttributeGreaterThan('age', 27),\n    client.newConditionBuilder().filterAttributeLessThan('age', 30)\n  ])\n\n  return client.newScanBuilder('user')\n    .withFilter(filter)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.length, 2)\n      test.deepEqual(data.result[0], {userId: 'userA', column: '4', age: 28, name: 'George'})\n      test.deepEqual(data.result[1], {userId: 'userA', column: '3', age: 29, name: 'John'})\n    })\n})\n\nbuilder.add(function testScanWithLimit(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '1', age: 27},\n        2: {userId: 'userA', column: '2', age: 28},\n        3: {userId: 'userA', column: '3', age: 29}\n    }\n  })\n  return client.newScanBuilder('user')\n      .setLimit(2)\n      .execute()\n      .then(function (data) {\n        var result = data.result\n        test.deepEqual(result[0], {userId: 'userA', column: '1', age: 27})\n        test.deepEqual(result[1], {userId: 'userA', column: '2', age: 28})\n        test.deepEqual(data.LastEvaluatedKey, {userId: 'userA', column: '2'})\n      })\n})\n\nbuilder.add(function testScanWithStartKey(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '1', age: 27},\n        2: {userId: 'userA', column: '2', age: 28},\n        3: {userId: 'userA', column: '3', age: 29}\n    },\n    'userB': {\n        1: {userId: 'userB', column: '1', age: 29}\n    }\n  })\n  return client.newScanBuilder('user')\n      .setStartKey({userId: 'userA', column: '2'})\n      .execute()\n      .then(function (data) {\n        var result = data.result\n        test.deepEqual(result[0], {userId: 'userA', column: '3', age: 29})\n        test.deepEqual(result[1], {userId: 'userB', column: '1', age: 29})\n      })\n})\n\n\n// test querying secondary index using greater than condition\nbuilder.add(function testQueryOnSecondaryIndexGreaterThan(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: 3, age: 27},\n        2: {userId: 'userA', column: 2, age: 28},\n        3: {userId: 'userA', column: 5, age: 3000},\n        4: {userId: 'userA', column: 4, age: 29}\n    },\n    'userB': {\n        1: {userId: 'userB', column: '1', age: 29}\n    }\n  })\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexGreaterThan('age', 28)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.length, 2, '2 results should be returned')\n      test.equal(data.result[0].age, 29, 'First entry should be 29')\n      test.equal(data.result[1].age, 3000, 'Second entry should be 3000')\n    })\n})\n\n// test querying secondary index using less than condition\nbuilder.add(function testQueryOnSecondaryIndexLessThan(test) {\n  db.getTable('user').setData({\n    'userA': {\n        0: {userId: 'userA', column: 0, age: 27},\n        1: {userId: 'userA', column: 3, age: 26},\n        2: {userId: 'userA', column: 2, age: 28},\n        3: {userId: 'userA', column: 1, age: 30},\n        4: {userId: 'userA', column: 4, age: 29}\n    },\n    'userB': {\n        1: {userId: 'userB', column: '1', age: 29}\n    }\n  })\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexLessThan('age', 29)\n    .scanBackward()\n    .execute()\n    .then(function (data) {\n      test.equal(data.result[0].age, 28, 'First entry should be 28')\n      test.equal(data.result[1].age, 27, 'Second entry should be 27')\n      test.equal(data.result[2].age, 26, 'Third entry should be 26')\n      test.equal(data.result.length, 3, '3 results should be returned')\n    })\n})\n\n// test querying secondary index using less than equals condition\nbuilder.add(function testQueryOnSecondaryIndexLessThanEquals(test) {\n  db.getTable('user').setData({\n    'userA': {\n        0: {userId: 'userA', column: 0, age: 27},\n        1: {userId: 'userA', column: 3, age: 26},\n        2: {userId: 'userA', column: 2, age: 28},\n        3: {userId: 'userA', column: 1, age: 29},\n        4: {userId: 'userA', column: 4, age: 30}\n    },\n    'userB': {\n        1: {userId: 'userB', column: '1', age: 29}\n    }\n  })\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexLessThanEqual('age', 29)\n    .scanBackward()\n    .execute()\n    .then(function (data) {\n      test.equal(data.result[0].age, 29, 'First entry should be 29')\n      test.equal(data.result[1].age, 28, 'Second entry should be 28')\n      test.equal(data.result[2].age, 27, 'Third entry should be 27')\n      test.equal(data.result[3].age, 26, 'Fourth entry should be 26')\n      test.equal(data.result.length, 4, '4 results should be returned')\n    })\n})\n\n\n// test querying secondary index using equals condition\nbuilder.add(function testQueryOnSecondaryIndexEquals(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: 3, age: 27},\n        2: {userId: 'userA', column: 2, age: 28},\n        3: {userId: 'userA', column: 1, age: 29},\n        4: {userId: 'userA', column: 4, age: 30}\n    },\n    'userB': {\n        1: {userId: 'userB', column: '1', age: 29}\n    }\n  })\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexEqual('age', 28)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result[0].age, 28, 'Age should match 28')\n      test.equal(data.result.length, 1, '1 result should be returned')\n    })\n})\n\n// test querying secondary index using equals condition\nbuilder.add(function testQueryOnGlobalSecondaryIndexEquals(test) {\n  db.getTable('user').setGsiDefinitions([\n    {\n      hash: {\n        name: 'age',\n        type: 'S'\n      },\n      range: {\n        name: 'userId',\n        type: 'S'\n      }\n    }\n  ])\n\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: 3, age: 27},\n        2: {userId: 'userA', column: 2, age: 28},\n        3: {userId: 'userA', column: 1, age: 29},\n        4: {userId: 'userA', column: 4, age: 30}\n    },\n    'userB': {\n        1: {userId: 'userB', column: '1', age: 29}\n    }\n  })\n  return client.newQueryBuilder('user')\n    .setHashKey('age', 27)\n    .setIndexName('age-userId-index')\n    .indexBeginsWith('userId', 'user')\n    .execute()\n    .then(function (data) {\n      test.equal(data.result[0].age, 27, 'Age should match 28')\n      test.equal(data.result.length, 1, '1 result should be returned')\n    })\n})\n\n// test querying secondary index that have repeated column values\n// this is a test for a regression where fake dynamo may reinsert values\n// when the keys match again\nbuilder.add(function testQueryOnMultipleIndexes(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '1', age: 27},\n        2: {userId: 'userA', column: '4', age: 28},\n        3: {userId: 'userA', column: '3', age: 29},\n        4: {userId: 'userA', column: '2', age: 30},\n        5: {userId: 'userA', column: '5', age: 30}\n    },\n    'userB': {\n        1: {userId: 'userB', column: '1', age: 29}\n    }\n  })\n\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexGreaterThanEqual('age', 28)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result[0].age, 28, 'Age should match 28')\n      test.equal(data.result[1].age, 29, 'Age should match 29')\n      test.equal(data.result[2].age, 30, 'Age should match 30')\n      test.equal(data.result[3].age, 30, 'Age should match 30')\n      test.equal(data.result.length, 4, '4 results should be returned')\n    })\n})\n\n/**\n * Basic test for Global Secondary Index support\n * Does not currently support testing for existence of GSIs\n */\nbuilder.add(function testQueryOnGlobalSecondaryIndexes(test) {\n  db.getTable('user').setGsiDefinitions([\n    {\n      hash: {\n        name: 'age',\n        type: 'S'\n      },\n      range: {\n        name: 'height',\n        type: 'N'\n      }\n    }\n  ])\n\n  db.getTable('user').setHashKey('userId', 'S')\n    .setData({\n    'userA': {\n        1: {userId: 'userA', column: '3', age: 27, height: 160},\n        2: {userId: 'userA', column: '2', age: 28, height: 170},\n        3: {userId: 'userA', column: '1', age: 28, height: 180},\n        4: {userId: 'userA', column: '4', age: 29, height: 150}\n    },\n    'userB': {\n        1: {userId: 'userB', column: '3', age: 27, height: 200},\n        2: {userId: 'userB', column: '2', age: 28, height: 170},\n        3: {userId: 'userB', column: '1', age: 28, height: 178},\n        4: {userId: 'userB', column: '4', age: 29, height: 190}\n    }\n  })\n\n  return client.newQueryBuilder('user')\n    .setHashKey('age', 28)\n    // It is important that the index name has three or more terms (separated by\n    // '-'), it's a DynamoDB index naming convention, and it is how we know that it\n    // is a GSI query\n    .setIndexName('age-height-gsi')\n    .indexGreaterThan('height', 175)\n    .execute()\n    .then(function (data) {\n      // results from userB\n      test.equal(data.result[0].age, 28, 'Age should match 28')\n      test.equal(data.result[0].height, 178, 'Height should match 178')\n\n      // results from userA\n      test.equal(data.result[1].age, 28, 'Age should match 28')\n      test.equal(data.result[1].height, 180, 'Height should match 180')\n\n      test.equal(data.result.length, 2, '2 results should be returned')\n    })\n})\n\n// Ensure results are sorted by range key, even when there is no condition\n// on the range key\nbuilder.add(function testQueryOnGlobalSecondaryIndexWithoutCondition(test) {\n  db.getTable('user').setGsiDefinitions([\n    {\n      hash: {\n        name: 'userId',\n        type: 'S'\n      },\n      range: {\n        name: 'height',\n        type: 'N'\n      }\n    }\n  ])\n\n  db.getTable('user')\n    .setHashKey('userId', 'S')\n    .setData({\n    'userA': {\n        0: {userId: 'userA', column: '0', age: 26, height: 188},\n        1: {userId: 'userA', column: '1', age: 27, height: 160},\n        2: {userId: 'userA', column: '2', age: 28, height: 170},\n        3: {userId: 'userA', column: '3', age: 28, height: 180},\n        4: {userId: 'userA', column: '4', age: 29, height: 150}\n    }\n  })\n\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    // It is important that the index name has three or more terms (separated by\n    // '-'), it's a DynamoDB index naming convention, and it is how we know that it\n    // is a GSI query\n    .setIndexName('userId-height-gsi')\n    .scanBackward()\n    .execute()\n    .then(function (data) {\n      var heights = data.result.map(function (r) { return r.height })\n      test.deepEqual(heights, [188, 180, 170, 160, 150])\n    })\n})\n\nbuilder.add(function testQueryWithLimit(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '3', age: 27},\n        2: {userId: 'userA', column: '2', age: 28},\n        3: {userId: 'userA', column: '1', age: 29},\n        4: {userId: 'userA', column: '4', age: 30}\n    }\n  })\n\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexGreaterThanEqual('age', 28)\n    .setLimit(2)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result[0].age, 28)\n      test.equal(data.result[1].age, 29)\n      test.equal(data.result.length, 2)\n      test.deepEqual(data.LastEvaluatedKey, {userId: 'userA', column: '1'})\n\n      return client.newQueryBuilder('user')\n        .setStartKey(data.LastEvaluatedKey)\n        .setHashKey('userId', 'userA')\n        .setIndexName('age-index')\n        .indexGreaterThanEqual('age', 28)\n        .execute()\n    })\n    .then(function (data) {\n      test.equal(data.result[0].age, 30, 'Age should match 30')\n      test.equal(data.result.length, 1, '1 result should be returned')\n    })\n})\n\nbuilder.add(function testQueryWithNext(test) {\n db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '3', age: 27},\n        2: {userId: 'userA', column: '2', age: 28},\n        3: {userId: 'userA', column: '1', age: 29},\n        4: {userId: 'userA', column: '4', age: 30}\n    }\n  })\n\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexGreaterThanEqual('age', 25)\n    .scanBackward()\n    .setLimit(3)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.length, 3)\n      test.equal(data.result[0].age, 30)\n      test.equal(data.result[1].age, 29)\n      test.equal(data.result[2].age, 28)\n      test.ok(data.hasNext())\n\n      return data.next()\n    })\n    .then(function (data) {\n      test.equal(data.result.length, 1)\n      test.equal(data.result[0].age, 27)\n      test.ok(!data.hasNext())\n    })\n})\n\nbuilder.add(function testQueryWithLimitBackwards(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '3', age: 27},\n        2: {userId: 'userA', column: '2', age: 28},\n        3: {userId: 'userA', column: '1', age: 29},\n        4: {userId: 'userA', column: '4', age: 30}\n    }\n  })\n\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .scanBackward()\n    .indexLessThan('age', 30)\n    .setLimit(2)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.length, 2)\n      test.equal(data.result[0].age, 29)\n      test.equal(data.result[1].age, 28)\n      test.deepEqual(data.LastEvaluatedKey, {userId: 'userA', column: '2'})\n\n      return client.newQueryBuilder('user')\n        .setStartKey(data.LastEvaluatedKey)\n        .setHashKey('userId', 'userA')\n        .setIndexName('age-index')\n        .indexLessThan('age', 30)\n        .scanBackward()\n        .execute()\n    })\n    .then(function (data) {\n      test.equal(data.result[0].age, 27)\n      test.equal(data.result.length, 1)\n    })\n})\n\nbuilder.add(function testQueryWithMaxResultSize(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '1', age: 27},\n        2: {userId: 'userA', column: '4', age: 28},\n        3: {userId: 'userA', column: '3', age: 29},\n        4: {userId: 'userA', column: '2', age: 30}\n    }\n  })\n  db.getTable('user').setMaxResultSetSize(1)\n\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexGreaterThanEqual('age', 28)\n    .setLimit(2)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.length, 1, '1 result should be returned')\n      test.equal(data.result[0].age, 28, 'Age should match 28')\n      test.deepEqual(data.LastEvaluatedKey, {userId: 'userA', column: '4'})\n    })\n})\n\nbuilder.add(function testDescribeTable(test) {\n  return client.describeTable('user')\n    .execute()\n    .then(function (data) {\n      var tableDescription = data.Table\n      var attributes = tableDescription.AttributeDefinitions\n      var keySchema = tableDescription.KeySchema\n\n      test.ok(tableDescription, 'Table description should exist.')\n      test.equal(attributes.length, 2, 'Table should have 2 attributes in AttributeDefinitions (keys).')\n      test.equal(keySchema.length, 2, 'Table should have 2 attributes in KeySchema (keys).')\n      test.equal(tableDescription.TableName, 'user', 'Table name should be user.')\n      test.equal(tableDescription.TableStatus, 'ACTIVE', 'Table status should be active.')\n\n      // deep check attributes\n      for (var i = 0; i < attributes.length; i++) {\n        var attribute = attributes[i]\n        if (attribute.AttributeName == 'userId') {\n          test.deepEqual(attribute, {AttributeName: 'userId', AttributeType: 'S'})\n        } else if (attribute.AttributeName == 'column') {\n          test.deepEqual(attribute, {AttributeName: 'column', AttributeType: 'S'})\n        }\n      }\n\n      // deep check key schemas\n      for (i = 0; i < keySchema.length; i++) {\n        var key = keySchema[i]\n        if (key.AttributeName == 'userId') {\n          test.deepEqual(key, {AttributeName: 'userId', KeyType: 'HASH'})\n        } else if (key.AttributeName == 'column') {\n          test.deepEqual(key, {AttributeName: 'column', KeyType: 'RANGE'})\n        }\n      }\n\n      test.expect(9) // make sure the tests in conditionals ran\n    })\n})\n\nbuilder.add(function testQueryFiltering(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '1', age: 27, name: 'Ringo'},\n        2: {userId: 'userA', column: '4', age: 28, name: 'George'},\n        3: {userId: 'userA', column: '3', age: 29, name: 'John'},\n        4: {userId: 'userA', column: '2', age: 30, name: 'Paul'}\n    }\n  })\n\n  var filter = client.newConditionBuilder()\n    .filterAttributeBeginsWith('name', 'Geo')\n\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexGreaterThanEqual('age', 28)\n    .withFilter(filter)\n    .execute()\n    .then(function (data) {\n      test.deepEqual(data.result[0], {userId: 'userA', column: '4', age: 28, name: 'George'})\n      test.equal(data.result.length, 1)\n    })\n})\n\nbuilder.add(function testQueryFilterNotNull(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '1', age: 27, name: 'Ringo'},\n        2: {userId: 'userA', column: '4', age: 28, name: 'George'},\n        3: {userId: 'userA', column: '3', age: 29},\n        4: {userId: 'userA', column: '2', age: 30}\n    }\n  })\n\n  var filter = client.newConditionBuilder()\n    .filterAttributeNotNull('name')\n\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexGreaterThanEqual('age', 28)\n    .withFilter(filter)\n    .execute()\n    .then(function (data) {\n      test.deepEqual(data.result[0], {userId: 'userA', column: '4', age: 28, name: 'George'})\n      test.equal(data.result.length, 1)\n    })\n})\n\nbuilder.add(function testBooleanQueryFilter(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '1', age: 27, isHappy: true},\n        2: {userId: 'userA', column: '4', age: 28, isHappy: false},\n        3: {userId: 'userA', column: '3', age: 29, isHappy: true},\n        4: {userId: 'userA', column: '2', age: 30}\n    }\n  })\n\n  var filter = client.newConditionBuilder()\n    .filterAttributeEquals('isHappy', true)\n\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexGreaterThanEqual('age', 27)\n    .withFilter(filter)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.length, 2)\n      test.deepEqual(data.result[0], {userId: 'userA', column: '1', age: 27, isHappy: true})\n      test.deepEqual(data.result[1], {userId: 'userA', column: '3', age: 29, isHappy: true})\n    })\n})\n\nbuilder.add(function testQueryFilterWithPartitionKeyThrowsError(test) {\n  var filter = client.newConditionBuilder()\n    .filterAttributeNotEquals('userId', 'Me')\n\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexGreaterThanEqual('age', 28)\n    .withFilter(filter)\n    .execute()\n    .then(function () {\n      test.fail('Expected validation exception')\n    })\n    .fail(function (e) {\n      if (!client.isValidationError(e)) throw e\n    })\n})\n\nbuilder.add(function testQueryFilterWithIndexKeyThrowsError(test) {\n  var filter = client.newConditionBuilder()\n    .filterAttributeNotEquals('age', 1)\n\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setIndexName('age-index')\n    .indexGreaterThan('age', 15)\n    .withFilter(filter)\n    .execute()\n    .then(function () {\n      test.fail('Expected validation exception')\n    })\n    .fail(function (e) {\n      if (!client.isValidationError(e)) throw e\n    })\n})\n\nbuilder.add(function testQueryFilterWithRangeKeyThrowsError(test) {\n  var filter = client.newConditionBuilder()\n    .filterAttributeLessThan('createdAt', 30)\n\n  return client.newQueryBuilder('cookie')\n    .setHashKey('cookieId', 'CookieA')\n    .indexGreaterThan('createdAt', 15)\n    .withFilter(filter)\n    .execute()\n    .then(function () {\n      test.fail('Expected validation exception')\n    })\n    .fail(function (e) {\n      if (!client.isValidationError(e)) throw e\n    })\n})\n\nbuilder.add(function testQueryFilterOnGsiIndexThrowsError(test) {\n  db.getTable('cookie').setGsiDefinitions([\n    {\n      hash: {\n        name: 'cookieType',\n        type: 'S'\n      },\n      range: {\n        name: 'orderedAt',\n        type: 'N'\n      }\n    }\n  ])\n\n  var filter = client.newConditionBuilder()\n    .filterAttributeLessThan('orderedAt', 30)\n\n  return client.newQueryBuilder('cookie')\n    .setHashKey('cookieType', 'Oreo')\n    .setIndexName('cookieType-orderedAt-gsi')\n    .indexGreaterThanEqual('orderedAt', 28)\n    .withFilter(filter)\n    .execute()\n    .then(function () {\n      test.fail('Expected validation exception')\n    })\n    .fail(function (e) {\n      if (!client.isValidationError(e)) throw e\n    })\n})\n\nbuilder.add(function testQueryFilterOnGsiWithNoRangeWorks(test) {\n  db.getTable('cookie').setGsiDefinitions([\n    {\n      hash: {\n        name: 'cookieType',\n        type: 'S'\n      }\n    }\n  ])\n\n  db.getTable('cookie').setData({\n    'cookieA': {\n        1: {cookieId: 'cookieA', column: '1', createdAt: 1, orderedAt: 27, cookieType: 'Oreo'},\n        2: {cookieId: 'cookieA', column: '2', createdAt: 2, orderedAt: 29, cookieType: 'Oreo'}\n    },\n    'cookieB': {\n        1: {cookieId: 'cookieB', column: '1', createdAt: 2, orderedAt: 12, cookieType: 'Snickerdoodle'},\n        2: {cookieId: 'cookieB', column: '2', createdAt: 1, orderedAt: 14, cookieType: 'Snickerdoodle'}\n    }\n  })\n\n  var filter = client.newConditionBuilder()\n    .filterAttributeLessThan('orderedAt', 28)\n\n  return client.newQueryBuilder('cookie')\n    .setHashKey('cookieType', 'Oreo')\n    .setIndexName('index-cookieType-gsi')\n    .withFilter(filter)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.length, 1)\n      test.deepEqual(data.result[0], {cookieId: 'cookieA', column: '1', createdAt: 1, orderedAt: 27, cookieType: 'Oreo'})\n    })\n})\n\nbuilder.add(function testQueryFilterWithRangeWithIndexDoesNotThrowsError(test) {\n  var filter = client.newConditionBuilder()\n    .filterAttributeLessThan('createdAt', 30)\n\n  return client.newQueryBuilder('cookie')\n    .setHashKey('cookieId', 'CookieA')\n    .setIndexName('age-index')\n    .indexGreaterThanEqual('age', 28)\n    .withFilter(filter)\n    .execute()\n    .then(function () {\n      test.ok(true)\n    })\n    .fail(function () {\n      test.fail('Expected not to fail')\n    })\n})\n\nbuilder.add(function testDeleteItem(test) {\n  var conditions = client.newConditionBuilder()\n    .expectAttributeEquals('userId', 'userA')\n\n  return client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .deleteAttribute('age')\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.column, '@')\n      test.equal(data.result.age, undefined)\n      return client.getItem('user')\n        .setHashKey('userId', 'userA')\n        .setRangeKey('column', '@')\n        .execute()\n    })\n    .then(function (data) {\n      test.equal(data.result.age, undefined)\n    })\n})\n\nbuilder.add(function testPutAttributeNonExisting(test) {\n  return client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userB')\n    .setRangeKey('column', '@')\n    .enableUpsert()\n    .putAttribute('age', 30)\n    .putAttribute('height', 72)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.age, 30, 'result age should be 30')\n      test.equal(data.result.height, 72, 'result height should be 72')\n      return client.getItem('user')\n        .setHashKey('userId', 'userB')\n        .setRangeKey('column', '@')\n        .execute()\n    })\n    .then(function (data) {\n      test.equal(data.result.age, 30, 'result age should be 30')\n      test.equal(data.result.height, 72, 'result height should be 72')\n    })\n})\n\nbuilder.add(function testDeleteItemFromSet(test) {\n  var conditions = client.newConditionBuilder()\n    .expectAttributeEquals('userId', 'userA')\n\n  return client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .deleteFromAttribute('luckyNumbers', [3])\n    .execute()\n    .then(function () {\n      return client.getItem('user')\n        .setHashKey('userId', 'userA')\n        .setRangeKey('column', '@')\n        .execute()\n    })\n    .then(function (data) {\n      data.result.luckyNumbers.sort()\n      test.deepEqual(data.result.luckyNumbers, [1, 5])\n    })\n})\n\nbuilder.add(function testLongKey(test) {\n  // Create a string 2^10 chars long.\n  var str = '.'\n  for (var i = 0; i < 10; i++) {\n    str = str + str\n  }\n\n  return client.getItem('user')\n      .setHashKey('userId', 'userA')\n      .setRangeKey('column', str)\n      .execute()\n  .then(function () {\n    test.fail('Expected validation exception')\n  })\n  .fail(function (e) {\n    if (!client.isValidationError(e)) throw e\n  })\n})\n\nbuilder.add(function testQueryFilterIn(test) {\n  db.getTable('user').setData({\n    'userA': {\n        1: {userId: 'userA', column: '1', age: 27, name: 'Ringo'},\n        2: {userId: 'userA', column: '4', age: 28, name: 'George'},\n        3: {userId: 'userA', column: '3', age: 29, name: 'Paul'},\n        4: {userId: 'userA', column: '2', age: 30}\n    }\n  })\n\n  var filter = client.newConditionBuilder()\n    .filterAttributeIn('name', ['Ringo', 'George'])\n\n  return client.newQueryBuilder('user')\n    .setHashKey('userId', 'userA')\n    .withFilter(filter)\n    .execute()\n    .then(function (data) {\n      test.deepEqual(['George', 'Ringo'], data.result.map(function (r) {\n        return r.name\n      }).sort())\n    })\n})\n\nbuilder.add(function testAbsentConditionUpdateSuccess(test) {\n  var conditions = client.newConditionBuilder()\n       .expectAttributeAbsent('userId')\n\n  return client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userNew')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .execute()\n    .then(function () {\n      return client.getItem('user')\n        .setHashKey('userId', 'userNew')\n        .setRangeKey('column', '@')\n        .execute()\n    })\n    .then(function (data) {\n      test.deepEqual({userId: 'userNew', column: '@'}, data.result)\n    })\n})\n\nbuilder.add(function testAbsentConditionUpdateFail(test) {\n  var conditions = client.newConditionBuilder()\n       .expectAttributeAbsent('userId')\n\n  return client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .execute()\n    .then(function () {\n      test.fail('Expected error')\n    })\n    .fail(client.throwUnlessConditionalError)\n})\n"
  },
  {
    "path": "test/testGetItem.js",
    "content": "// Copyright 2013 The Obvious Corporation.\n\nvar utils = require('./utils/testUtils.js')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\n\nvar onError = console.error.bind(console)\nvar initialData = [{\"userId\": \"userA\", \"column\": \"@\", \"age\": \"29\"}]\n\n// basic setup for the tests, creating record userA with range key @\nexports.setUp = function (done) {\n  this.db = utils.getMockDatabase()\n  this.client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n  utils.createTable(this.db, \"user\", \"userId\", \"column\")\n    .thenBound(utils.initTable, null, {db: this.db, tableName: \"user\", data: initialData})\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  utils.deleteTable(this.db, \"user\")\n    .fin(done)\n}\n\n// check that an item exists\nbuilder.add(function testItemExists(test) {\n  return this.client.getItem('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.age, 29, 'Age should match the provided age')\n    })\n})\n\n// check that only selected attributes are returned\nbuilder.add(function testSelectedAttributes(test) {\n  return this.client.getItem('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .selectAttributes(['userId', 'column'])\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.column, '@', 'Column should be defined')\n      test.equal(data.result.age, undefined, 'Age should be undefined')\n    })\n})\n\n// check that an item doesn't exist\nbuilder.add(function testItemDoesNotExist(test) {\n  return this.client.getItem('user')\n    .setHashKey('userId', 'userB')\n    .setRangeKey('column', '@')\n    .execute()\n    .then(function (data) {\n      test.equal(data.result, undefined, 'Record should not exist')\n    })\n})\n"
  },
  {
    "path": "test/testGetSet.js",
    "content": "var utils = require('./utils/testUtils.js')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\n\nvar onError = console.error.bind(console)\nvar initialData = [  {\"userId\": \"userA\", \"column\": \"@\", \"postIds\": ['1a', '1b', '1c']}\n                   , {\"userId\": \"userB\", \"column\": \"@\", \"postIds\": [1, 2, 3]}\n                   , {\"userId\": \"userC\", \"column\": \"@\"}]\n\n/*\n * Sets up for test, and creates a record userA with range key @.\n */\nexports.setUp = function (done) {\n  this.db = utils.getMockDatabase()\n  this.client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n  utils.createTable(this.db, \"user\", \"userId\", \"column\")\n    .thenBound(utils.initTable, null, {db: this.db, tableName: \"user\", data: initialData})\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  utils.deleteTable(this.db, \"user\")\n    .fin(done)\n}\n\n// get the set of strings\nbuilder.add(function testStringSetRetrieve(test) {\n  return this.client.getItem('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .execute()\n    .then(function (data) {\n      test.deepEqual(data.result.postIds, ['1a', '1b', '1c'], \"postIds should be ['1a', '1b', '1c']\")\n    })\n})\n\n// get the set of numbers\nbuilder.add(function testNumberSetRetrieve(test) {\n  return this.client.getItem('user')\n    .setHashKey('userId', 'userB')\n    .setRangeKey('column', '@')\n    .execute()\n    .then(function (data) {\n      test.deepEqual(data.result.postIds, [1, 2, 3], \"postIds should be [1, 2, 3]\")\n    })\n})\n\n// get a set that doesn't exist\nbuilder.add(function testSetDoesNotExist(test) {\n  return this.client.getItem('user')\n    .setHashKey('userId', 'userC')\n    .setRangeKey('column', '@')\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.postIds, undefined, \"postIds should not exist for userC\")\n    })\n})\n"
  },
  {
    "path": "test/testHashKeyOnly.js",
    "content": "// Copyright 2013 The Obvious Corporation.\nvar utils = require('./utils/testUtils.js')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\n\nvar onError = function (err) {\n  console.error(err.stack)\n}\nvar initialData = [{\"userId\": \"userA\", \"column\": \"@\", \"age\": \"29\"}]\n\n// basic setup for the tests, creating record userA with range key @\nexports.setUp = function (done) {\n  var self = this\n  this.db = utils.getMockDatabase()\n  this.client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n  utils.createTable(self.db, \"userRangeOnly\", \"userId\")\n    .thenBound(utils.initTable, null, {db: self.db, tableName: \"userRangeOnly\", data: initialData})\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  utils.deleteTable(this.db, 'userRangeOnly')\n    .fin(done)\n}\n\n// check that an item exists\nbuilder.add(function testItemExists(test) {\n  return this.client.getItem('userRangeOnly')\n    .setHashKey('userId', 'userA')\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.age, 29, 'Age should match the provided age')\n    })\n})\n\n// put an item and check that it exists\nbuilder.add(function testSimplePut(test) {\n  var self = this\n  return this.client.putItem(\"userRangeOnly\", {\n    userId: 'userB',\n    column: '@',\n    age: 30\n  })\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userB\", null, 'userRangeOnly')\n  })\n  .then(function (data) {\n    test.equal(data['Item']['age'].N, \"30\", \"Age should be set\")\n  })\n})\n\n// put a list of strings and check if they exist\nbuilder.add(function testStringSetPut(test) {\n  var self = this\n  return this.client.putItem(\"userRangeOnly\", {\n    userId: 'userC',\n    column: '@',\n    postIds: ['3a', '3b', '3c']\n  })\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userC\", null, 'userRangeOnly')\n  })\n  .then(function (data) {\n    test.deepEqual(data['Item']['postIds'].SS, ['3a', '3b', '3c'], \"postIds should be ['3a', '3b', '3c']\")\n  })\n})\n\n// test putting an attribute for an existing record\nbuilder.add(function testPutAttributeExisting(test) {\n  var self = this\n\n  return this.client.newUpdateBuilder('userRangeOnly')\n    .setHashKey('userId', 'userA')\n    .enableUpsert()\n    .putAttribute('age', 30)\n    .putAttribute('height', 72)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.age, 30, 'result age should be 30')\n      test.equal(data.result.height, 72, 'height should be 72')\n      return utils.getItemWithSDK(self.db, \"userA\", null, 'userRangeOnly')\n    })\n    .then(function (data) {\n      test.equal(data['Item']['age'].N, \"30\", \"result age should be 30\")\n      test.equal(data['Item']['height'].N, \"72\", \"height should be 72\")\n    })\n})\n\n\nbuilder.add(function testDeleteItem(test) {\n  //AWS.config.logger = process.stdout\n\n  var self = this\n  return self.client.deleteItem('userRangeOnly')\n    .setHashKey('userId', 'userA')\n    .execute()\n    .then(function () {\n      return utils.getItemWithSDK(self.db, \"userA\", null, \"userRangeOnly\")\n    })\n    .then(function (data) {\n      test.equal(data['Item'], undefined, \"User should be deleted~~~\" + JSON.stringify(data))\n    })\n})\n"
  },
  {
    "path": "test/testLocalUpdater.js",
    "content": "// Copyright 2015 A Medium Corporation.\n\nvar typeUtil = require('../lib/typeUtil')\nvar localUpdater = require('../lib/localUpdater')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\nvar Q = require('kew')\n\nbuilder.add(function testDelete(test) {\n  var data = typeUtil.packObjectOrArray({\n    \"userId\": \"userA\",\n    \"column\": \"@\",\n    \"age\": 29,\n    \"someStringSet\": ['a', 'b', 'c']\n  })\n\n  var updated = localUpdater.update(data, {\n    'age': {\n      Action: 'DELETE'\n    }\n  })\n\n  test.equal(data.age.N, 29, 'age should not change')\n  test.ok(updated.age === undefined, 'age should be undefined')\n  return Q.resolve()\n})\n\nbuilder.add(function testDeleteFromSet(test) {\n  var data = typeUtil.packObjectOrArray({\n    \"userId\": \"userA\",\n    \"column\": \"@\",\n    \"age\": 29,\n    \"someStringSet\": ['a', 'b', 'c']\n  })\n\n  var updated = localUpdater.update(data, {\n    'someStringSet': {\n      Action: 'DELETE',\n      Value: typeUtil.valueToObject(['b','c','d'])\n    }\n  })\n\n  test.deepEqual(data.someStringSet.SS, ['a', 'b', 'c'], 'someStringSet should not change')\n  test.deepEqual(updated.someStringSet.SS, ['a'], 'someStringSet should equal [\\'a\\']')\n  return Q.resolve()\n})\n\nbuilder.add(function testAddToSet(test) {\n  var data = typeUtil.packObjectOrArray({\n    \"userId\": \"userA\",\n    \"column\": \"@\",\n    \"age\": 29,\n    \"someStringSet\": ['a', 'b', 'c']\n  })\n\n  var updated = localUpdater.update(data, {\n    'someStringSet': {\n      Action: 'ADD',\n      Value: typeUtil.valueToObject(['b','c','d'])\n    }\n  })\n\n  test.deepEqual(data.someStringSet.SS, ['a', 'b', 'c'], 'someStringSet should not change')\n  test.deepEqual(updated.someStringSet.SS, ['a', 'b', 'c', 'd'], 'someStringSet should equal [\\'a\\', \\'b\\', \\'c\\', \\'d\\']')\n  return Q.resolve()\n})\n\nbuilder.add(function testAddToEmptySet(test) {\n  var data = typeUtil.packObjectOrArray({\n    \"userId\": \"userA\",\n    \"column\": \"@\",\n    \"age\": 29\n  })\n\n  var updated = localUpdater.update(data, {\n    'someStringSet': {\n      Action: 'ADD',\n      Value: typeUtil.valueToObject(['b','c','d'])\n    }\n  })\n\n  test.ok(data.someStringSet === undefined, 'someStringSet should not change')\n  test.deepEqual(updated.someStringSet.SS, ['b', 'c', 'd'], 'someStringSet should equal [\\'a\\', \\'b\\', \\'c\\', \\'d\\']')\n  return Q.resolve()\n})\n\nbuilder.add(function testAddToNumber(test) {\n  var data = typeUtil.packObjectOrArray({\n    \"userId\": \"userA\",\n    \"column\": \"@\",\n    \"age\": 29\n  })\n\n  var updated = localUpdater.update(data, {\n    'age': {\n      Action: 'ADD',\n      Value: typeUtil.valueToObject(1)\n    }\n  })\n\n  test.deepEqual(data.age.N, 29, 'age should not change')\n  test.deepEqual(updated.age.N, '30', 'age should equal 30')\n  return Q.resolve()\n})\n\nbuilder.add(function testAddToEmptyNumber(test) {\n  var data = typeUtil.packObjectOrArray({\n    \"userId\": \"userA\",\n    \"column\": \"@\"\n  })\n\n  var updated = localUpdater.update(data, {\n    'age': {\n      Action: 'ADD',\n      Value: typeUtil.valueToObject(30)\n    }\n  })\n\n  test.ok(data.age === undefined, 'age should not change')\n  test.deepEqual(updated.age.N, '30', 'age should equal 30')\n  return Q.resolve()\n})\n\nbuilder.add(function testPut(test) {\n  var data = typeUtil.packObjectOrArray({\n    \"userId\": \"userA\",\n    \"column\": \"@\",\n    \"age\": 29,\n    \"someStringSet\": ['a', 'b', 'c']\n  })\n\n  var updated = localUpdater.update(data, {\n    'someStringSet': {\n      Action: 'PUT',\n      Value: typeUtil.valueToObject(['b','c','d'])\n    }\n  })\n\n  test.deepEqual(data.someStringSet.SS, ['a', 'b', 'c'], 'someStringSet should not change')\n  test.deepEqual(updated.someStringSet.SS, ['b', 'c', 'd'], 'someStringSet should equal [\\'b\\', \\'c\\', \\'d\\']')\n  return Q.resolve()\n})\n"
  },
  {
    "path": "test/testPutItem.js",
    "content": "// Copyright 2013 The Obvious Corporation.\n\nvar utils = require('./utils/testUtils.js')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\nvar errors = require('../lib/errors')\n\nvar onError = console.error.bind(console)\nvar initialData = [{\"userId\": \"userA\", \"column\": \"@\", \"age\": \"29\"}]\n\n/*\n * Sets up for test, and creates a record userA with range key @.\n */\nexports.setUp = function (done) {\n  this.db = utils.getMockDatabase()\n  this.client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n  utils.createTable(this.db, \"user\", \"userId\", \"column\")\n    .thenBound(utils.initTable, null, {db: this.db, tableName: \"user\", data: initialData})\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  utils.deleteTable(this.db, \"user\")\n    .fin(done)\n}\n\nbuilder.add(function testSetInvalidReturnValue(test) {\n  var putBuilder = this.client.putItem('user', {\n    userId: 'userB',\n    age: 30\n  })\n\n  test.throws(function () {\n    putBuilder.setReturnValues('ALL_SOMETHING')\n  }, errors.InvalidReturnValuesError)\n\n  test.done()\n})\n\n// put an item and check that it exists\nbuilder.add(function testSimplePut(test) {\n  var self = this\n  return this.client.putItem(\"user\", {\n    userId: 'userB',\n    column: '@',\n    age: 30\n  })\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userB\", \"@\")\n  })\n  .then(function (data) {\n    test.equal(data['Item']['age'].N, \"30\", \"Age should be set\")\n  })\n})\n\nbuilder.add(function testPutItemWithReturnValuesNone(test) {\n  var self = this\n  return this.client.putItem(\"user\", {\n    userId: 'userB',\n    column: '@',\n    age: 30\n  })\n  .setReturnValues('NONE')\n  .execute()\n  .then(function (data) {\n    test.equal(data.result, undefined)\n\n    return utils.getItemWithSDK(self.db, \"userB\", \"@\")\n  })\n  .then(function (data) {\n    test.equal(data['Item']['age'].N, \"30\", \"Age should be set\")\n  })\n})\n\n// put overrides all fields\nbuilder.add(function testOverridePut(test) {\n  var self = this\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    height: 72\n  })\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n  })\n  .then(function (data) {\n    test.equal(data['Item']['age'], undefined, \"Age should be undefined\")\n    test.equal(data['Item']['height'].N, \"72\", \"Height should be 72\")\n  })\n})\n\n// put with successful conditional exists\nbuilder.add(function testPutWithConditional(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeEquals('age', 29)\n\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    height: 72\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n  })\n  .then(function (data) {\n    test.equal(data['Item']['height'].N, \"72\", \"Height should be 72\")\n  })\n})\n\n// put with successful absent conditional exists\nbuilder.add(function testPutWithAbsentConditional(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeAbsent('height')\n\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    height: 72\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n  })\n  .then(function (data) {\n    test.equal(data['Item']['height'].N, \"72\", \"Height should be 72\")\n  })\n})\n\n// put with successful absent conditional doesn't exist\nbuilder.add(function testPutWithAbsentConditionalAndNoRecord(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeAbsent('age')\n\n  return this.client.putItem(\"user\", {\n    userId: 'userB',\n    column: '@',\n    height: 72\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userB\", \"@\")\n  })\n  .then(function (data) {\n    test.equal(data['Item']['height'].N, \"72\", \"Height should be 72\")\n  })\n})\n\n// put with failed conditional exists\nbuilder.add(function testPutWithFailedConditional(test) {\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeEquals('age', 30)\n\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    height: 72\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    test.fail(\"'testPutWithFailedConditional' failed\")\n  })\n  .fail(this.client.throwUnlessConditionalError)\n})\n\n// put with failed conditional doesn't exist\nbuilder.add(function testPutWithFailedConditionalForNoRecord(test) {\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeEquals('age', 29)\n\n  return this.client.putItem(\"user\", {\n    userId: 'userB',\n    column: '@',\n    height: 72\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    test.fail(\"'testPutWithFailedConditionalForNoRecord' failed\")\n  })\n  .fail(this.client.throwUnlessConditionalError)\n})\n\n// put set with failed absent conditional exists\nbuilder.add(function testPutWithFailedAbsentConditionalExists(test) {\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeAbsent('age')\n\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    height: 72\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    test.fail(\"'testPutWithFailedAbsentConditionalExists' failed\")\n  })\n  .fail(this.client.throwUnlessConditionalError)\n})\n\n// trigger a conditional error and check its content\nbuilder.add(function testConditionalErrorFormat(test) {\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeAbsent('age')\n\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    height: 72\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    test.fail('This putItem request should fail due to a conditional error')\n  })\n  .fail(function (err) {\n    test.ok(err instanceof errors.ConditionalError)\n    test.equals('The conditional request failed', err.message, 'The \"message\" field should be the custom message')\n    test.equals('The conditional request failed', err.details, 'The \"details\" field should be the custom message')\n    test.equals('user', err.table, 'The \"table\" field should be the right table name')\n    test.ok(!!err.requestId, 'The \"requestId\" field should exist')\n  })\n})\n"
  },
  {
    "path": "test/testPutSet.js",
    "content": "var utils = require('./utils/testUtils.js')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\n\nvar onError = console.error.bind(console)\nvar initialData = [  {\"userId\": \"userA\", \"column\": \"@\", \"postIds\": ['1a', '1b', '1c']}\n                   , {\"userId\": \"userB\", \"column\": \"@\", \"postIds\": [1, 2, 3]}]\n\n/*\n * Sets up for test, and creates a record userA with range key @.\n */\nexports.setUp = function (done) {\n  this.db = utils.getMockDatabase()\n  this.client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n  utils.createTable(this.db, \"user\", \"userId\", \"column\")\n    .thenBound(utils.initTable, null, {db: this.db, tableName: \"user\", data: initialData})\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  utils.deleteTable(this.db, \"user\")\n    .fin(done)\n}\n\n// put a list of strings and check if they exist\nbuilder.add(function testStringSetPut(test) {\n  var self = this\n  return this.client.putItem(\"user\", {\n    userId: 'userC',\n    column: '@',\n    postIds: ['3a', '3b', '3c']\n  })\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userC\", \"@\")\n  })\n  .then(function (data) {\n    test.deepEqual(data['Item']['postIds'].SS, ['3a', '3b', '3c'], \"postIds should be ['3a', '3b', '3c']\")\n  })\n})\n\n// put a list of numbers and check if they exist\nbuilder.add(function testNumberSetPut(test) {\n  var self = this\n  return this.client.putItem(\"user\", {\n    userId: 'userD',\n    column: '@',\n    postIds: [1, 2, 3]\n  })\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userD\", \"@\")\n  })\n  .then(function (data) {\n    test.deepEqual(data['Item']['postIds'].NS, [1, 2, 3], \"postIds should be [1, 2, 3]\")\n  })\n})\n\n// override all string set fields\nbuilder.add(function testStringSetPutOverride(test) {\n  var self = this\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    otherIds: ['3a', '3b', '3c']\n  })\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n  })\n  .then(function (data) {\n    test.equal(data['Item']['postIds'], undefined, \"postIds should not exist\")\n    test.deepEqual(data['Item']['otherIds'].SS, ['3a', '3b', '3c'], \"otherIds should be ['3a', '3b', '3c']\")\n  })\n})\n\n// override all number set fields\nbuilder.add(function testNumberSetPutOverride(test) {\n  var self = this\n  return this.client.putItem(\"user\", {\n    userId: 'userB',\n    column: '@',\n    otherIds: [4, 5, 6]\n  })\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userB\", \"@\")\n  })\n  .then(function (data) {\n    test.equal(data['Item']['postIds'], undefined, \"postIds should not exist\")\n    test.deepEqual(data['Item']['otherIds'].NS, [4, 5, 6], \"otherIds should be [4, 5, 6]\")\n  })\n})\n\n// override all number set fields with a string set\nbuilder.add(function testNumberSetPutOverrideWithStringSet(test) {\n  var self = this\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    otherIds: [4, 5, 6]\n  })\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n  })\n  .then(function (data) {\n    test.equal(data['Item']['postIds'], undefined, \"postIds should not exist\")\n    test.deepEqual(data['Item']['otherIds'].NS, [4, 5, 6], \"otherIds should be [4, 5, 6]\")\n  })\n})\n\n// put string set with successful conditional exists\nbuilder.add(function testStringSetPutWithConditional(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n  .expectAttributeEquals('postIds', ['1a', '1b', '1c'])\n\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    otherIds: ['5a', '5b', '5c']\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n  })\n  .then(function (data) {\n    test.equal(data['Item']['otherIds'].SS[0], '5a', \"otherIds[0] should be 5a\")\n  })\n})\n\n// put string set with successful absent conditional exists\nbuilder.add(function testStringSetPutWithAbsentConditional(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n  .expectAttributeAbsent('otherIds')\n\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    otherIds: ['5a', '5b', '5c']\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n  })\n  .then(function (data) {\n    test.equal(data['Item']['otherIds'].SS[0], '5a', \"otherIds[0] should be 5a\")\n  })\n})\n\n// put with successful absent conditional doesn't exist\nbuilder.add(function testStringSetPutWithAbsentConditionalDoesntExist(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n  .expectAttributeAbsent('otherIds')\n\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    otherIds: ['5a', '5b', '5c']\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n  })\n  .then(function (data) {\n    test.equal(data['Item']['otherIds'].SS[0], '5a', \"otherIds[0] should be 5a\")\n  })\n})\n\n// put set with failed conditional exists\nbuilder.add(function testStringSetPutWithFailedConditional(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n  .expectAttributeEquals('postIds', ['a', 'b', 'c'])\n\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    otherIds: ['5a', '5b', '5c']\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n  })\n  .then(function () {\n    test.fail(\"'testStringSetPutWithFailedConditional' failed\")\n  })\n  .fail(this.client.throwUnlessConditionalError)\n})\n\n// put set with failed conditional doesn't exist\nbuilder.add(function testStringSetPutWithFailedConditionalForNoRecord(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n  .expectAttributeEquals('postIds', ['a', 'b', 'c'])\n\n  return this.client.putItem(\"user\", {\n    userId: 'userC',\n    column: '@',\n    otherIds: ['5a', '5b', '5c']\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userC\", \"@\")\n  })\n  .then(function () {\n    test.fail(\"'testStringSetPutWithFailedConditionalForNoRecord' failed\")\n  })\n  .fail(this.client.throwUnlessConditionalError)\n})\n\n// put set with failed absent conditional exists\nbuilder.add(function testStringSetPutWithFailedConditionalExists(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n  .expectAttributeAbsent('postIds', ['1a', '1b', '1c'])\n\n  return this.client.putItem(\"user\", {\n    userId: 'userA',\n    column: '@',\n    otherIds: ['5a', '5b', '5c']\n  })\n  .withCondition(conditions)\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n  })\n  .then(function () {\n    test.fail(\"'testStringSetPutWithFailedConditionalForNoRecord' failed\")\n  })\n  .fail(this.client.throwUnlessConditionalError)\n})\n"
  },
  {
    "path": "test/testQuery.js",
    "content": "// Copyright 2013 The Obvious Corporation.\n\nvar ConditionBuilder = require('../lib/ConditionBuilder')\nvar utils = require('./utils/testUtils.js')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\n\nvar onError = console.error.bind(console)\nvar tableName = \"comments\"\nvar rawData = [{\"postId\": \"post1\", \"column\": \"@\", \"title\": \"This is my post\", \"content\": \"And here is some content!\", \"tags\": ['bar', 'foo']},\n               {\"postId\": \"post1\", \"column\": \"/comment/timestamp/002123\", \"comment\": \"this is slightly later\"},\n               {\"postId\": \"post1\", \"column\": \"/comment/timestamp/010000\", \"comment\": \"where am I?\"},\n               {\"postId\": \"post1\", \"column\": \"/comment/timestamp/001111\", \"comment\": \"HEYYOOOOO\"},\n               {\"postId\": \"post1\", \"column\": \"/comment/timestamp/001112\", \"comment\": \"what's up?\"},\n               {\"postId\": \"post1\", \"column\": \"/canEdit/user/AAA\", \"userId\": \"AAA\"}]\n\n// sorted data for checking the order of returned data\nvar sortedRawData = []\nfor (var i = 0; i < rawData.length; i++) {\n  sortedRawData[i] = rawData[i]\n}\nsortedRawData.sort(function(obj1, obj2) {\n  return obj1.column > obj2.column ? 1 : -1\n})\n\n// basic setup for the tests, creating record userA with Index key @\nexports.setUp = function (done) {\n  this.db = utils.getMockDatabase()\n  this.client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n  utils.createTable(this.db, tableName, \"postId\", \"column\")\n    .thenBound(utils.initTable, null, {\"db\": this.db, \"tableName\": tableName, \"data\": rawData})\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  utils.deleteTable(this.db, tableName)\n    .fin(done)\n}\n\nfunction checkResults(test, total, offset) {\n  return function (data) {\n    test.equal(data.result.length, total, total + \" records should be returned\")\n    for (var i = 0; i < data.result.length; i++) {\n      test.deepEqual(data.result[i], sortedRawData[i + offset], \"Row should be retrieved in the correct order\")\n    }\n  }\n}\n\n// test basic query\nbuilder.add(function testBasicQuery(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .execute()\n    .then(checkResults(test, 6, 0))\n})\n\n// test basic query with an empty filter\nbuilder.add(function testBasicQueryEmptyFilter(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .withFilter(this.client.newConditionBuilder())\n    .execute()\n    .then(checkResults(test, 6, 0))\n})\n\n// test Index key begins with\nbuilder.add(function testindexBeginsWith(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBeginsWith('column', '/comment/')\n    .execute()\n    .then(checkResults(test, 4, 1))\n})\n\n// test filtering\nbuilder.add(function testFilterByComment(test) {\n  var filter = this.client.newConditionBuilder()\n    .filterAttributeBeginsWith(\"comment\", \"HEY\")\n\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBeginsWith('column', '/comment/')\n    .withFilter(filter)\n    .execute()\n    .then(checkResults(test, 1, 1))\n})\n\n// test filter with limit\nbuilder.add(function testFilterWithLimit(test) {\n  var filter = this.client.newConditionBuilder()\n    .filterAttributeBeginsWith(\"comment\", \"wh\")\n\n  // The limit parameter is applied before the filter\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBeginsWith('column', '/comment/')\n    .withFilter(filter)\n    .setLimit(2)\n    .execute()\n    .then(checkResults(test, 1, 2))\n})\n\n\n\n// test Index key between\nbuilder.add(function testIndexKeyBetween(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBetween('column', '/comment/', '/comment/timestamp/009999')\n    .execute()\n    .then(checkResults(test, 3, 1))\n})\n\n// test Index key less than\nbuilder.add(function testIndexKeyLessThan(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexLessThan('column', '/comment/timestamp/001111')\n    .execute()\n    .then(checkResults(test, 1, 0))\n})\n\n// test Index key less than equal\nbuilder.add(function testIndexKeyLessThanEqual(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexLessThanEqual('column', '/comment/timestamp/001111')\n    .execute()\n    .then(checkResults(test, 2, 0))\n})\n\n// test Index key greater than\nbuilder.add(function testIndexKeyGreaterThan(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexGreaterThan('column', '/comment/timestamp/001111')\n    .execute()\n    .then(checkResults(test, 4, 2))\n})\n\n// test Index key greater than equal\nbuilder.add(function testIndexKeyGreaterThanEqual(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexGreaterThanEqual('column', '/comment/timestamp/001111')\n    .execute()\n    .then(checkResults(test, 5, 1))\n})\n\n// test Index key equal\nbuilder.add(function testIndexKeyEqual(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexEqual('column', '/comment/timestamp/001111')\n    .execute()\n    .then(checkResults(test, 1, 1))\n})\n\n// test limit\nbuilder.add(function testLimit(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBetween('column', '/comment/', '/comment/timestamp/999999')\n    .setLimit(3)\n    .execute()\n    .then(checkResults(test, 3, 1))\n})\n\n// test scan forward\nbuilder.add(function testScanForward(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBetween('column', '/comment/', '/comment/timestamp/999999')\n    .execute()\n    .then(checkResults(test, 4, 1))\n})\n\n// test cursoring forward\nbuilder.add(function testCursorForward(test) {\n  var client = this.client\n\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBetween('column', '/comment/', '/comment/timestamp/999999')\n    .setLimit(3)\n    .execute()\n    .then(function (data) {\n      return client.newQueryBuilder('comments')\n        .setHashKey('postId', 'post1')\n        .indexBetween('column', '/comment/', '/comment/timestamp/999999')\n        .setStartKey(data.LastEvaluatedKey)\n        .execute()\n    })\n    .then(function (data) {\n      test.equal(data.result.length, 1, \"1 record should be returned\")\n      test.equal(data.result[0].comment, \"where am I?\", \"Row comment should be set\")\n    })\n})\n\n// test cursoring backward\nbuilder.add(function testCursorBackward(test) {\n  var client = this.client\n\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBetween('column', '/comment/', '/comment/timestamp/999999')\n    .setLimit(3)\n    .scanBackward()\n    .execute()\n    .then(function (data) {\n      return client.newQueryBuilder('comments')\n        .setHashKey('postId', 'post1')\n        .indexBetween('column', '/comment/', '/comment/timestamp/999999')\n        .scanBackward()\n        .setStartKey(data.LastEvaluatedKey)\n        .execute()\n    })\n    .then(function (data) {\n      test.equal(data.result.length, 1, \"1 record should be returned\")\n      test.equal(data.result[0].comment, \"HEYYOOOOO\", \"Row comment should be set\")\n    })\n})\n\n// test select attributes\nbuilder.add(function testSelectAttributes(test) {\n  var keyOffset = 1\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBetween('column', '/comment/', '/comment/timestamp/999999')\n    .selectAttributes(['postId', 'comment'])\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.length, 4, \"4 records should be returned\")\n      for (var i = 0; i < data.result.length; i++) {\n        test.equal(data.result[i].comment, sortedRawData[i + keyOffset].comment, \"Row comment should be set\")\n        test.equal(data.result[i].column, undefined, 'Column should not be set')\n      }\n    })\n})\n\n// test set existence\nbuilder.add(function testSetExistence(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexEqual('column', '@')\n    .selectAttributes(['postId', 'tags'])\n    .execute()\n    .then(function (data) {\n      test.deepEqual(data.result[0].tags, ['bar', 'foo'], \"post should have tags ['bar', 'foo']\")\n    })\n})\n\n// test scan backward\nbuilder.add(function testScanBackward(test) {\n  var keyOffset = 1\n\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBetween('column', '/comment/', '/comment/timestamp/999999')\n    .scanBackward()\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.length, 4, \"4 records should be returned\")\n      for (var i = 0; i < data.result.length; i++) {\n        test.deepEqual(data.result[i], sortedRawData[(data.result.length - 1 - i) + keyOffset],\n                       \"Row should be retrieved in the correct order\")\n      }\n    })\n})\n\n// test count\nbuilder.add(function testCount(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBetween('column', '/comment/timestamp/002123', '/comment/timestamp/999999')\n    .getCount()\n    .execute()\n    .then(function (data) {\n      test.equal(data.Count, 2, '\"2\" should be returned')\n    })\n})\n\n// test count if it's zero\nbuilder.add(function testCountIfZero(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'postDNE')\n    .indexBetween('column', '/comment/timestamp/002123', '/comment/timestamp/999999')\n    .getCount()\n    .execute()\n    .then(function (data) {\n      test.equal(data.Count, 0, '\"0\" should be returned')\n    })\n})\n\nbuilder.add(function testNext(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBetween('column', '/comment/', '/comment/timestamp/999999')\n    .setLimit(3)\n    .execute()\n    .then(function (data) {\n      test.equal(3, data.Count)\n      test.ok(data.hasNext())\n      return data.next()\n    })\n    .then(function (data) {\n      test.equal(1, data.Count)\n      test.ok(!data.hasNext())\n      return data.next()\n    })\n    .then(function () {\n      test.fail('Expected error')\n    })\n    .fail(function (e) {\n      if (e.message !== 'No more results') throw e\n    })\n})\n\nbuilder.add(function testNextWithLimit(test) {\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBetween('column', '/comment/', '/comment/timestamp/999999')\n    .setLimit(2)\n    .execute()\n    .then(function (data) {\n      test.equal(2, data.Count)\n      test.ok(data.hasNext())\n      return data.next(3)\n    })\n    .then(function (data) {\n      test.equal(2, data.Count)\n      test.ok(!data.hasNext())\n      return data.next()\n    })\n    .then(function () {\n      test.fail('Expected error')\n    })\n    .fail(function (e) {\n      if (e.message !== 'No more results') throw e\n    })\n})\n\nbuilder.add(function testValidationError(test) {\n  var client = this.client\n  return client.newQueryBuilder('comments')\n    .setHashKey('garbage', 'postId')\n    .execute()\n    .then(function () {\n      test.fail('Expected validation exception')\n    })\n    .fail(function (e) {\n      if (!client.isValidationError(e)) throw e\n      test.equal('comments', e.table)\n    })\n})\n\nbuilder.add(function testRetryHandler(test) {\n  var client = this.client\n  var calledRetryHandler = 0\n\n  return client.newQueryBuilder('comments')\n    .setHashKey('invalid', 'post1')\n    .setRetryHandler(function (method, table, response) {\n      test.equal(response.error.retryable, false)\n      ++calledRetryHandler\n    })\n    .execute()\n    .then(function () {\n      test.fail('Expected validation to fail!')\n    })\n    .fail(function () {\n      test.equal(calledRetryHandler, 1)\n    })\n})\n\n\nbuilder.add(function testKeyConditionExpression(test) {\n  var filter = this.client.newConditionBuilder()\n    .filterAttributeBeginsWith(\"comment\", \"HEY\")\n\n  var data = {}\n  ConditionBuilder.populateExpressionField(\n      data, 'KeyConditionExpression', [filter], {})\n  test.equal('{\"#comment\":\"comment\"}', JSON.stringify(data.ExpressionAttributeNames))\n  test.equal('{\":VC2\":{\"S\":\"HEY\"}}', JSON.stringify(data.ExpressionAttributeValues))\n  test.equal('begins_with(#comment, :VC2)', data.KeyConditionExpression)\n  test.done()\n})\n\n\nbuilder.add(function testOrConditionExpression(test) {\n  var filter1 = this.client.newConditionBuilder()\n      .filterAttributeBeginsWith(\"comment\", \"HEY\")\n  var filter2 = this.client.newConditionBuilder()\n      .filterAttributeBeginsWith(\"comment\", \"what\")\n\n\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBeginsWith('column', '/comment/')\n    .withFilter(this.client.orConditions([filter1, filter2]))\n    .execute()\n    .then(checkResults(test, 2, 1))\n})\n\n\nbuilder.add(function testAndConditionExpression(test) {\n  var filter1 = this.client.newConditionBuilder()\n      .filterAttributeBeginsWith(\"comment\", \"HEY\")\n  var filter2 = this.client.newConditionBuilder()\n      .filterAttributeEquals(\"comment\", \"HEYYOOOOO\")\n\n\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBeginsWith('column', '/comment/')\n    .withFilter(this.client.andConditions([filter1, filter2]))\n    .execute()\n    .then(checkResults(test, 1, 1))\n})\n\n\n\nbuilder.add(function testNotConditionExpression(test) {\n  var filter1 = this.client.newConditionBuilder()\n      .filterAttributeBeginsWith(\"comment\", \"HEY\")\n\n\n  return this.client.newQueryBuilder('comments')\n    .setHashKey('postId', 'post1')\n    .indexBeginsWith('column', '/comment/')\n    .withFilter(this.client.notCondition(filter1))\n    .execute()\n    .then(checkResults(test, 3, 2))\n})\n"
  },
  {
    "path": "test/testScan.js",
    "content": "// Copyright 2013 The Obvious Corporation.\n\nvar utils = require('./utils/testUtils.js')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\n\nvar onError = console.error.bind(console)\nvar tableName = \"user\"\nvar rawData = [{\"userId\": \"a\", \"column\": \"@\", \"post\": \"3\", \"email\": \"1@medium.com\"},\n               {\"userId\": \"b\", \"column\": \"@\", \"post\": \"0\", \"address\": \"800 Market St. SF, CA\"},\n               {\"userId\": \"c\", \"column\": \"@\", \"post\": \"5\", \"email\": \"3@medium\"},\n               {\"userId\": \"d\", \"column\": \"@\", \"post\": \"2\", \"twitter\": \"haha\"},\n               {\"userId\": \"e\", \"column\": \"@\", \"post\": \"2\", \"twitter\": \"hoho\"},\n               {\"userId\": \"f\", \"column\": \"@\", \"post\": \"4\", \"description\": \"Designer\", \"email\": \"h@w.com\"},\n               {\"userId\": \"h\", \"column\": \"@\", \"post\": \"6\", \"tags\": ['bar', 'foo']}]\n\n\n// basic setup for the tests, creating record userA with range key @\nexports.setUp = function (done) {\n  this.db = utils.getMockDatabase()\n  this.client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n  utils.createTable(this.db, tableName, \"userId\", \"column\", [{hashKey: \"post\", hashKeyType: \"N\"}, {hashKey: \"post\", hashKeyType: \"N\", rangeKey: \"email\"}, {hashKey: \"description\"}])\n    .thenBound(utils.initTable, null, {\"db\": this.db, \"tableName\": tableName, \"data\": rawData})\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  utils.deleteTable(this.db, tableName)\n    .fin(done)\n}\n\n/**\n * A helper function that runs a query and check the result.\n *\n * @param query {Query} The query that has been built and ready to execute.\n * @param expect {Array<Integer>} The expected returned results\n * @param test {Object} The test object from nodeunit.\n * @return {Q}\n */\nvar scanAndCheck = function (scan, expect, test) {\n  return scan.execute()\n    .then(function (data) {\n      test.equal(data.result.length, expect.length, expect.length + \" records should be returned, got \" + data.result.length)\n      data.result.sort(function(a, b) {return (a.userId < b.userId) ? -1 : ((a.userId > b.userId) ? 1 : 0)})\n      for (var i = 0; i < data.result.length; i++) {\n        test.deepEqual(data.result[i], rawData[expect[i]])\n      }\n    })\n}\n\n// test basic scan on the entire table\nbuilder.add(function testScanAll(test) {\n  var scan = this.client.newScanBuilder(tableName)\n  return scanAndCheck(scan, [0, 1, 2, 3, 4, 5, 6], test)\n})\n\nbuilder.add(function testScanSegment(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .setParallelScan(0, 2)\n  return scanAndCheck(scan, [1, 3, 4], test)\n})\n\nbuilder.add(function testScanOnGlobalSecondaryIndex(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .setIndexNameGenerator(utils.indexNameGenerator)\n               .setHashKey('post')\n               .setRangeKey('email')\n  return scanAndCheck(scan, [0, 2, 5], test)\n})\n\nbuilder.add(function testScanOnGlobalSecondaryIndexWithoutRangeKey(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .setIndexNameGenerator(utils.indexNameGenerator)\n               .setHashKey('description')\n  return scanAndCheck(scan, [5], test)\n})\n\nbuilder.add(function testParallelScanOnGlobalSecondaryIndex(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .setIndexNameGenerator(utils.indexNameGenerator)\n               .setHashKey('post')\n               .setParallelScan(1, 2)\n  return scanAndCheck(scan, [0, 2, 5, 6], test)\n})\n\n// test filtering with post == 2\nbuilder.add(function testFilterByEqual(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeEquals(\"post\", 2))\n  return scanAndCheck(scan, [3, 4], test)\n})\n\n// test filtering with post != 2\nbuilder.add(function testFilterByNotEqual(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeNotEquals(\"post\", 2))\n  return scanAndCheck(scan, [0, 1, 2, 5, 6], test)\n})\n\n// test filtering with post <= 2\nbuilder.add(function testFilterByLessThanEqual(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeLessThanEqual(\"post\", 2))\n  return scanAndCheck(scan, [1, 3, 4], test)\n})\n\n// test filtering with post < 2\nbuilder.add(function testFilterByLessThan(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeLessThan(\"post\", 2))\n  return scanAndCheck(scan, [1], test)\n})\n\n// test filtering with post >= 2\nbuilder.add(function testFilterByGreaterThanEqual(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeGreaterThanEqual(\"post\", 2))\n  return scanAndCheck(scan, [0, 2, 3, 4, 5, 6], test)\n})\n\n// test filtering with post > 2\nbuilder.add(function testFilterByGreaterThan(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeGreaterThan(\"post\", 2))\n  return scanAndCheck(scan, [0, 2, 5, 6], test)\n})\n\n// test filtering with not null\nbuilder.add(function testFilterByNotNull(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeNotNull(\"post\"))\n  return scanAndCheck(scan, [0, 1, 2, 3, 4, 5, 6], test)\n})\n\n// test filtering with email 'CONTAINS' 'medium'\nbuilder.add(function testFilterByContains(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeContains(\"email\", \"medium\"))\n  return scanAndCheck(scan, [0, 2], test)\n})\n\n// test filters with tags 'CONTAINS' 'foo'\nbuilder.add(function testFilterBySetContains(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeContains(\"tags\", \"foo\"))\n  return scanAndCheck(scan, [6], test)\n})\n\n// test filtering with email 'NOT_CONTAINS' 'medium'\nbuilder.add(function testFilterByNotContains(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeNotContains(\"email\", \"medium\"))\n  return scanAndCheck(scan, [5], test, \"testFilterByNotContains\")\n})\n\n// test filters with tags 'NOT_CONTAINS' 'baz'\nbuilder.add(function testFilterBySetNotContains(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeNotContains(\"tags\", \"baz\"))\n  return scanAndCheck(scan, [6], test, \"testFilterBySetNotContains\")\n})\n\n// test filtering with twitter 'BEGIN_WITH' 'h'\nbuilder.add(function testFilterByBeginWith(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeBeginsWith(\"twitter\", \"h\"))\n  return scanAndCheck(scan, [3, 4], test, \"testFilterByBeginWith\")\n})\n\n// test filtering with post 'BETWEEN' 2 3\nbuilder.add(function testFilterByBetween(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeBetween(\"post\", 2, 3))\n  return scanAndCheck(scan, [0, 3, 4], test, \"testFilterByBetween\")\n})\n\n// test filtering with post 'IN' 2 3\nbuilder.add(function testFilterByIn(test) {\n  var scan = this.client.newScanBuilder(tableName)\n               .withFilter(this.client.newConditionBuilder().filterAttributeIn(\"post\", [2, 3]))\n  return scanAndCheck(scan, [0, 3, 4], test, \"testFilterByIn\")\n})\n\nbuilder.add(function testNext(test) {\n  var numInFirstScan = 0\n  return this.client.newScanBuilder(tableName)\n    .withFilter(this.client.newConditionBuilder().filterAttributeGreaterThan(\"post\", 2))\n    // The limit is *not* the number of records to return; instead it is\n    // the number of records to scan. So the actual number of records returned\n    // is not specified when a filter is given.\n    .setLimit(4)\n    .execute()\n    .then(function (data) {\n      numInFirstScan = data.Count\n      test.ok(data.hasNext())\n      return data.next()\n    })\n    .then(function (data) {\n      test.equal(4, numInFirstScan + data.Count, 'Scan should return 4 records in total')\n      test.ok(!data.hasNext())\n      return data.next()\n    })\n    .then(function () {\n      test.fail('Expected error')\n    })\n    .fail(function (e) {\n      if (e.message !== 'No more results') throw e\n    })\n})\n"
  },
  {
    "path": "test/testStringSet.js",
    "content": "var utils = require('./utils/testUtils.js')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\n\nvar onError = console.error.bind(console)\nvar initialData = [  {\"userId\": \"userA\", \"column\": \"@\", \"postIds\": ['1a', '1b', '1c']}\n                   , {\"userId\": \"userB\", \"column\": \"@\", \"postIds\": [1, 2, 3]}]\n\n/*\n * Sets up for test, and creates a record userA with range key @.\n */\nexports.setUp = function (done) {\n  this.db = utils.getMockDatabase()\n  this.client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n  utils.createTable(this.db, \"user\", \"userId\", \"column\")\n    .thenBound(utils.initTable, null, {db: this.db, tableName: \"user\", data: initialData})\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  utils.deleteTable(this.db, \"user\")\n    .fin(done)\n}\n\n// put a list of strings and check if they exist\nbuilder.add(function testStringSetPut(test) {\n  var self = this\n  return this.client.putItem(\"user\", {\n    userId: 'userC',\n    column: '@',\n    postIds: ['3a', '3b', '3c']\n  })\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userC\", \"@\")\n  })\n  .then(function (data) {\n    test.deepEqual(data['Item']['postIds'].SS, ['3a', '3b', '3c'], \"postIds should be ['3a', '3b', '3c']\")\n  })\n})\n\n// put a list of numbers and check if they exist\nbuilder.add(function testNumberSetPut(test) {\n  var self = this\n  return this.client.putItem(\"user\", {\n    userId: 'userD',\n    column: '@',\n    postIds: [1, 2, 3]\n  })\n  .execute()\n  .then(function () {\n    return utils.getItemWithSDK(self.db, \"userD\", \"@\")\n  })\n  .then(function (data) {\n    test.deepEqual(data['Item']['postIds'].NS, [1, 2, 3], \"postIds should be [1, 2, 3]\")\n  })\n})\n\n// get the set of strings\nbuilder.add(function testStringSetRetrieve(test) {\n  return this.client.getItem('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .execute()\n    .then(function (data) {\n      test.deepEqual(data.result.postIds, ['1a', '1b', '1c'], \"postIds should be ['1a', '1b', '1c']\")\n    })\n})\n\n// get the set of numbers\nbuilder.add(function testNumberSetRetrieve(test) {\n  return this.client.getItem('user')\n    .setHashKey('userId', 'userB')\n    .setRangeKey('column', '@')\n    .execute()\n    .then(function (data) {\n      test.deepEqual(data.result.postIds, [1, 2, 3], \"postIds should be [1, 2, 3]\")\n    })\n})\n"
  },
  {
    "path": "test/testTypeUtil.js",
    "content": "// Copyright 2015 A Medium Corporation.\n\nvar typeUtil = require('../lib/typeUtil')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\nvar Q = require('kew')\n\nbuilder.add(function testAddToSet(test) {\n  var set = typeUtil.valueToObject([1,2,3])\n  test.equal(typeUtil.objectToType(set), 'NS')\n\n  var additions = typeUtil.valueToObject([4])\n  test.equal(typeUtil.objectToType(additions), 'NS')\n\n  var modified = typeUtil.addToSet(set, additions)\n  test.equal(typeUtil.objectToType(modified), 'NS')\n  modified.NS.sort()\n\n  test.deepEqual(set.NS, [1,2,3].map(String))\n  test.deepEqual(modified.NS, [1,2,3,4].map(String))\n\n  return Q.resolve()\n})\n\nbuilder.add(function testAddToNullSet(test) {\n  var set = null\n\n  var additions = typeUtil.valueToObject([4])\n  test.equal(typeUtil.objectToType(additions), 'NS')\n\n  var modified = typeUtil.addToSet(set, additions)\n  test.equal(typeUtil.objectToType(modified), 'NS')\n  modified.NS.sort()\n\n  test.deepEqual(modified.NS, [4].map(String))\n\n  return Q.resolve()\n})\n\nbuilder.add(function testDeleteFromSet(test) {\n  var set = typeUtil.valueToObject([1,2,3])\n  test.equal(typeUtil.objectToType(set), 'NS')\n\n  var deletions = typeUtil.valueToObject([1, 4])\n  test.equal(typeUtil.objectToType(deletions), 'NS')\n\n  var modified = typeUtil.deleteFromSet(set, deletions)\n  test.equal(typeUtil.objectToType(modified), 'NS')\n  modified.NS.sort()\n\n  test.deepEqual(modified.NS, [2, 3].map(String))\n\n  return Q.resolve()\n})\n\nbuilder.add(function testObjectIsNonEmptySet(test) {\n  test.ok(!typeUtil.objectIsNonEmptySet())\n  test.ok(!typeUtil.objectIsNonEmptySet(null))\n  test.ok(!typeUtil.objectIsNonEmptySet({}))\n  test.ok(!typeUtil.objectIsNonEmptySet(typeUtil.valueToObject(4)))\n  test.ok(!typeUtil.objectIsNonEmptySet(typeUtil.valueToObject('4')))\n\n  test.ok(typeUtil.objectIsNonEmptySet(typeUtil.valueToObject([4])))\n  test.ok(typeUtil.objectIsNonEmptySet(typeUtil.valueToObject(['4'])))\n\n  return Q.resolve()\n})\n\nbuilder.add(function testGetAttributeAlias(test) {\n  var getAlias = typeUtil.getAttributeAlias\n  test.equal('userId', getAlias('userId'))\n  test.equal('userId2', getAlias('userId2'))\n  test.equal('#comment', getAlias('comment')) // reserved word\n  test.equal('#5f5f757365724964', getAlias('__userId'))\n  test.equal('#7573657249645f', getAlias('userId_'))\n  test.equal('#30757365724964', getAlias('0userId'))\n  return Q.resolve()\n})\n"
  },
  {
    "path": "test/testUpdateItem.js",
    "content": "// Copyright 2013 The Obvious Corporation.\n\nvar utils = require('./utils/testUtils.js')\nvar errors = require('../lib/errors')\nvar nodeunitq = require('nodeunitq')\nvar builder = new nodeunitq.Builder(exports)\n\nvar onError = console.error.bind(console)\nvar initialData = [{\n  \"userId\": \"userA\",\n  \"column\": \"@\",\n  \"age\": \"29\",\n  \"someStringSet\": ['a', 'b', 'c']\n}]\n\n// basic setup for the tests, creating record userA with range key @\nexports.setUp = function (done) {\n  this.db = utils.getMockDatabase()\n  this.client = utils.getMockDatabaseClient()\n  utils.ensureLocalDynamo()\n  utils.createTable(this.db, \"user\", \"userId\", \"column\")\n    .thenBound(utils.initTable, null, {db: this.db, tableName: \"user\", data: initialData})\n    .fail(onError)\n    .fin(done)\n}\n\nexports.tearDown = function (done) {\n  utils.deleteTable(this.db, \"user\")\n    .fin(done)\n}\n\nbuilder.add(function testSetInvalidReturnValue(test) {\n  var updateBuilder = this.client.newUpdateBuilder('user')\n\n  test.throws(function () {\n    updateBuilder.setReturnValues('ALL_SOMETHING')\n  }, errors.InvalidReturnValuesError)\n\n  test.done()\n})\n\n// test putting an attribute for an existing record\nbuilder.add(function testPutAttributeExisting(test) {\n  var self = this\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .enableUpsert()\n    .putAttribute('age', 30)\n    .putAttribute('height', 72)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.age, 30, 'result age should be 30')\n      test.equal(data.result.height, 72, 'result height should be 72')\n      test.equal(data.previous.age, 29, 'previous age should be 29')\n      test.equal(data.previous.height, undefined, 'previous height should be undefined')\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item']['age'].N, \"30\", \"result age should be 30\")\n      test.equal(data['Item']['height'].N, \"72\", \"height should be 72\")\n    })\n})\n\n// test putting an attribute for a non-existing record\nbuilder.add(function testPutAttributeNonExisting(test) {\n  var self = this\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userB')\n    .setRangeKey('column', '@')\n    .enableUpsert()\n    .putAttribute('age', 30)\n    .putAttribute('height', 72)\n    .execute()\n    .then(function (data) {\n      test.equal(data.previous, null, 'previous should be null')\n      test.equal(data.result.age, 30, 'result age should be 30')\n      test.equal(data.result.height, 72, 'result height should be 72')\n      return utils.getItemWithSDK(self.db, \"userB\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item']['age'].N, \"30\", \"result age should be 30\")\n      test.equal(data['Item']['height'].N, \"72\", \"Height should be 72\")\n    })\n})\n\n//test putting attributes with empty would succeed\nbuilder.add(function testPutAttributeEmpty(test) {\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .enableUpsert()\n    .putAttribute('name', '')\n    .execute()\n    .then(function () {\n      test.fail(\"'testPutAttributeEmpty' failed - the query is expected to fail, but it didn't.\")\n    })\n    .fail(function (e) {\n      test.equal(e.message.indexOf('An AttributeValue may not contain an empty') !== -1, true, \"Conditional request should fail\")\n    })\n})\n\n// test adding an attribute for an existing record\nbuilder.add(function testAddAttributeExisting(test) {\n  var self = this\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .enableUpsert()\n    .addToAttribute('age', 1)\n    .addToAttribute('views', -1)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.age, 30, 'result age should be 30')\n      test.equal(data.result.views, -1, 'result views should be -1')\n      test.equal(data.previous.age, 29, 'previous age should be 29')\n      test.equal(data.previous.views, undefined, 'previous views should be undefined')\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item']['age'].N, \"30\", \"result age should be 30\")\n      test.equal(data['Item']['views'].N, \"-1\", \"views should be -1\")\n    })\n})\n\n// test adding an attribute for a non-existing record\nbuilder.add(function testAddAttributeNonExisting(test) {\n  var self = this\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userB')\n    .setRangeKey('column', '@')\n    .enableUpsert()\n    .addToAttribute('age', 1)\n    .addToAttribute('views', -1)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.age, 1, 'result age should be 30')\n      test.equal(data.result.views, -1, 'views should be -1')\n      return utils.getItemWithSDK(self.db, \"userB\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item']['age'].N, \"1\", \"result age should be 1\")\n      test.equal(data['Item']['views'].N, \"-1\", \"views should be -1\")\n    })\n})\n\n// test deleting an attribute for an existing record\nbuilder.add(function testDeleteAttributeExisting(test) {\n  var self = this\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .enableUpsert()\n    .deleteAttribute('age')\n    .deleteAttribute('height')\n    .deleteFromAttribute('someStringSet', ['b', 'c', 'd'])\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.age, undefined, 'result age should be undefined')\n      test.equal(data.result.height, undefined, 'height should be undefined')\n      test.deepEqual(data.result.someStringSet, ['a'], 'someStringSet should contain \"a\"')\n      test.equal(data.previous.age, 29, 'previous age should be 29')\n      test.equal(data.previous.height, undefined, 'height should be undefined')\n      test.deepEqual(data.previous.someStringSet, ['a', 'b', 'c'], 'someStringSet should contain \"a\", \"b\", \"c\"')\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item']['age'], undefined, 'result age should be undefined')\n      test.equal(data['Item']['height'], undefined, 'height should be undefined')\n      test.deepEqual(data['Item']['someStringSet'].SS, ['a'], 'someStringSet should contain only \"a\"')\n    })\n})\n\nbuilder.add(function testDeleteAllItemsFromStringSet(test) {\n  var self = this\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .enableUpsert()\n    .deleteFromAttribute('someStringSet', ['a', 'b', 'c', 'd'])\n    .execute()\n    .then(function (data) {\n      test.deepEqual(data.result.someStringSet, undefined, 'someStringSet should be undefined')\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.deepEqual(data['Item']['someStringSet'], undefined, 'someStringSet should be undefined')\n    })\n})\n\n// test deleting an attribute for a non-existing record\nbuilder.add(function testDeleteAttributeNonExisting(test) {\n  var self = this\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .enableUpsert()\n    .deleteAttribute('age')\n    .deleteAttribute('height')\n    .execute()\n    .then(function (data) {\n      // The data from AWS SDK is something like this:\n      // { ConsumedCapacityUnits: 1,\n      //   LastEvaluatedKey: undefined,\n      //   Count: undefined }\n      // whereas the data from dynamo-client is like this:\n      // { ConsumedCapacityUnits: 1,\n      //   LastEvaluatedKey: undefined,\n      //   Count: undefined,\n      //   result: {} }\n      // so the original testing code is:\n      // test.deepEqual(data.result, {}, 'result should be undefined')\n      test.deepEqual(data.result, {userId: 'userA', column: '@', someStringSet: ['a', 'b', 'c']}, 'fields should be updated')\n      return utils.getItemWithSDK(self.db, \"userB\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item'], undefined, \"userB with range key @ should be undefined\")\n    })\n})\n\n// test updating with conditional exists\nbuilder.add(function testUpdateWithConditional(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeEquals('column', '@')\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .putAttribute('age', 30)\n    .putAttribute('height', 72)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.age, 30, 'result age should be 30')\n      test.equal(data.result.height, 72, 'height should be 72')\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item']['age'].N, \"30\", 'result age should be 30')\n      test.equal(data['Item']['height'].N, \"72\", 'height should be 72')\n    })\n})\n\n// test updating with returnValues set to NONE\nbuilder.add(function testUpdateWithReturnValuesNone(test) {\n  var self = this\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .enableUpsert()\n    .putAttribute('age', 30)\n    .putAttribute('height', 72)\n    .setReturnValues('NONE')\n    .execute()\n    .then(function (data) {\n      test.equal(data.result, undefined)\n\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item']['age'].N, \"30\", 'result age should be 30')\n      test.equal(data['Item']['height'].N, \"72\", 'height should be 72')\n    })\n})\n\n// test updating with absent conditional exists\nbuilder.add(function testUpdateWithAbsentConditionalExists(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeAbsent('height')\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .putAttribute('age', 30)\n    .putAttribute('height', 72)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.age, 30, 'result age should be 30')\n      test.equal(data.result.height, 72, 'height should be 72')\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item']['age'].N, \"30\", 'result age should be 30')\n      test.equal(data['Item']['height'].N, \"72\", 'height should be 72')\n    })\n})\n\n// test updating with absent conditional doesn't exist\nbuilder.add(function testUpdateWithAbsentConditionalDoesNotExist(test) {\n  var self = this\n\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeAbsent('height')\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userB')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .putAttribute('age', 30)\n    .putAttribute('height', 72)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.age, 30, 'result age should be 30')\n      test.equal(data.result.height, 72, 'height should be 72')\n      return utils.getItemWithSDK(self.db, \"userB\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item']['age'].N, \"30\", 'result age should be 30')\n      test.equal(data['Item']['height'].N, \"72\", 'height should be 72')\n    })\n})\n\n// Test that deleting from a non-existant record upserts a new item\nbuilder.add(function testUpdateWithDeleteAttributeDoesNotExist(test) {\n  var self = this\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userC')\n    .setRangeKey('column', '@')\n    .enableUpsert()\n    .deleteAttribute('age')\n    .deleteAttribute('height')\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.userId, 'userC', 'userId should be set')\n      return utils.getItemWithSDK(self.db, \"userC\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data.Item.userId.S, 'userC', 'userId should be set')\n    })\n})\n\n// test updating fails with conditional exists\nbuilder.add(function testUpdateFailsWithConditional(test) {\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeEquals('age', 30)\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .putAttribute('age', 30)\n    .putAttribute('height', 72)\n    .execute()\n    .then(function () {\n      test.fail(\"'testUpdateFailsWithConditional' failed - the query is expected to fail, but it didn't.\")\n    })\n    .fail(this.client.throwUnlessConditionalError)\n})\n\n// test updating fails with conditional doesnt exist\nbuilder.add(function testUpdateFailsWithConditionalDoesNotExist(test) {\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeEquals('age', 30)\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userB')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .putAttribute('age', 30)\n    .putAttribute('height', 72)\n    .execute()\n    .then(function () {\n      test.fail(\"'testUpdateFailsWithConditionalDoesNotExist' failed - the query is expected to fail, but it didn't.\")\n    })\n    .fail(this.client.throwUnlessConditionalError)\n})\n\n// test updating fails with absent conditional exists\nbuilder.add(function testUpdateFailsWithAbsentConditional(test) {\n  var conditions = this.client.newConditionBuilder()\n    .expectAttributeAbsent('age')\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .withCondition(conditions)\n    .putAttribute('age', 30)\n    .putAttribute('height', 72)\n    .execute()\n    .then(function () {\n      test.fail(\"'testUpdateFailsWithAbsentConditional' failed - the query is expected to fail, but it didn't.\")\n    })\n    .fail(this.client.throwUnlessConditionalError)\n})\n\nbuilder.add(function testUpdateFailsWhenConditionalArgumentBad(test) {\n  try {\n    this.client.newUpdateBuilder('user')\n      .setHashKey('userId', 'userA')\n      .setRangeKey('column', '@')\n      .withCondition({age: null})\n      .putAttribute('age', 30)\n      .execute()\n    test.fail('Expected error')\n  } catch (e) {\n    if (!/Expected ConditionBuilder/.test(e.message)) {\n      throw e\n    }\n  }\n  test.done()\n})\n\nbuilder.add(function testPutAttributeWithUnderscores(test) {\n  var self = this\n\n  return this.client.newUpdateBuilder('user')\n    .setHashKey('userId', 'userA')\n    .setRangeKey('column', '@')\n    .enableUpsert()\n    .putAttribute('__age', 30)\n    .putAttribute('00height', 72)\n    .execute()\n    .then(function (data) {\n      test.equal(data.result.__age, 30, 'result __age should be 30')\n      test.equal(data.result['00height'], 72, 'result 00height should be 72')\n      return utils.getItemWithSDK(self.db, \"userA\", \"@\")\n    })\n    .then(function (data) {\n      test.equal(data['Item']['__age'].N, \"30\", \"result age should be 30\")\n      test.equal(data['Item']['00height'].N, \"72\", \"height should be 72\")\n    })\n})\n"
  },
  {
    "path": "test/utils/testUtils.js",
    "content": "/**\n * Provides ultility function for unit testing.\n *\n * @module utils\n **/\nvar AWS = require('aws-sdk')\nvar localDynamo = require('local-dynamo')\nvar Q = require('kew')\nvar dynamite = require('../../dynamite')\nvar AWSName = require('../../lib/common').AWSName\n\nvar utils = {}\n\nvar apiVersion = AWSName.API_VERSION_2012\n\n// These options make dynamite connect to the fake Dynamo DB instance.\n// The good thing here is that we can initialize a dynamite.Client\n// using the exact same way as we use in production.\nvar options = {\n  apiVersion: apiVersion,\n  sslEnabled: false,\n  endpoint: 'localhost:4567',\n  accessKeyId: 'xxx',\n  secretAccessKey: 'xxx',\n  region: 'xxx',\n  retryHandler: function (method, table, response) {\n    console.log('retrying', method, table, response)\n  }\n}\n\nutils.getMockDatabase = function () {\n  AWS.config.update(options)\n  return new AWS.DynamoDB()\n}\n\nutils.getMockDatabaseClient = function () {\n  return new dynamite.Client(options)\n}\n\nvar localDynamoProc = null\nutils.ensureLocalDynamo = function () {\n  if (!localDynamoProc) {\n    localDynamoProc = localDynamo.launch({\n      port: 4567,\n      detached: true,\n      heap: '1g'\n    })\n    localDynamoProc.on('exit', function () {\n      localDynamoProc = null\n    })\n    localDynamoProc.unref()\n  }\n\n  return localDynamoProc\n}\nprocess.on('exit', function () {\n  if (localDynamoProc) {\n    localDynamoProc.kill()\n  }\n})\n\n/*\n * A helper function that delete the testing table.\n *\n * @param db {AWS.DynamoDB} The database instance.\n * @return {Promise}\n */\nutils.deleteTable = function (db, tableName) {\n  var defer = Q.defer()\n  db.deleteTable(\n    {TableName: tableName},\n    defer.makeNodeResolver()\n  )\n  return defer.promise\n}\n\n/*\n * A helper to generate an index name for testing indices.\n *\n * @param hashKey {string}\n * @param rangeKey {string}\n */\nutils.indexNameGenerator = function (hashKey, rangeKey) {\n  var name = 'index-' + hashKey\n  if (rangeKey) name = name + '-' + rangeKey\n  return name\n}\n\n/*\n * A helper function that creates the testing table.\n *\n * @param db {AWS.DynamoDB} The database instance.\n * @return {Promise}\n */\nutils.createTable = function (db, tableName, hashKey, rangeKey, gsiDefinitions) {\n  var defer = Q.defer()\n  var opts = {}\n  if (apiVersion === AWSName.API_VERSION_2011) {\n    opts =  {\n      TableName: tableName,\n      KeySchema: {\n        HashKeyElement: {AttributeName: hashKey, AttributeType: \"S\"}\n      },\n      ProvisionedThroughput: {ReadCapacityUnits: 1, WriteCapacityUnits: 1}\n    }\n\n    if (rangeKey) {\n      opts.KeySchema.RangeKeyElement = {AttributeName: rangeKey, AttributeType: \"S\"}\n    }\n\n    db.createTable(opts, defer.makeNodeResolver())\n  } else if (apiVersion === AWSName.API_VERSION_2012) {\n    var attributeDefinitions = {}\n    attributeDefinitions[hashKey] = \"S\"\n    opts = {\n      TableName: tableName,\n      AttributeDefinitions: [],\n      KeySchema: [\n        {AttributeName: hashKey, KeyType: \"HASH\"}\n      ],\n      ProvisionedThroughput: {ReadCapacityUnits: 1, WriteCapacityUnits: 1}\n    }\n\n    if (rangeKey) {\n      attributeDefinitions[rangeKey] = \"S\"\n      opts.KeySchema.push({\n        AttributeName: rangeKey,\n        KeyType: \"RANGE\"\n      })\n    }\n\n    if (gsiDefinitions) {\n      opts.GlobalSecondaryIndexes = gsiDefinitions.map(function (index) {\n\n        var keySchema = [\n          {AttributeName: index.hashKey, KeyType: \"HASH\"}\n        ]\n        var hashKeyType = index.hashKeyType || \"S\"\n        attributeDefinitions[index.hashKey] = hashKeyType\n\n        if (index.rangeKey) {\n          var rangeKeyType = index.rangeKeyType || \"S\"\n          keySchema.push({AttributeName: index.rangeKey, KeyType: \"RANGE\"})\n          attributeDefinitions[index.rangeKey] = rangeKeyType\n        }\n        return {\n          IndexName: utils.indexNameGenerator(index.hashKey, index.rangeKey),\n          KeySchema: keySchema,\n          Projection: {\n            ProjectionType: \"ALL\"\n          },\n          ProvisionedThroughput: {ReadCapacityUnits: 1, WriteCapacityUnits: 1}\n        }\n      })\n    }\n\n    for (var field in attributeDefinitions) {\n      opts.AttributeDefinitions.push({AttributeName: field, AttributeType: attributeDefinitions[field]})\n    }\n\n    db.createTable(opts, defer.makeNodeResolver())\n  } else {\n    defer.reject(new Error('No api version found'))\n  }\n  return defer.promise\n}\n\n/*\n * A helper function that converts raw data JSON into AWS JSON format.\n *\n * Example:\n *\n * raw data JSON: { userId: 'userA', column: '@', age: '29' }\n *\n * AWS JSON: { userId: { S: 'userA' }, column: { S: '@' }, age: { N: '29' } }\n *\n * @param obj {Object} The raw JSON data\n * @return {Object} The same data in AWS JSON\n */\nvar convert = function (obj) {\n  var items = {}\n  for (var key in obj) {\n    if (Array.isArray(obj[key]) && isNaN(obj[key][0])) {\n      items[key] = {\"SS\": obj[key]}\n    } else if (Array.isArray(obj[key])) {\n      var numArray = []\n      for (var i in obj[key]) {\n        numArray.push(String(obj[key][i]))\n      }\n\n      items[key] = {\"NS\": numArray}\n    } else if (isNaN(obj[key])) {\n      items[key] = {\"S\": obj[key]}\n    } else {\n      items[key] = {\"N\": obj[key]}\n    }\n  }\n\n  return items\n}\n\n/*\n * A helper function that incert one record directly using AWS API (not\n * our own putItem)\n *\n * @param db {Object} The database instance\n * @param tableName {String} The name of the table to insert\n * @param record {Object} The raw JSON data\n * @return {Q.Promise}\n */\nvar putOneRecord = function(db, tableName, record) {\n  var defer = Q.defer()\n  db.putItem(\n    {TableName: tableName, Item: convert(record)},\n    defer.makeNodeResolver()\n  )\n  return defer.promise\n}\n\n/*\n * A helper function that initializes the testing database with\n * some data.\n *\n * @return {Promise}\n */\nutils.initTable = function (context) {\n  var db = context.db\n  var promises = []\n  for (var i = 0; i < context.data.length; i += 1) {\n    promises.push(putOneRecord(db, context.tableName, context.data[i]))\n  }\n  return Q.all(promises)\n}\n\n/*\n * Get a record from the database with the original AWS SDK.\n * The reason we don't use dynamite.getItem() here is to focus this test suite\n * on putItem().\n *\n * @param db {AWS.DynamoDB} The database instance.\n * @param hashKey {String}\n * @param rangeKey {String}\n */\nutils.getItemWithSDK = function (db, hashKey, rangeKey, table) {\n  var defer = Q.defer()\n  var opts = {}\n\n  table = table || 'user'\n\n  if (apiVersion === AWSName.API_VERSION_2011) {\n    opts = {\n      TableName: table,\n      Key: {\n        HashKeyElement: {\"S\": hashKey}\n      }\n    }\n\n    if (rangeKey) {\n      opts.Key.RangeKeyElement = {\"S\": rangeKey}\n    }\n\n    db.getItem(\n      opts,\n      defer.makeNodeResolver()\n    )\n  } else if (apiVersion === AWSName.API_VERSION_2012) {\n    opts = {\n      TableName: table,\n      Key: {\n        userId: {\"S\": hashKey}\n      }\n    }\n\n    if (rangeKey) {\n      opts.Key.column = {\"S\": rangeKey}\n    }\n\n    db.getItem(\n      opts,\n      defer.makeNodeResolver()\n    )\n  }\n  return defer.promise\n}\n\nexports = module.exports = utils\n"
  }
]