[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\"react-native\"]\n}\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"parser\": \"babel-eslint\",\n\n  \"plugins\": [\n    \"babel\"\n  ],\n\n  \"env\": {\n    \"es6\": true,\n    \"node\": true\n  },\n\n  \"ecmaFeatures\": {\n    \"arrowFunctions\": true,\n    \"binaryLiterals\": true,\n    \"blockBindings\": true,\n    \"classes\": true,\n    \"defaultParams\": true,\n    \"destructuring\": true,\n    \"experimentalObjectRestSpread\": true,\n    \"forOf\": true,\n    \"generators\": true,\n    \"globalReturn\": true,\n    \"jsx\": true,\n    \"modules\": true,\n    \"objectLiteralComputedProperties\": true,\n    \"objectLiteralDuplicateProperties\": true,\n    \"objectLiteralShorthandMethods\": true,\n    \"objectLiteralShorthandProperties\": true,\n    \"octalLiterals\": true,\n    \"regexUFlag\": true,\n    \"regexYFlag\": true,\n    \"restParams\": true,\n    \"spread\": true,\n    \"superInFunctions\": true,\n    \"templateStrings\": true,\n    \"unicodeCodePointEscapes\": true\n  },\n\n  \"rules\": {\n    \"babel/arrow-parens\": [2, \"as-needed\"],\n\n    \"array-bracket-spacing\": [2, \"always\"],\n    \"arrow-spacing\": 2,\n    \"block-scoped-var\": 0,\n    \"brace-style\": [2, \"1tbs\", {\"allowSingleLine\": true}],\n    \"callback-return\": 2,\n    \"camelcase\": [2, {\"properties\": \"always\"}],\n    \"comma-dangle\": 0,\n    \"comma-spacing\": 0,\n    \"comma-style\": [2, \"last\"],\n    \"complexity\": 0,\n    \"computed-property-spacing\": [2, \"never\"],\n    \"consistent-return\": 0,\n    \"consistent-this\": 0,\n    \"curly\": [2, \"all\"],\n    \"default-case\": 0,\n    \"dot-location\": [2, \"property\"],\n    \"dot-notation\": 0,\n    \"eol-last\": 2,\n    \"eqeqeq\": [2, \"allow-null\"],\n    \"func-names\": 0,\n    \"func-style\": 0,\n    \"generator-star-spacing\": [0, {\"before\": true, \"after\": false}],\n    \"guard-for-in\": 2,\n    \"handle-callback-err\": [2, \"error\"],\n    \"id-length\": 0,\n    \"id-match\": [2, \"^(?:_?[a-zA-Z0-9]*)|[_A-Z0-9]+$\"],\n    \"indent\": [2, 2, {\"SwitchCase\": 1}],\n    \"init-declarations\": 0,\n    \"key-spacing\": [2, {\"beforeColon\": false, \"afterColon\": true}],\n    \"linebreak-style\": 2,\n    \"lines-around-comment\": 0,\n    \"max-depth\": 0,\n    \"max-len\": [2, 100, 4],\n    \"max-nested-callbacks\": 0,\n    \"max-params\": 0,\n    \"max-statements\": 0,\n    \"new-cap\": 0,\n    \"new-parens\": 2,\n    \"newline-after-var\": 0,\n    \"no-alert\": 2,\n    \"no-array-constructor\": 2,\n    \"no-bitwise\": 0,\n    \"no-caller\": 2,\n    \"no-catch-shadow\": 0,\n    \"no-class-assign\": 2,\n    \"no-cond-assign\": 2,\n    \"no-console\": 1,\n    \"no-const-assign\": 2,\n    \"no-constant-condition\": 2,\n    \"no-continue\": 0,\n    \"no-control-regex\": 0,\n    \"no-debugger\": 1,\n    \"no-delete-var\": 2,\n    \"no-div-regex\": 2,\n    \"no-dupe-args\": 2,\n    \"no-dupe-keys\": 2,\n    \"no-duplicate-case\": 2,\n    \"no-else-return\": 2,\n    \"no-empty\": 2,\n    \"no-empty-character-class\": 2,\n//    \"no-empty-label\": 2,\n    \"no-eq-null\": 0,\n    \"no-eval\": 2,\n    \"no-ex-assign\": 2,\n    \"no-extend-native\": 2,\n    \"no-extra-bind\": 2,\n    \"no-extra-boolean-cast\": 2,\n    \"no-extra-parens\": 0,\n    \"no-extra-semi\": 2,\n    \"no-fallthrough\": 2,\n    \"no-floating-decimal\": 2,\n    \"no-func-assign\": 2,\n    \"no-implicit-coercion\": 2,\n    \"no-implied-eval\": 2,\n    \"no-inline-comments\": 0,\n    \"no-inner-declarations\": [2, \"functions\"],\n    \"no-invalid-regexp\": 2,\n    \"no-invalid-this\": 0,\n    \"no-irregular-whitespace\": 2,\n    \"no-iterator\": 2,\n    \"no-label-var\": 2,\n    \"no-labels\": 0,\n    \"no-lone-blocks\": 2,\n    \"no-lonely-if\": 2,\n    \"no-loop-func\": 0,\n    \"no-mixed-requires\": [2, true],\n    \"no-mixed-spaces-and-tabs\": 2,\n    \"no-multi-spaces\": 2,\n    \"no-multi-str\": 2,\n    \"no-multiple-empty-lines\": 0,\n    \"no-native-reassign\": 0,\n    \"no-negated-in-lhs\": 2,\n    \"no-nested-ternary\": 0,\n    \"no-new\": 2,\n    \"no-new-func\": 0,\n    \"no-new-object\": 2,\n    \"no-new-require\": 2,\n    \"no-new-wrappers\": 2,\n    \"no-obj-calls\": 2,\n    \"no-octal\": 2,\n    \"no-octal-escape\": 2,\n    \"no-param-reassign\": 2,\n    \"no-path-concat\": 2,\n    \"no-plusplus\": 0,\n    \"no-process-env\": 0,\n    \"no-process-exit\": 0,\n    \"no-proto\": 2,\n    \"no-redeclare\": 2,\n    \"no-regex-spaces\": 2,\n    \"no-restricted-modules\": 0,\n    \"no-return-assign\": 2,\n    \"no-script-url\": 2,\n    \"no-self-compare\": 0,\n    \"no-sequences\": 2,\n    \"no-shadow\": 2,\n    \"no-shadow-restricted-names\": 2,\n    \"no-spaced-func\": 2,\n    \"no-sparse-arrays\": 2,\n    \"no-sync\": 2,\n    \"no-ternary\": 0,\n    \"no-this-before-super\": 2,\n    \"no-throw-literal\": 2,\n    \"no-trailing-spaces\": 2,\n    \"no-undef\": 2,\n    \"no-undef-init\": 2,\n    \"no-undefined\": 0,\n    \"no-underscore-dangle\": 0,\n    \"no-unexpected-multiline\": 2,\n    \"no-unneeded-ternary\": 2,\n    \"no-unreachable\": 2,\n    \"no-unused-expressions\": 2,\n    \"no-unused-vars\": [2, {\"vars\": \"all\", \"args\": \"after-used\"}],\n    \"no-use-before-define\": 0,\n    \"no-useless-call\": 2,\n    \"no-var\": 0,\n    \"no-void\": 2,\n    \"no-warning-comments\": 0,\n    \"no-with\": 2,\n    \"object-curly-spacing\": [0, \"always\"],\n    \"object-shorthand\": [2, \"always\"],\n    \"one-var\": [2, \"never\"],\n    \"operator-assignment\": [2, \"always\"],\n    \"operator-linebreak\": [2, \"after\"],\n    \"padded-blocks\": 0,\n    \"prefer-const\": 0,\n    \"prefer-reflect\": 0,\n    \"prefer-spread\": 0,\n    \"quote-props\": [2, \"as-needed\"],\n    \"quotes\": [2, \"single\"],\n    \"radix\": 2,\n    \"require-yield\": 2,\n    \"semi\": [2, \"always\"],\n    \"semi-spacing\": [2, {\"before\": false, \"after\": true}],\n    \"sort-vars\": 0,\n//    \"space-after-keywords\": [2, \"always\"],\n    \"space-before-blocks\": [2, \"always\"],\n    \"space-before-function-paren\": [2, {\"anonymous\": \"always\", \"named\": \"never\"}],\n    \"space-in-parens\": 0,\n    \"space-infix-ops\": [2, {\"int32Hint\": false}],\n//    \"space-return-throw-case\": 2,\n    \"space-unary-ops\": [2, {\"words\": true, \"nonwords\": false}],\n    \"spaced-comment\": [2, \"always\"],\n    \"strict\": 0,\n    \"use-isnan\": 2,\n    \"valid-jsdoc\": 0,\n    \"valid-typeof\": 2,\n    \"vars-on-top\": 0,\n    \"wrap-iife\": 2,\n    \"wrap-regex\": 0,\n    \"yoda\": [2, \"never\", {\"exceptRange\": true}]\n  }\n}\n"
  },
  {
    "path": ".flowconfig",
    "content": "[ignore]\n.*/lib/.*\n.*/dist/.*\n.*/coverage/.*\n.*/resources/.*\n\n[include]\n\n[libs]\n./flow\n\n[options]\nesproposal.class_static_fields=enable\nesproposal.class_instance_fields=enable\nmunge_underscores=true\nsuppress_comment= \\\\(.\\\\|\\n\\\\)*\\\\$FlowIgnore\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directory\nnode_modules\n\n# Optional npm cache directory\n.npm\n\n# Optional REPL history\n.node_repl_history\n\ndist/\ndist.zip\nbuild/\nconfig.env.production\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 for dynamodb-lambda-autoscale\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# dynamodb-lambda-autoscale\n**Autoscale AWS DynamoDB using an AWS Lambda function**\n\n+ 5 minute setup process\n+ Serverless design\n+ Flexible code over configuration style\n+ Autoscale table and global secondary indexes\n+ Autoscale multiple tables\n+ Autoscale by fixed settings\n+ Autoscale by provisioned capacity utilisation\n+ Autoscale by throttled event metrics\n+ Optimised for large spikes in usage and hotkey issues by incorporating throttled event metrics\n+ Optimised performance using concurrent queries\n+ RateLimitedDecrement as imposed by AWS\n+ Statistics via 'measured'\n+ AWS credential configuration via 'dotenv'\n+ Optimised lambda package via 'webpack'\n+ ES7 code\n+ 100% [Flow](https://flowtype.org/) static type checking coverage\n\n## Disclaimer\n\nAny reliance you place on dynamodb-lambda-autoscale is strictly at your own\nrisk.\n\nIn no event will we be liable for any loss or damage including without\nlimitation, indirect or consequential loss or damage, or any loss or damage\nwhatsoever arising from loss of data or profits arising out of, or in\nconnection with, the use of this code.\n\n## Getting started\n\nNote: dynamodb-lambda-autoscale uses [Flow](https://flowtype.org/) extensively for static type\nchecking, we highly recommend you use [Nuclide](https://nuclide.io/) when making modification to code /\nconfiguration.  Please see the respective websites for advantages / reasons.\n\n1. Build and package the code\n  1. Fork the repo\n  2. Clone your fork\n  3. Create a new file in the root folder called 'config.env.production'\n  4. Put your AWS credentials into the file in the following format, only if you want to run a local test (not needed for lambda)\n\n    ```javascript\n    AWS_ACCESS_KEY_ID=\"###################\"\n    AWS_SECRET_ACCESS_KEY=\"###############\"\n    ```\n\n  5. Update [Region.json](./src/configuration/Region.json) to match the region of your DynamoDB instance\n  6. Run 'npm install'\n  7. Run 'npm run build'\n  8. Verify this has created a 'dist.zip' file\n  9. Optionally, run a local test by running 'npm run start'\n\n## Running on AWS Lambda\n\n1. Follow the steps in 'Running locally'\n2. Create an AWS Policy and Role\n  1. Create a policy called 'DynamoDBLambdaAutoscale'\n  2. Use the following content to give access to dynamoDB, cloudwatch and lambda logging\n\n      ```javascript\n      {\n        \"Version\": \"2012-10-17\",\n        \"Statement\": [\n          {\n            \"Action\": [\n              \"dynamodb:ListTables\",\n              \"dynamodb:DescribeTable\",\n              \"dynamodb:UpdateTable\",\n              \"cloudwatch:GetMetricStatistics\",\n              \"logs:CreateLogGroup\",\n              \"logs:CreateLogStream\",\n              \"logs:PutLogEvents\"\n            ],\n            \"Effect\": \"Allow\",\n            \"Resource\": \"*\"\n          }\n        ]\n      }\n      ```\n\n  3. Create a role called 'DynamoDBLambdaAutoscale'\n  4. Attach the newly created policy to the role\n3. Create a AWS Lambda function\n  1. Skip the pre defined functions step\n  2. Set the name to 'DynamoDBLambdaAutoscale'\n  3. Set the runtime to 'Node.js 4.3'\n  4. Select upload a zip file and select 'dist.zip' which you created earlier\n  5. Set the handler to 'index.handler'\n  6. Set the Role to 'DynamoDBLambdaAutoscale'\n  7. Set the Memory to the lowest value initially but test different values at a later date to see how it affects performance\n  8. Set the Timeout to approximately 5 seconds (higher or lower depending on the amount of tables you have and the selected memory setting)\n  9. Once the function is created, attach a 'scheduled event' event source and make it run every minute.  Event Sources > Add Event Source > Event Type = Cloudwatch Events - Schedule. Set the name to 'DynamoDBLambdaAutoscale' and the schedule expression to 'rate(1 minute)'\n\n## Configuration\n\nThe default setup in the [Provisioner.js](./src/Provisioner.js) allows for a quick no touch setup.\nA breakdown of the configuration behaviour is as follows:\n- AWS region is set to 'us-east-1' via [Region.json](./src/configuration/Region.json) configuration\n- Autoscales all tables and indexes\n- Autoscaling 'Strategy' settings are defined in [DefaultProvisioner.json](./src/configuration/DefaultProvisioner.json) and are as follows\n  - Separate 'Read' and 'Write' capacity adjustment strategies\n  - Separate asymmetric 'Increment' and 'Decrement' capacity adjustment strategies\n  - Read/Write provisioned capacity increased\n    - when capacity utilisation > 75% or throttled events in the last minute > 25\n    - by 3 + (0.7 * throttled events) units or by 30% + (0.7 * throttled events) of provisioned value or to 130% of the current consumed capacity, which ever is the greater\n    - with hard min/max limits of 1 and 100 respectively\n  - Read/Write provisioned capacity decreased\n    - when capacity utilisation < 30% AND\n    - when at least 60 minutes have passed since the last increment AND\n    - when at least 60 minutes have passed since the last decrement AND\n    - when the adjustment will be at least 5 units AND\n    - when we are allowed to utilise 1 of our 4 AWS enforced decrements\n    - to the consumed throughput value\n    - with hard min/max limits of 1 and 100 respectively\n\n## Strategy Settings\n\nThe strategy settings described above uses a simple schema which applies to both Read/Write and to\nboth the Increment/Decrement.  Using the options below many different strategies can be constructed:\n- ReadCapacity.Min : (Optional) Define a minimum allowed capacity, otherwise 1\n- ReadCapacity.Max : (Optional) Define a maximum allowed capacity, otherwise unlimited\n- ReadCapacity.Increment : (Optional) Defined an increment strategy\n- ReadCapacity.Increment.When : (Required) Define when capacity should be incremented\n- ReadCapacity.Increment.When.ThrottledEventsPerMinuteIsAbove : (Optional) Define a threshold at which throttled events trigger an increment\n- ReadCapacity.Increment.When.UtilisationIsAbovePercent : (Optional) Define a percentage utilisation upper threshold at which capacity is subject to recalculation\n- ReadCapacity.Increment.When.UtilisationIsBelowPercent : (Optional) Define a percentage utilisation lower threshold at which capacity is subject to recalculation, possible but non sensical for increments however.\n- ReadCapacity.Increment.When.AfterLastIncrementMinutes : (Optional) Define a grace period based off the previous increment in which capacity adjustments should not occur\n- ReadCapacity.Increment.When.AfterLastDecrementMinutes : (Optional) Define a grace period based off the previous decrement in which capacity adjustments should not occur\n- ReadCapacity.Increment.When.UnitAdjustmentGreaterThan : (Optional) Define a minimum unit adjustment so that only capacity adjustments of a certain size are allowed\n- ReadCapacity.Increment.By : (Optional) Define a 'relative' value to change the capacity by\n- ReadCapacity.Increment.By.ConsumedPercent : (Optional) Define a 'relative' percentage adjustment based on the current ConsumedCapacity\n- ReadCapacity.Increment.By.ProvisionedPercent : (Optional) Define a 'relative' percentage adjustment based on the current ProvisionedCapacity\n- ReadCapacity.Increment.By.Units : (Optional) Define a 'relative' unit adjustment\n- ReadCapacity.Increment.By.ThrottledEventsWithMultiplier : (Optional) Define a 'multiple' of the throttled events in the last minute which are added to all other 'By' unit adjustments\n- ReadCapacity.Increment.To : (Optional) Define an 'absolute' value to change the capacity to\n- ReadCapacity.Increment.To.ConsumedPercent : (Optional) Define an 'absolute' percentage adjustment based on the current ConsumedCapacity\n- ReadCapacity.Increment.To.ProvisionedPercent : (Optional) Define an 'absolute' percentage adjustment based on the current ProvisionedCapacity\n- ReadCapacity.Increment.To.Units : (Optional) Define an 'absolute' unit adjustment\n\nA sample of the strategy setting json is...\n```javascript\n{\n  \"ReadCapacity\": {\n    \"Min\": 1,\n    \"Max\": 100,\n    \"Increment\": {\n      \"When\": {\n        \"UtilisationIsAbovePercent\": 75,\n        \"ThrottledEventsPerMinuteIsAbove\": 25\n      },\n      \"By\": {\n        \"Units\": 3,\n        \"ProvisionedPercent\": 30,\n        \"ThrottledEventsWithMultiplier\": 0.7\n      },\n      \"To\": {\n        \"ConsumedPercent\": 130\n      }\n    },\n    \"Decrement\": {\n      \"When\": {\n        \"UtilisationIsBelowPercent\": 30,\n        \"AfterLastIncrementMinutes\": 60,\n        \"AfterLastDecrementMinutes\": 60,\n        \"UnitAdjustmentGreaterThan\": 5\n      },\n      \"To\": {\n        \"ConsumedPercent\": 100\n      }\n    }\n  },\n  \"WriteCapacity\": {\n    \"Min\": 1,\n    \"Max\": 100,\n    \"Increment\": {\n      \"When\": {\n        \"UtilisationIsAbovePercent\": 75,\n        \"ThrottledEventsPerMinuteIsAbove\": 25\n      },\n      \"By\": {\n        \"Units\": 3,\n        \"ProvisionedPercent\": 30,\n        \"ThrottledEventsWithMultiplier\": 0.7\n      },\n      \"To\": {\n        \"ConsumedPercent\": 130\n      }\n    },\n    \"Decrement\": {\n      \"When\": {\n        \"UtilisationIsBelowPercent\": 30,\n        \"AfterLastIncrementMinutes\": 60,\n        \"AfterLastDecrementMinutes\": 60,\n        \"UnitAdjustmentGreaterThan\": 5\n      },\n      \"To\": {\n        \"ConsumedPercent\": 100\n      }\n    }\n  }\n}\n```\n\n## Advanced Configuration\n\nThis project takes a 'React' style code first approach over declarative configuration traditionally\nused by other autoscaling community projects.  Rather than being limited to a structured\nconfiguration file or even the 'strategy' settings above you have the option to extend the [ProvisionerBase.js](./src/provisioning/ProvisionerBase.js)\nabstract base class for yourself and programmatically implement any desired logic.\n\nThe following three functions are all that is required to complete the provisioning functionality.  \nAs per the 'React' style, only actual updates to the ProvisionedCapacity will be sent to AWS.\n\n```javascript\ngetDynamoDBRegion(): string {\n  // Return the AWS region as a string\n}\n\nasync getTableNamesAsync(): Promise<string[]> {\n  // Return the table names to apply autoscaling to as a string array promise\n}\n\nasync getTableUpdateAsync(\n  tableDescription: TableDescription,\n  tableConsumedCapacityDescription: TableConsumedCapacityDescription):\n  Promise<?UpdateTableRequest> {\n  // Given an AWS DynamoDB TableDescription and AWS CloudWatch ConsumedCapacity metrics\n  // return an AWS DynamoDB UpdateTable request\n}\n```\n[DescribeTable.ResponseSyntax](http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeTable.html#API_DescribeTable_ResponseSyntax)\n[UpdateTable.RequestSyntax](http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTable.html#API_UpdateTable_RequestSyntax)\n\nFlexibility is great, but implementing all the logic required for a robust autoscaling\nstrategy isn't something everyone wants to do.  Hence, the default 'Provisioner' builds upon the base\nclass in a layered approach.  The layers are as follows:\n- [Provisioner.js](./src/Provisioner.js) concrete implementation which provides very robust autoscaling logic which can be manipulated with a 'strategy' settings json object\n- [ProvisionerConfigurableBase.js](./src/provisioning/ProvisionerConfigurableBase.js) abstract base class which breaks out the 'getTableUpdateAsync' function into more manageable abstract methods\n- [ProvisionerBase.js](./src/provisioning/ProvisionerBase.js) the root abstract base class which defines the minimum contract\n\n## Throttled Events\nThrottled events are now taken into account as part of the provisioning calculation.  A multiple of the events can be added to the existing calculation so that both large spikes in usage and hot key issues are both dealt with.\n\n## Rate Limited Decrement\n\nAWS only allows 4 table decrements in a calendar day.  To account for this we have included\nan algorithm which segments the remaining time to midnight by the amount of decrements we have left.\nThis logic allows us to utilise each 4 decrements as efficiently as possible.  The increments on the\nother hand are unlimited, so the algorithm follows a unique 'sawtooth' profile, dropping the\nprovisioned capacity all the way down to the consumed throughput rather than gradually.  Please see\n[RateLimitedDecrement.js](./src/utils/RateLimitedDecrement.js) for full implementation.\n\n## Capacity Calculation\n\nAs well as implementing the correct Provisioning logic it is also important to calculate the\nConsumedCapacity for the current point in time.  We have provided a default algorithm in\n[CapacityCalculator.js](./src/CapacityCalculator.js) which should be good enough for most purposes\nbut it could be swapped out with perhaps an improved version.  The newer version could potentially\ntake a series of data points and plot a linear regression line through them for example.\n\n## Dependencies\n\nThis project has the following main dependencies (n.b. all third party dependencies are compiled\ninto a single javascript file before being zipped and uploaded to lambda):\n+ aws-sdk - Access to AWS services\n+ dotenv - Environment variable configuration useful for lambda\n+ measured - Statistics gathering\n\n## Licensing\n\nThe source code is licensed under the MIT license found in the\n[LICENSE](LICENSE) file in the root directory of this source tree.\n"
  },
  {
    "path": "flow/aws-sdk.js",
    "content": "/* @flow */\ndeclare module 'aws-sdk' {\n  declare class DynamoDB {\n    constructor(dynamoDBConfig: DynamoDBConfig): void;\n\n    listTables(params: ?ListTablesRequest, callback: ?(err: ?Error,\n      data: ListTablesResponse) => void): PromiseRequest<ListTablesResponse>;\n\n    deleteTable(params: ?DeleteTableRequest,callback: ?(err: ?Error,\n      data: DeleteTableResponse) => void): PromiseRequest<DeleteTableResponse>;\n\n    createTable(params: ?CreateTableRequest, callback: ?(err: ?Error,\n      data: CreateTableResponse) => void): PromiseRequest<CreateTableResponse>;\n\n    describeTable(params: ?DescribeTableRequest, callback: ?(err: ?Error,\n      data: DescribeTableResponse) => void): PromiseRequest<DescribeTableResponse>;\n\n    updateTable(params: ?UpdateTableRequest, callback: ?(err: ?Error,\n      data: UpdateTableResponse) => void): PromiseRequest<UpdateTableResponse>;\n\n    scan(params: ?ScanRequest, callback: ?(err: ?Error,\n      data: ScanQueryResponse) => void): PromiseRequest<ScanQueryResponse>;\n\n    query(params: ?QueryRequest, callback: ?(err: ?Error,\n      data: ScanQueryResponse) => void): PromiseRequest<ScanQueryResponse>;\n\n    putItem(params: ?PutItemRequest, callback: ?(err: ?Error,\n      data: PutItemResponse) => void): PromiseRequest<PutItemResponse>;\n\n    getItem(params: ?GetItemRequest, callback: ?(err: ?Error,\n      data: GetItemResponse) => void): PromiseRequest<GetItemResponse>;\n\n    batchGetItem(params: ?BatchGetItemRequest, callback: ?(err: ?Error,\n      data: BatchGetItemResponse) => void): PromiseRequest<BatchGetItemResponse>;\n\n    batchWriteItem(params: ?BatchWriteItemRequest, callback: ?(err: ?Error,\n      data: BatchWriteItemResponse) => void): PromiseRequest<BatchWriteItemResponse>;\n\n    deleteItem(params: ?DeleteItemRequest, callback: ?(err: ?Error,\n      data: DeleteItemResponse) => void): PromiseRequest<DeleteItemResponse>;\n\n    updateItem(params: ?UpdateItemRequest, callback: ?(err: ?Error,\n      data: UpdateItemResponse) => void): PromiseRequest<UpdateItemResponse>;\n  }\n\n  declare class CloudWatch {\n    getMetricStatistics(params: ?GetMetricStatisticsRequest, callback: ?(err: ?Error,\n      data: GetMetricStatisticsResponse) => void): PromiseRequest<GetMetricStatisticsResponse>;\n  }\n\n  declare class PromiseRequest<T> {\n    promise(): Promise<T>;\n  }\n\n  declare type ListTablesRequest = {\n     ExclusiveStartTableName?: string,\n     Limit?: number\n  };\n\n  declare type ListTablesResponse = {\n     LastEvaluatedTableName?: string,\n     TableNames: string[]\n  };\n\n  declare type DeleteTableRequest = {\n     TableName: string,\n  };\n\n  declare type DeleteTableResponse = {\n    TableDescription: TableDescription,\n  };\n\n  declare type CreateTableRequest = {\n    AttributeDefinitions: AttributeDefinition[],\n    KeySchema: KeyDefinition[],\n    ProvisionedThroughput: ProvisionedThroughput,\n    TableName: string,\n    GlobalSecondaryIndexes: GlobalSecondaryIndex[],\n    LocalSecondaryIndexes: LocalSecondaryIndex[],\n    StreamSpecification: StreamSpecification,\n  };\n\n  declare type CreateTableResponse = {\n    TableDescription: TableDescription,\n  };\n\n  declare type ScanRequest = {\n    AttributesToGet: string[],\n    ConditionalOperator: string,\n    ConsistentRead: boolean,\n    ExclusiveStartKey: any,\n    ExpressionAttributeNames: any,\n    ExpressionAttributeValues: any,\n    FilterExpression: string,\n    IndexName: string,\n    Limit: number,\n    ProjectionExpression: string,\n    ReturnConsumedCapacity: string,\n    ScanFilter: any,\n    Segment: number,\n    Select: string,\n    TableName: string,\n    TotalSegments: number\n  };\n\n  declare type TableConsumedCapacity = {\n     CapacityUnits: number,\n  };\n\n  declare type ConsumedCapacity = {\n    CapacityUnits: number,\n    GlobalSecondaryIndexes: any,\n    LocalSecondaryIndexes: any,\n    Table: TableConsumedCapacity,\n    TableName: string\n  };\n\n  declare type ScanQueryResponse = {\n    ConsumedCapacity: ConsumedCapacity,\n    Count: number,\n    Items: any[],\n    LastEvaluatedKey: any,\n    ScannedCount: number,\n  };\n\n  declare type QueryRequest = {\n    AttributesToGet: string[],\n    ConditionalOperator: string,\n    ConsistentRead: boolean,\n    ExclusiveStartKey: any,\n    ExpressionAttributeNames: any,\n    ExpressionAttributeValues: any,\n    FilterExpression: string,\n    IndexName: string,\n    KeyConditionExpression: string,\n    KeyConditions: any,\n    Limit: number,\n    ProjectionExpression: string,\n    QueryFilter: any,\n    ReturnConsumedCapacity: string,\n    ScanIndexForward: boolean,\n    Select: string,\n    TableName: string,\n  };\n\n  declare type PutItemRequest = {\n    ConditionalOperator: string,\n    ConditionExpression: string,\n    Expected: any,\n    ExpressionAttributeNames: string,\n    ExpressionAttributeValues: any,\n    Item: any,\n    ReturnConsumedCapacity: string,\n    ReturnItemCollectionMetrics: string,\n    ReturnValues: string,\n    TableName: string,\n  };\n\n  declare type PutItemResponse = {\n    Attributes: any,\n    ConsumedCapacity: ConsumedCapacity,\n    ItemCollectionMetrics: any,\n  };\n\n  declare type GetItemRequest = {\n    AttributesToGet: string[],\n    ConsistentRead: boolean,\n    ExpressionAttributeNames: any,\n    Key: any,\n    ProjectionExpression: string,\n    ReturnConsumedCapacity: string,\n    TableName: string,\n  };\n\n  declare type GetItemResponse = {\n    ConsumedCapacity: ConsumedCapacity,\n    Item: any,\n  };\n\n  declare type BatchGetItemRequest = {\n    RequestItems: any,\n    ReturnConsumedCapacity: string,\n  };\n\n  declare type BatchGetItemResponse = {\n    ConsumedCapacity: ConsumedCapacity[],\n    Responses: any,\n    UnprocessedKeys: any,\n  };\n\n  declare type BatchWriteItemRequest = {\n    RequestItems: any,\n    ReturnConsumedCapacity: string,\n    ReturnItemCollectionMetrics: string,\n  };\n\n  declare type BatchWriteItemResponse = {\n    ConsumedCapacity: ConsumedCapacity[],\n    ItemCollectionMetrics: ItemCollectionMetrics,\n    UnprocessedKeys: any,\n  };\n\n  declare type DeleteItemRequest = {\n    ConditionalOperator: string,\n    ConditionExpression: string,\n    Expected: any,\n    ExpressionAttributeNames: any,\n    ExpressionAttributeValues: any,\n    Key: any,\n    ReturnConsumedCapacity: string,\n    ReturnItemCollectionMetrics: string,\n    ReturnValues: string,\n    TableName: string,\n  };\n\n  declare type DeleteItemResponse = {\n    Attributes: any,\n    ConsumedCapacity: ConsumedCapacity,\n    ItemCollectionMetrics: ItemCollectionMetrics,\n  };\n\n  declare type UpdateItemRequest = {\n    AttributeUpdates: any,\n    ConditionalOperator: string,\n    ConditionExpression: string,\n    Expected: any,\n    ExpressionAttributeNames: any,\n    ExpressionAttributeValues: any,\n    ReturnConsumedCapacity: string,\n    ReturnItemCollectionMetrics: string,\n    ReturnValues: string,\n    TableName: string,\n    UpdateExpression: string\n  };\n\n  declare type UpdateItemResponse = {\n    Attributes: any,\n    ConsumedCapacity: ConsumedCapacity,\n    ItemCollectionMetrics: ItemCollectionMetrics,\n  };\n\n  declare type ItemCollectionMetrics = {\n    ItemCollectionKey: any,\n    SizeEstimateRangeGB: number[],\n  };\n\n  declare type DynamoDBConfig = {\n    apiVersion: string,\n    region: string,\n    dynamoDbCrc32: boolean\n  };\n\n  declare type DynamoDBAttributeDefinition = {\n    AttributeName: string,\n    AttributeType: string\n  };\n\n  declare type DynamoDBKeySchema = {\n    AttributeName: string,\n    KeyType: string\n  };\n\n  declare type DynamoDBTable = {\n    TableName: string,\n    AttributeDefinitions: DynamoDBAttributeDefinition[],\n    KeySchema: DynamoDBKeySchema[],\n    GlobalSecondaryIndexes?: DynamoDBGlobalSecondaryIndex[],\n    LocalSecondaryIndexes?: DynamoDBLocalSecondaryIndex[],\n    ProvisionedThroughput: DynamoDBProvisionedThroughput,\n    StreamSpecification?: DynamoDBStreamSpecification\n  };\n\n  declare type DynamoDBGlobalSecondaryIndex = {\n    IndexName: string,\n    KeySchema: DynamoDBKeySchema [],\n    Projection: DynamoDBProjection,\n    ProvisionedThroughput: DynamoDBProvisionedThroughput\n  };\n\n  declare type DynamoDBLocalSecondaryIndex = {\n    IndexName: string,\n    KeySchema: DynamoDBKeySchema [],\n    Projection: DynamoDBProjection,\n  };\n\n  declare type DynamoDBProjection = {\n     NonKeyAttributes?: string[],\n     ProjectionType: string\n  };\n\n  declare type DynamoDBProvisionedThroughput = {\n    ReadCapacityUnits: number,\n    WriteCapacityUnits: number\n  };\n\n  declare type DynamoDBStreamSpecification = {\n    StreamEnabled: boolean,\n    StreamViewType: string\n  };\n\n  declare type DynamoDBSchema = {\n    tables: DynamoDBTable[]\n  };\n\n  // DynamoDB\n  declare type DynamoDBOptions = {\n    apiVersion: string,\n    region: string,\n    dynamoDbCrc32: boolean,\n    httpOptions: HTTPOptions,\n  };\n\n  declare type HTTPOptions = {\n    timeout: number,\n  };\n\n  declare type AttributeDefinition = {\n    AttributeName: string,\n    AttributeType: string,\n  };\n\n  declare type KeyDefinition = {\n    AttributeName: string,\n    KeyType: string,\n  };\n\n  declare type Projection = {\n    NonKeyAttributes: string[],\n    ProjectionType: string,\n  };\n\n  declare type ProvisionedThroughput = {\n     LastDecreaseDateTime: string,\n     LastIncreaseDateTime: string,\n     NumberOfDecreasesToday: number,\n     ReadCapacityUnits: number,\n     WriteCapacityUnits: number,\n  };\n\n  declare type Throughput = {\n    ReadCapacityUnits: number,\n    WriteCapacityUnits: number,\n  };\n\n  declare type GlobalSecondaryIndex = {\n    Backfilling: boolean,\n    IndexArn: string,\n    IndexName: string,\n    IndexSizeBytes: number,\n    IndexStatus: string,\n    ItemCount: number,\n    KeySchema: KeyDefinition[],\n    Projection: Projection,\n    ProvisionedThroughput: ProvisionedThroughput,\n  };\n\n  declare type LocalSecondaryIndex = {\n     IndexArn: string,\n     IndexName: string,\n     IndexSizeBytes: number,\n     ItemCount: number,\n     KeySchema: KeyDefinition[],\n     Projection: Projection,\n  };\n\n  declare type StreamSpecification = {\n     StreamEnabled: boolean,\n     StreamViewType: string,\n  };\n\n  declare type TableDescription = {\n    AttributeDefinitions: AttributeDefinition[],\n    CreationDateTime: number,\n    GlobalSecondaryIndexes: GlobalSecondaryIndex[],\n    ItemCount: number,\n    KeySchema: KeyDefinition[],\n    LatestStreamArn: string,\n    LatestStreamLabel: string,\n    LocalSecondaryIndexes: LocalSecondaryIndex[],\n    ProvisionedThroughput: ProvisionedThroughput,\n    StreamSpecification: StreamSpecification,\n    TableArn: string,\n    TableName: string,\n    TableSizeBytes: number,\n    TableStatus: string\n  };\n\n  declare type DescribeTableRequest = {\n     TableName: string,\n  };\n\n  declare type DescribeTableResponse = {\n    Table: TableDescription,\n  };\n\n  declare type GlobalSecondaryIndexUpdateCreate = {\n    IndexName: string,\n    KeySchema: KeyDefinition[],\n    Projection: Projection,\n    ProvisionedThroughput: Throughput,\n  };\n\n  declare type GlobalSecondaryIndexUpdateDelete = {\n    IndexName: string,\n  };\n\n  declare type GlobalSecondaryIndexUpdateUpdate = {\n    IndexName: string,\n    ProvisionedThroughput: Throughput,\n  };\n\n  declare type GlobalSecondaryIndexUpdate = {\n     Create?: GlobalSecondaryIndexUpdateCreate,\n     Delete?: GlobalSecondaryIndexUpdateDelete,\n     Update?: GlobalSecondaryIndexUpdateUpdate,\n  };\n\n  declare type UpdateTableRequest = {\n     AttributeDefinitions?: AttributeDefinition[],\n     GlobalSecondaryIndexUpdates? : GlobalSecondaryIndexUpdate[],\n     ProvisionedThroughput?: Throughput,\n     StreamSpecification?: StreamSpecification,\n     TableName: string\n  };\n\n  declare type UpdateTableResponse = {\n    TableDescription: TableDescription,\n  };\n\n  // CloudWatch\n  declare type CloudWatchOptions = {\n    apiVersion: string,\n    region: string,\n    httpOptions: HTTPOptions,\n  };\n\n  declare type GetMetricStatisticsResponse = {\n    ResponseMetadata: ResponseMetadata,\n    Label: string,\n    Datapoints: Datapoint[],\n  };\n\n  declare type Dimension = {\n    Name: string,\n    Value: string,\n  };\n\n  declare type GetMetricStatisticsRequest = {\n    Namespace: string,\n    MetricName: string,\n    Dimensions: Dimension[],\n    StartTime: Date,\n    EndTime: Date,\n    Period: number,\n    Statistics: string[],\n    Unit: string,\n  };\n\n  declare type ResponseMetadata = {\n    RequestId: string,\n  };\n\n  declare type Datapoint = {\n    Timestamp: string,\n    Average: number,\n    Sum: number,\n    Unit: string,\n  };\n}\n"
  },
  {
    "path": "flow/invariant.js",
    "content": "/* @flow */\ndeclare module 'invariant' {\n  declare class Invariant {\n    (condition: boolean, message: string): any;\n  }\n  declare var exports: Invariant;\n}\n"
  },
  {
    "path": "flow/measured.js",
    "content": "/* @flow */\ndeclare module 'measured' {\n  declare class MeasuredCollection {\n    _metrics: any;\n\n    timer(name: string): MeasuredTimer;\n\n    counter(name: string): MeasuredCounter;\n\n    toJSON(): any;\n  }\n\n  declare class MeasuredTimer {\n    start(): Stopwatch;\n  }\n\n  declare class MeasuredCounter {\n    inc(value: number): void;\n  }\n\n  declare class Stopwatch {\n    end(): void;\n  }\n\n  declare function createCollection(): MeasuredCollection;\n}\n"
  },
  {
    "path": "flow/warning.js",
    "content": "/* @flow */\ndeclare module 'warning' {\n  declare var exports: (shouldBeTrue: bool, warning: string) => void;\n}\n"
  },
  {
    "path": "gulpfile.js",
    "content": "var gulp = require(\"gulp\");\nvar del = require('del');\nvar rename = require('gulp-rename');\nvar install = require('gulp-install');\nvar zip = require('gulp-zip');\nvar uglify = require('gulp-uglify');\nvar AWS = require('aws-sdk');\nvar fs = require('fs');\nvar runSequence = require('run-sequence');\nvar webpack = require('webpack-stream');\n\n// First we need to clean out the dist folder and remove the compiled zip file.\ngulp.task('clean', function(cb) {\n  del('./dist');\n  cb();\n});\n\ngulp.task(\"webpack\", function () {\n  return gulp.src('src/Index.js')\n  .pipe(webpack( require('./webpack-dev.config.js') ))\n  .pipe(gulp.dest('dist/'));\n});\n\n// The js task could be replaced with gulp-coffee as desired.\ngulp.task(\"js\", function () {\n  return gulp\n    .src(\"dist/index.js\")\n    .pipe(gulp.dest(\"dist/\"));\n});\n\n// Here we want to install npm packages to dist, ignoring devDependencies.\ngulp.task('npm', function() {\n  return gulp\n    .src('./package.json')\n    .pipe(gulp.dest('./dist/'))\n    .pipe(install({production: true}));\n});\n\n// Next copy over environment variables managed outside of source control.\ngulp.task('env', function() {\n  return gulp\n    .src('./config.env.production')\n    .pipe(rename('config.env'))\n    .pipe(gulp.dest('./dist'));\n});\n\n// Now the dist directory is ready to go. Zip it.\ngulp.task('zip', function() {\n  return gulp\n    .src(['dist/**/*', '!dist/package.json', 'dist/.*'])\n    .pipe(zip('dist.zip'))\n    .pipe(gulp.dest('./'));\n});\n\n// Per the gulp guidelines, we do not need a plugin for something that can be\n// done easily with an existing node module. #CodeOverConfig\n//\n// Note: This presumes that AWS.config already has credentials. This will be\n// the case if you have installed and configured the AWS CLI.\n//\n// See http://aws.amazon.com/sdk-for-node-js/\ngulp.task('upload', function() {\n\n  // TODO: This should probably pull from package.json\n  AWS.config.region = 'us-east-1';\n  var lambda = new AWS.Lambda();\n  var functionName = 'video-events';\n\n  lambda.getFunction({FunctionName: functionName}, function(err, data) {\n    if (err) {\n      if (err.statusCode === 404) {\n        var warning = 'Unable to find lambda function ' + deploy_function + '. '\n        warning += 'Verify the lambda function name and AWS region are correct.'\n        gutil.log(warning);\n      } else {\n        var warning = 'AWS API request failed. '\n        warning += 'Check your AWS credentials and permissions.'\n        gutil.log(warning);\n      }\n    }\n\n    // This is a bit silly, simply because these five parameters are required.\n    var current = data.Configuration;\n    var params = {\n      FunctionName: functionName,\n      Handler: current.Handler,\n      Mode: current.Mode,\n      Role: current.Role,\n      Runtime: current.Runtime\n    };\n\n    fs.readFile('./dist.zip', function(err, data) {\n      params['FunctionZip'] = data;\n      lambda.uploadFunction(params, function(err, data) {\n        if (err) {\n          var warning = 'Package upload failed. '\n          warning += 'Check your iam:PassRole permissions.'\n          gutil.log(warning);\n        }\n      });\n    });\n  });\n});\n\n// The key to deploying as a single command is to manage the sequence of events.\ngulp.task('dist', function(cb) {\n  return runSequence(\n    ['clean'],\n    ['webpack'],\n    ['js', 'npm', 'env'],\n    ['zip'],\n//    ['upload'],\n    function (err) {\n      //if any error happened in the previous tasks, exit with a code > 0\n      if (err) {\n        cb(err);\n        var exitCode = 2;\n        console.log('[ERROR] gulp build task failed', err);\n        console.log('[FAIL] gulp build task failed - exiting with code ' + exitCode);\n        return process.exit(exitCode);\n      }\n      else {\n        return cb();\n      }\n    }\n  );\n});\n"
  },
  {
    "path": "make-webpack-config.js",
    "content": "var path = require('path');\nvar webpack = require('webpack');\nvar StatsPlugin = require('stats-webpack-plugin');\nvar ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin');\n\nmodule.exports = function (options) {\n\n  var entry = { index: './src/Index.js' };\n  var loaders = [\n    { test: /\\.jsx$/, loader: options.hotComponents ?\n\t\t\t[ 'react-hot-loader', 'babel-loader?stage=0' ] : 'babel-loader?stage=0'},\n    { test: /\\.js$/, loader: 'babel-loader', include: path.join(__dirname, 'src')},\n    { test: /\\.json$/, loader: 'json-loader' },\n    { test: /\\.md|markdown$/, loader: 'markdown-loader'}\n  ];\n\n  var alias = {};\n  var aliasLoader = {};\n  var externals = [\n\t\t{ 'aws-sdk': 'commonjs aws-sdk' }   // This is already available on the lambda server\n  ];\n  var modulesDirectories = [ 'node_modules', 'web_modules' ];\n  var extensions = [ '', '.web.js', '.js', '.jsx', '.json' ];\n  var root = __dirname;\n\n  var publicPath = options.devServer ? 'http://localhost:2992/assets/' : '/assets/';\n\n  var output = {\n    path: path.join(__dirname, 'dist'),\n    publicPath,\n    filename: '[name].js',\n    chunkFilename: options.devServer ? '[id].js' : '[name].js',\n    sourceMapFilename: 'debugging/[file].map',\n    pathinfo: options.debug,\n    libraryTarget: 'umd'\n  };\n\n  var excludeFromStats = [\n    /node_modules[\\\\\\/]react(-router)?[\\\\\\/]/,\n    /node_modules[\\\\\\/]items-store[\\\\\\/]/\n  ];\n\n  var plugins = [\n    new StatsPlugin(path.join(__dirname, 'build', 'stats.json'), {\n      chunkModules: true,\n      exclude: excludeFromStats\n    }),\n    new ContextReplacementPlugin(/moment\\.js[\\/\\\\]lang$/, /^\\.\\/(de|pl)$/)\n  ];\n\n  if(options.minimize) {\n    plugins.push(\n      new webpack.optimize.UglifyJsPlugin({\n        compressor: {\n          warnings: false\n        }\n      }),\n    new webpack.optimize.DedupePlugin()\n    );\n    plugins.push(\n\t\t\tnew webpack.DefinePlugin({'process.env': { NODE_ENV: JSON.stringify('production')}}),\n\t\t\tnew webpack.NoErrorsPlugin()\n\t\t);\n  }\n\n  return {\n    entry,\n    output,\n    target: 'node',\n    module: { loaders },\n    devtool: options.devtool,\n    debug: options.debug,\n    resolveLoader: {\n      root: path.join(__dirname, 'node_modules'),\n      alias: aliasLoader\n    },\n    externals,\n    resolve: {\n      root,\n      modulesDirectories,\n      extensions,\n      alias\n    },\n    plugins,\n    devServer: {\n      stats: {\n        cached: false,\n        exclude: excludeFromStats\n      }\n    }\n  };\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"dynamodb-lambda-autoscale\",\n  \"version\": \"0.3.0\",\n  \"description\": \"Autoscale DynamoDB provisioned capacity using AWS Lambda\",\n  \"contributors\": [\n    \"Thomas Mitchell <channlappio@gmail.com>\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"http://github.com/channl/dynamodb-lambda-autoscale.git\"\n  },\n  \"main\": \"dist/index.js\",\n  \"options\": {\n    \"mocha\": \"--require resources/mocha-bootload src/**/__tests__/**/*.js\"\n  },\n  \"scripts\": {\n    \"localtest\": \"babel-node ./scripts/localtest.js\",\n    \"test\": \"npm run lint && npm run check\",\n    \"lint\": \"eslint src\",\n    \"check\": \"flow check\",\n    \"build\": \"gulp dist\",\n    \"start\": \"node ./scripts/start.js\",\n    \"debug\": \"node --inspect --debug-brk ./scripts/start.js\"\n  },\n  \"dependencies\": {},\n  \"devDependencies\": {\n    \"async\": \"^2.0.0\",\n    \"aws-sdk\": \"2.6.9\",\n    \"babel\": \"^6.5.2\",\n    \"babel-cli\": \"^6.7.7\",\n    \"babel-eslint\": \"^6.0.4\",\n    \"babel-loader\": \"^6.2.4\",\n    \"babel-polyfill\": \"^6.7.4\",\n    \"babel-preset-react-native\": \"^1.5.7\",\n    \"del\": \"^2.2.0\",\n    \"dotenv\": \"^2.0.0\",\n    \"eslint\": \"^2.9.0\",\n    \"eslint-plugin-babel\": \"^3.2.0\",\n    \"gulp\": \"^3.9.1\",\n    \"gulp-install\": \"^0.6.0\",\n    \"gulp-rename\": \"^1.2.2\",\n    \"gulp-uglify\": \"^1.5.3\",\n    \"gulp-zip\": \"^3.2.0\",\n    \"invariant\": \"^2.2.1\",\n    \"json-loader\": \"^0.5.4\",\n    \"measured\": \"^1.1.0\",\n    \"run-sequence\": \"^1.1.5\",\n    \"stats-webpack-plugin\": \"^0.3.1\",\n    \"warning\": \"^2.1.0\",\n    \"webpack\": \"^1.13.0\",\n    \"webpack-stream\": \"^3.2.0\"\n  }\n}\n"
  },
  {
    "path": "scripts/start.js",
    "content": "/* eslint-disable no-console */\n\ntry {\n  var lambda = require('../dist/index.js');\n\n  process.chdir('./dist');\n\n  var context = {\n    succeed: data => {\n      try {\n        if (data) {\n          console.log(JSON.stringify(data));\n        }\n      } catch (e) {\n        console.error(e.stack);\n      }\n    },\n    fail: e => {\n      console.error(e.stack);\n    }\n  };\n\n  var event = {\n    json: { padding: 2 }\n  };\n\n  lambda.handler(event, context);\n\n} catch (e) {\n  console.log(e.stack);\n  console.error(e);\n}\n"
  },
  {
    "path": "src/App.js",
    "content": "/* @flow */\nimport Provisioner from './Provisioner';\nimport Stats from './utils/Stats';\nimport CostEstimation from './utils/CostEstimation';\nimport Throughput from './utils/Throughput';\nimport CapacityCalculator from './CapacityCalculator';\nimport { json, stats, log, invariant } from './Global';\nimport type { UpdateTableRequest } from 'aws-sdk';\n\nexport default class App {\n  _provisioner: Provisioner;\n  _capacityCalculator: CapacityCalculator;\n\n  constructor() {\n    this._provisioner = new Provisioner();\n    this._capacityCalculator = new CapacityCalculator();\n  }\n\n  async runAsync(event: any, context: any): Promise<void> {\n    invariant(event != null, 'The argument \\'event\\' was null');\n    invariant(context != null, 'The argument \\'context\\' was null');\n\n    let sw = stats.timer('Index.handler').start();\n\n    // In local mode the json padding can be overridden\n    if (event.json && event.json.padding) {\n      json.padding = event.json.padding;\n    }\n\n    log('Getting table names');\n    let tableNames = await this._provisioner.getTableNamesAsync();\n\n    log('Getting table details');\n    let tableDetails = await this._getTableDetailsAsync(tableNames);\n\n    log('Getting required table update requests');\n    let tableUpdateRequests = this._getTableUpdateRequests(tableDetails);\n\n    if (tableUpdateRequests.length > 0) {\n      log('Updating tables');\n      await this._updateTablesAsync(tableUpdateRequests);\n      log('Updated tables');\n    } else {\n      log('No table updates required');\n    }\n\n    sw.end();\n    this._logMetrics(tableDetails);\n\n    // Return an empty response\n    if (context) {\n      context.succeed(null);\n    }\n  }\n\n  async _getTableDetailsAsync(tableNames: string[]): Promise<Object[]> {\n    invariant(tableNames instanceof Array, 'The argument \\'tableNames\\' was not an array');\n\n    let tasks = tableNames.map(name => this._getTableDetailAsync(name));\n    return await Promise.all(tasks);\n  }\n\n  async _getTableDetailAsync(tableName: string): Promise<Object> {\n    invariant(typeof tableName === 'string', 'The argument \\'tableName\\' was not a string');\n\n    log('Getting table description', tableName);\n    let describeTableResponse = await this._provisioner.db\n      .describeTableAsync({TableName: tableName});\n\n    let tableDescription = describeTableResponse.Table;\n\n    log('Getting table consumed capacity description', tableName);\n    let consumedCapacityTableDescription = await this._capacityCalculator\n      .describeTableConsumedCapacityAsync(tableDescription);\n\n    log('Getting table update request', tableName);\n    let tableUpdateRequest = await this._provisioner.getTableUpdateAsync(tableDescription,\n      consumedCapacityTableDescription);\n\n    // Log the monthlyEstimatedCost\n    let totalTableProvisionedThroughput = Throughput\n      .getTotalTableProvisionedThroughput(tableDescription);\n\n    let monthlyEstimatedCost = CostEstimation\n      .getMonthlyEstimatedTableCost(totalTableProvisionedThroughput);\n\n    stats\n      .counter('DynamoDB.monthlyEstimatedCost')\n      .inc(monthlyEstimatedCost);\n\n    let result = {\n      tableName,\n      tableDescription,\n      consumedCapacityTableDescription,\n      tableUpdateRequest,\n      totalTableProvisionedThroughput,\n      monthlyEstimatedCost,\n    };\n\n    return result;\n  }\n\n  async _updateTablesAsync(tableUpdateRequests: UpdateTableRequest[]): Promise<void> {\n    invariant(tableUpdateRequests instanceof Array,\n      'The argument \\'tableUpdateRequests\\' was not an array');\n\n    // If we are updating more than 10 tables in a single run\n    // then we must wait until each one has been completed to\n    // ensure we do not hit the AWS limit of 10 concurrent updates\n    let isRateLimitedUpdatingRequired = tableUpdateRequests.length > 10;\n    await Promise.all(tableUpdateRequests.map(\n      async req => this._updateTableAsync(req, isRateLimitedUpdatingRequired)\n    ));\n  }\n\n  async _updateTableAsync(tableUpdateRequest: UpdateTableRequest,\n    isRateLimitedUpdatingRequired: boolean): Promise<void> {\n    invariant(tableUpdateRequest != null, 'The argument \\'tableUpdateRequest\\' was null');\n    invariant(typeof isRateLimitedUpdatingRequired === 'boolean',\n      'The argument \\'isRateLimitedUpdatingRequired\\' was not a boolean');\n\n    log('Updating table', tableUpdateRequest.TableName);\n    await this._provisioner.db\n      .updateTableWithRateLimitAsync(tableUpdateRequest, isRateLimitedUpdatingRequired);\n\n    log('Updated table', tableUpdateRequest.TableName);\n  }\n\n  _getTableUpdateRequests(tableDetails: Object[]): UpdateTableRequest[] {\n    invariant(tableDetails instanceof Array,\n      'The argument \\'tableDetails\\' was not an array');\n\n    return tableDetails\n      .filter(({tableUpdateRequest}) => { return tableUpdateRequest != null; })\n      .map(({tableUpdateRequest}) => tableUpdateRequest);\n  }\n\n  _logMetrics(tableDetails: Object[]) {\n    invariant(tableDetails instanceof Array,\n      'The argument \\'tableDetails\\' was not an array');\n\n    // Log stats\n    let st = new Stats(stats);\n    let stJSON = st.toJSON();\n    st.reset();\n\n    // Log readable info\n    let updateRequests = tableDetails.map(i => i.tableUpdateRequest).filter(i => i !== null);\n    let totalMonthlyEstimatedCost = tableDetails\n      .reduce((prev, curr) => prev + curr.monthlyEstimatedCost, 0);\n    let totalProvisionedThroughput = tableDetails.reduce((prev, curr) => {\n      return {\n        ReadCapacityUnits: prev.ReadCapacityUnits +\n          curr.totalTableProvisionedThroughput.ReadCapacityUnits,\n        WriteCapacityUnits: prev.WriteCapacityUnits +\n          curr.totalTableProvisionedThroughput.WriteCapacityUnits,\n      };\n    }, {ReadCapacityUnits: 0, WriteCapacityUnits: 0});\n\n    let indexHandler = stJSON['Index.handler'] != null ? {\n      mean: stJSON['Index.handler'].histogram.mean\n    } : undefined;\n\n    let dynamoDBListTablesAsync = stJSON['DynamoDB.listTablesAsync'] != null ? {\n      mean: stJSON['DynamoDB.listTablesAsync'].histogram.mean,\n    } : undefined;\n\n    let dynamoDBDescribeTableAsync = stJSON['DynamoDB.describeTableAsync'] != null ? {\n      mean: stJSON['DynamoDB.describeTableAsync'].histogram.mean,\n    } : undefined;\n\n    let dynamoDBDescribeTableConsumedCapacityAsync =\n      stJSON['DynamoDB.describeTableConsumedCapacityAsync'] != null ?\n        { mean: stJSON['DynamoDB.describeTableConsumedCapacityAsync'].histogram.mean } :\n        undefined;\n\n    let cloudWatchGetMetricStatisticsAsync =\n      stJSON['CloudWatch.getMetricStatisticsAsync'] != null ?\n        { mean: stJSON['CloudWatch.getMetricStatisticsAsync'].histogram.mean } :\n        undefined;\n\n    let tableUpdates = updateRequests != null ? { count: updateRequests.length } :\n      undefined;\n\n    log(JSON.stringify({\n      'Index.handler': indexHandler,\n      'DynamoDB.listTablesAsync': dynamoDBListTablesAsync,\n      'DynamoDB.describeTableAsync': dynamoDBDescribeTableAsync,\n      'DynamoDB.describeTableConsumedCapacityAsync': dynamoDBDescribeTableConsumedCapacityAsync,\n      'CloudWatch.getMetricStatisticsAsync': cloudWatchGetMetricStatisticsAsync,\n      TableUpdates: tableUpdates,\n      TotalProvisionedThroughput: totalProvisionedThroughput,\n      TotalMonthlyEstimatedCost: totalMonthlyEstimatedCost,\n    }, null, json.padding));\n  }\n}\n"
  },
  {
    "path": "src/CapacityCalculator.js",
    "content": "/* @flow */\nimport { invariant } from './Global';\nimport { Region } from './configuration/Region';\nimport CapacityCalculatorBase from './capacity/CapacityCalculatorBase';\nimport type { GetMetricStatisticsResponse } from 'aws-sdk';\nimport type { StatisticSettings } from './flow/FlowTypes';\n\nexport default class CapacityCalculator extends CapacityCalculatorBase {\n\n  // Get the region\n  getCloudWatchRegion() {\n    return Region;\n  }\n\n  getStatisticSettings(): StatisticSettings {\n    return {\n      count: 5,\n      spanMinutes: 1,\n      type: 'Sum',\n    };\n  }\n\n  getThrottledEventStatisticSettings(): StatisticSettings {\n    return {\n      count: 1,\n      spanMinutes: 1,\n      type: 'Sum',\n    };\n  }\n\n  // Gets the projected capacity value based on the cloudwatch datapoints\n  getProjectedValue(settings: StatisticSettings, data: GetMetricStatisticsResponse) {\n    invariant(data != null, 'Parameter \\'data\\' is not set');\n\n    if (data.Datapoints.length === 0) {\n      return 0;\n    }\n\n    // Default algorithm for projecting a good value for the current ConsumedThroughput is:\n    // 1. Query 5 average readings each spanning a minute\n    // 2. Select the Max value from those 5 readings\n    let spanSeconds = settings.spanMinutes * 60;\n    let averages = data.Datapoints.map(dp => dp.Sum / spanSeconds);\n    let projectedValue = Math.max(...averages);\n    return projectedValue;\n  }\n}\n"
  },
  {
    "path": "src/Global.js",
    "content": "/* @flow */\nimport measured from 'measured';\nimport _warning from 'warning';\nimport _invariant from 'invariant';\n\nexport const json = { padding: 0 };\n\nexport const stats = measured.createCollection();\n\nexport const log = (...params: any[]) => {\n  // eslint-disable-next-line no-console\n  console.log(...params);\n};\n\nexport const warning = (predicateOrValue: any, value: ?any) => {\n  if (value == null) {\n    _warning(false, predicateOrValue);\n  } else {\n    _warning(predicateOrValue, value);\n  }\n};\n\nexport const invariant = (predicateOrValue: any, value: ?any) => {\n  if (value == null) {\n    _invariant(false, predicateOrValue);\n  } else {\n    _invariant(predicateOrValue, value);\n  }\n};\n"
  },
  {
    "path": "src/Index.js",
    "content": "/* @flow */\n/* eslint-disable */\n// $FlowIgnore\nimport babelPolyfill from 'babel-polyfill';\n/* eslint-enable */\n// $FlowIgnore\nimport dotenv from 'dotenv';\nimport App from './App';\nimport { log } from './Global';\n\nlog('*** LAMBDA INIT ***');\nexport let handler = async (event: any, context: any) => {\n  try {\n    dotenv.config({path: 'config.env'});\n\n    let app = new App();\n    log('*** LAMBDA START ***');\n    await app.runAsync(event, context);\n  } catch (e) {\n    log('*** LAMBDA ERROR ***');\n    if (context) {\n      context.fail(e);\n    } else {\n      throw e;\n    }\n  } finally {\n    log('*** LAMBDA FINISH ***');\n  }\n};\n"
  },
  {
    "path": "src/Provisioner.js",
    "content": "/* @flow */\n/* eslint-disable max-len */\nimport ProvisionerConfigurableBase from './provisioning/ProvisionerConfigurableBase';\nimport RateLimitedDecrement from './utils/RateLimitedDecrement';\nimport Throughput from './utils/Throughput';\nimport ProvisionerLogging from './provisioning/ProvisionerLogging';\nimport { Region } from './configuration/Region';\nimport DefaultProvisioner from './configuration/DefaultProvisioner';\nimport { invariant } from './Global';\nimport type { TableProvisionedAndConsumedThroughput, ProvisionerConfig, AdjustmentContext } from './flow/FlowTypes';\n\nexport default class Provisioner extends ProvisionerConfigurableBase {\n\n  // Get the region\n  getDynamoDBRegion(): string {\n    return Region;\n  }\n\n  // Gets the list of tables which we want to autoscale\n  async getTableNamesAsync(): Promise<string[]> {\n\n    // Option 1 - All tables (Default)\n    return await this.db.listAllTableNamesAsync();\n\n    // Option 2 - Hardcoded list of tables\n    // return ['Table1', 'Table2', 'Table3'];\n\n    // Option 3 - DynamoDB / S3 configured list of tables\n    // return await ...;\n  }\n\n  // Gets the json settings which control how the specifed table will be autoscaled\n  // eslint-disable-next-line no-unused-vars\n  getTableConfig(data: TableProvisionedAndConsumedThroughput): ProvisionerConfig {\n\n    // Option 1 - Default settings for all tables\n    return DefaultProvisioner;\n\n    // Option 2 - Bespoke table specific settings\n    // return data.TableName === 'Table1' ? Climbing : Default;\n\n    // Option 3 - DynamoDB / S3 sourced table specific settings\n    // return await ...;\n  }\n\n  isReadCapacityIncrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {\n    invariant(data != null, 'Parameter \\'data\\' is not set');\n\n    let config = this.getTableConfig(data);\n    let adjustmentContext = this.getReadCapacityIncrementAdjustmentContext(data, config);\n    return this.isCapacityAdjustmentRequired(data, adjustmentContext);\n  }\n\n  calculateIncrementedReadCapacityValue(data: TableProvisionedAndConsumedThroughput): number {\n    invariant(data != null, 'Parameter \\'data\\' is not set');\n\n    let config = this.getTableConfig(data);\n    let adjustmentContext = this.getReadCapacityIncrementAdjustmentContext(data, config);\n    return Throughput.getAdjustedCapacityUnits(adjustmentContext);\n  }\n\n  isReadCapacityDecrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {\n    invariant(data != null, 'Parameter \\'data\\' is not set');\n\n    let config = this.getTableConfig(data);\n    let adjustmentContext = this.getReadCapacityDecrementAdjustmentContext(data, config);\n    return this.isCapacityAdjustmentRequired(data, adjustmentContext);\n  }\n\n  calculateDecrementedReadCapacityValue(data: TableProvisionedAndConsumedThroughput): number {\n    invariant(data != null, 'Parameter \\'data\\' is not set');\n\n    let config = this.getTableConfig(data);\n    let adjustmentContext = this.getReadCapacityDecrementAdjustmentContext(data, config);\n    return Throughput.getAdjustedCapacityUnits(adjustmentContext);\n  }\n\n  isWriteCapacityIncrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {\n    invariant(data != null, 'Parameter \\'data\\' is not set');\n\n    let config = this.getTableConfig(data);\n    let adjustmentContext = this.getWriteCapacityIncrementAdjustmentContext(data, config);\n    return this.isCapacityAdjustmentRequired(data, adjustmentContext);\n  }\n\n  calculateIncrementedWriteCapacityValue(data: TableProvisionedAndConsumedThroughput): number {\n    invariant(data != null, 'Parameter \\'data\\' is not set');\n\n    let config = this.getTableConfig(data);\n    let adjustmentContext = this.getWriteCapacityIncrementAdjustmentContext(data, config);\n    return Throughput.getAdjustedCapacityUnits(adjustmentContext);\n  }\n\n  isWriteCapacityDecrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {\n    invariant(data != null, 'Parameter \\'data\\' is not set');\n\n    let config = this.getTableConfig(data);\n    let adjustmentContext = this.getWriteCapacityDecrementAdjustmentContext(data, config);\n    return this.isCapacityAdjustmentRequired(data, adjustmentContext);\n  }\n\n  calculateDecrementedWriteCapacityValue(data: TableProvisionedAndConsumedThroughput): number {\n    invariant(data != null, 'Parameter \\'data\\' is not set');\n\n    let config = this.getTableConfig(data);\n    let adjustmentContext = this.getWriteCapacityDecrementAdjustmentContext(data, config);\n    return Throughput.getAdjustedCapacityUnits(adjustmentContext);\n  }\n\n  getReadCapacityIncrementAdjustmentContext(data: TableProvisionedAndConsumedThroughput, config: ProvisionerConfig): AdjustmentContext {\n    invariant(data != null, 'Argument \\'data\\' cannot be null');\n    invariant(config != null, 'Argument \\'config\\' cannot be null');\n\n    let context = {\n      TableName: data.TableName,\n      IndexName: data.IndexName,\n      CapacityType: 'read',\n      AdjustmentType: 'increment',\n      ProvisionedValue: data.ProvisionedThroughput.ReadCapacityUnits,\n      ConsumedValue: data.ConsumedThroughput.ReadCapacityUnits,\n      ThrottledEvents: data.ThrottledEvents.ThrottledReadEvents,\n      UtilisationPercent: (data.ConsumedThroughput.ReadCapacityUnits / data.ProvisionedThroughput.ReadCapacityUnits) * 100,\n      CapacityConfig: config.ReadCapacity,\n    };\n\n    if (config.ReadCapacity.Increment != null) {\n      // $FlowIgnore\n      context.CapacityAdjustmentConfig = config.ReadCapacity.Increment;\n    }\n\n    return context;\n  }\n\n  getReadCapacityDecrementAdjustmentContext(data: TableProvisionedAndConsumedThroughput, config: ProvisionerConfig): AdjustmentContext {\n    invariant(data != null, 'Argument \\'data\\' cannot be null');\n    invariant(config != null, 'Argument \\'config\\' cannot be null');\n\n    let context = {\n      TableName: data.TableName,\n      IndexName: data.IndexName,\n      CapacityType: 'read',\n      AdjustmentType: 'decrement',\n      ProvisionedValue: data.ProvisionedThroughput.ReadCapacityUnits,\n      ConsumedValue: data.ConsumedThroughput.ReadCapacityUnits,\n      ThrottledEvents: data.ThrottledEvents.ThrottledReadEvents,\n      UtilisationPercent: (data.ConsumedThroughput.ReadCapacityUnits / data.ProvisionedThroughput.ReadCapacityUnits) * 100,\n      CapacityConfig: config.ReadCapacity,\n    };\n\n    if (config.ReadCapacity.Decrement != null) {\n      // $FlowIgnore\n      context.CapacityAdjustmentConfig = config.ReadCapacity.Decrement;\n    }\n\n    return context;\n  }\n\n  getWriteCapacityIncrementAdjustmentContext(data: TableProvisionedAndConsumedThroughput, config: ProvisionerConfig): AdjustmentContext {\n    invariant(data != null, 'Argument \\'data\\' cannot be null');\n    invariant(config != null, 'Argument \\'config\\' cannot be null');\n\n    let context = {\n      TableName: data.TableName,\n      IndexName: data.IndexName,\n      CapacityType: 'write',\n      AdjustmentType: 'increment',\n      ProvisionedValue: data.ProvisionedThroughput.WriteCapacityUnits,\n      ConsumedValue: data.ConsumedThroughput.WriteCapacityUnits,\n      ThrottledEvents: data.ThrottledEvents.ThrottledWriteEvents,\n      UtilisationPercent: (data.ConsumedThroughput.WriteCapacityUnits / data.ProvisionedThroughput.WriteCapacityUnits) * 100,\n      CapacityConfig: config.WriteCapacity,\n    };\n\n    if (config.WriteCapacity.Increment != null) {\n      // $FlowIgnore\n      context.CapacityAdjustmentConfig = config.WriteCapacity.Increment;\n    }\n\n    return context;\n  }\n\n  getWriteCapacityDecrementAdjustmentContext(data: TableProvisionedAndConsumedThroughput, config: ProvisionerConfig): AdjustmentContext {\n    invariant(data != null, 'Argument \\'data\\' cannot be null');\n    invariant(config != null, 'Argument \\'config\\' cannot be null');\n\n    let context = {\n      TableName: data.TableName,\n      IndexName: data.IndexName,\n      CapacityType: 'write',\n      AdjustmentType: 'decrement',\n      ProvisionedValue: data.ProvisionedThroughput.WriteCapacityUnits,\n      ConsumedValue: data.ConsumedThroughput.WriteCapacityUnits,\n      ThrottledEvents: data.ThrottledEvents.ThrottledWriteEvents,\n      UtilisationPercent: (data.ConsumedThroughput.WriteCapacityUnits / data.ProvisionedThroughput.WriteCapacityUnits) * 100,\n      CapacityConfig: config.WriteCapacity,\n    };\n\n    if (config.WriteCapacity.Decrement != null) {\n      // $FlowIgnore\n      context.CapacityAdjustmentConfig = config.WriteCapacity.Decrement;\n    }\n\n    return context;\n  }\n\n  isCapacityAdjustmentRequired(data: TableProvisionedAndConsumedThroughput, adjustmentContext: AdjustmentContext): boolean {\n\n    // Determine if an adjustment is wanted\n    let isProvAboveMax = adjustmentContext.CapacityConfig.Max == null ? false : adjustmentContext.ProvisionedValue > adjustmentContext.CapacityConfig.Max;\n    let isProvBelowMax = adjustmentContext.CapacityConfig.Max == null ? true : adjustmentContext.ProvisionedValue < adjustmentContext.CapacityConfig.Max;\n    let isProvBelowMin = adjustmentContext.CapacityConfig.Min == null ? adjustmentContext.ProvisionedValue < 1 : adjustmentContext.ProvisionedValue < adjustmentContext.CapacityConfig.Min;\n    let isProvAboveMin = adjustmentContext.CapacityConfig.Min == null ? adjustmentContext.ProvisionedValue > 1 : adjustmentContext.ProvisionedValue > adjustmentContext.CapacityConfig.Min;\n    let isUtilAboveThreshold = this.isAboveThreshold(adjustmentContext);\n    let isUtilBelowThreshold = this.isBelowThreshold(adjustmentContext);\n    let isThrottledEventsAboveThreshold = this.isThrottledEventsAboveThreshold(adjustmentContext);\n    let isAdjustmentWanted = adjustmentContext.AdjustmentType === 'increment' ?\n      (isProvBelowMin || isUtilAboveThreshold || isUtilBelowThreshold || isThrottledEventsAboveThreshold) && isProvBelowMax :\n      (isProvAboveMax || isUtilAboveThreshold || isUtilBelowThreshold) && isProvAboveMin;\n\n    // Determine if an adjustment is allowed under the rate limiting rules\n    let isAfterLastDecreaseGracePeriod = adjustmentContext.CapacityAdjustmentConfig == null ||\n      this.isAfterLastAdjustmentGracePeriod(data.ProvisionedThroughput.LastDecreaseDateTime,\n        adjustmentContext.CapacityAdjustmentConfig.When.AfterLastDecrementMinutes);\n    let isAfterLastIncreaseGracePeriod = adjustmentContext.CapacityAdjustmentConfig == null ||\n      this.isAfterLastAdjustmentGracePeriod(data.ProvisionedThroughput.LastIncreaseDateTime,\n        adjustmentContext.CapacityAdjustmentConfig.When.AfterLastIncrementMinutes);\n\n    let isReadDecrementAllowed = adjustmentContext.AdjustmentType === 'decrement' ?\n      RateLimitedDecrement.isDecrementAllowed(data, adjustmentContext, d => this.calculateDecrementedReadCapacityValue(d)) :\n      true;\n\n    let isAdjustmentAllowed = isAfterLastDecreaseGracePeriod && isAfterLastIncreaseGracePeriod && isReadDecrementAllowed;\n\n    // Package up the configuration and the results so that we can produce\n    // some effective logs\n    let adjustmentData = {\n      isAboveMax: isProvAboveMax,\n      isBelowMin: isProvBelowMin,\n      isAboveThreshold: isUtilAboveThreshold,\n      isBelowThreshold: isUtilBelowThreshold,\n      isAboveThrottledEventThreshold: isThrottledEventsAboveThreshold,\n      isAfterLastDecreaseGracePeriod,\n      isAfterLastIncreaseGracePeriod,\n      isAdjustmentWanted,\n      isAdjustmentAllowed\n    };\n\n    // Log and return result\n    ProvisionerLogging.isAdjustmentRequiredLog(adjustmentContext, adjustmentData);\n    return isAdjustmentWanted && isAdjustmentAllowed;\n  }\n\n  isThrottledEventsAboveThreshold(context: AdjustmentContext): boolean {\n    invariant(context != null, 'Parameter \\'context\\' is not set');\n\n    if (context.CapacityAdjustmentConfig == null ||\n      context.CapacityAdjustmentConfig.When.ThrottledEventsPerMinuteIsAbove == null ||\n      context.AdjustmentType === 'decrement') {\n      return false;\n    }\n\n    return context.ThrottledEvents >\n      context.CapacityAdjustmentConfig.When.ThrottledEventsPerMinuteIsAbove;\n  }\n\n  isAboveThreshold(context: AdjustmentContext): boolean {\n    invariant(context != null, 'Parameter \\'context\\' is not set');\n\n    if (context.CapacityAdjustmentConfig == null ||\n      context.CapacityAdjustmentConfig.When.UtilisationIsAbovePercent == null) {\n      return false;\n    }\n\n    let utilisationPercent = (context.ConsumedValue / context.ProvisionedValue) * 100;\n    return utilisationPercent > context.CapacityAdjustmentConfig.When.UtilisationIsAbovePercent;\n  }\n\n  isBelowThreshold(context: AdjustmentContext): boolean {\n    invariant(context != null, 'Parameter \\'context\\' is not set');\n\n    if (context.CapacityAdjustmentConfig == null ||\n      context.CapacityAdjustmentConfig.When.UtilisationIsBelowPercent == null) {\n      return false;\n    }\n\n    let utilisationPercent = (context.ConsumedValue / context.ProvisionedValue) * 100;\n    return utilisationPercent < context.CapacityAdjustmentConfig.When.UtilisationIsBelowPercent;\n  }\n\n  isAfterLastAdjustmentGracePeriod(lastAdjustmentDateTime: string, afterLastAdjustmentMinutes?: number): boolean {\n    if (lastAdjustmentDateTime == null || afterLastAdjustmentMinutes == null) {\n      return true;\n    }\n\n    let lastDecreaseDateTime = new Date(Date.parse(lastAdjustmentDateTime));\n    let thresholdDateTime = new Date(Date.now());\n    thresholdDateTime.setMinutes(thresholdDateTime.getMinutes() - (afterLastAdjustmentMinutes));\n    return lastDecreaseDateTime < thresholdDateTime;\n  }\n}\n"
  },
  {
    "path": "src/aws/CloudWatch.js",
    "content": "/* @flow */\nimport AWS from 'aws-sdk';\nimport { json, stats, warning, invariant } from '../Global';\nimport type {\n  CloudWatchOptions,\n  GetMetricStatisticsRequest,\n  GetMetricStatisticsResponse,\n} from 'aws-sdk';\n\nexport default class CloudWatch {\n  _cw: AWS.CloudWatch;\n\n  constructor(cloudWatchOptions: CloudWatchOptions) {\n    invariant(cloudWatchOptions != null, 'Parameter \\'cloudWatchOptions\\' is not set');\n    this._cw = new AWS.CloudWatch(cloudWatchOptions);\n  }\n\n  static create(region: string): CloudWatch {\n    var options = {\n      region,\n      apiVersion: '2010-08-01',\n      httpOptions: { timeout: 5000 }\n    };\n\n    return new CloudWatch(options);\n  }\n\n  async getMetricStatisticsAsync(params: GetMetricStatisticsRequest)\n    : Promise<GetMetricStatisticsResponse> {\n    let sw = stats.timer('CloudWatch.getMetricStatisticsAsync').start();\n    try {\n      invariant(params != null, 'Parameter \\'params\\' is not set');\n      return await this._cw.getMetricStatistics(params).promise();\n    } catch (ex) {\n      warning(JSON.stringify({\n        class: 'CloudWatch',\n        function: 'getMetricStatisticsAsync',\n        params\n      }, null, json.padding));\n      throw ex;\n    } finally {\n      sw.end();\n    }\n  }\n}\n"
  },
  {
    "path": "src/aws/DynamoDB.js",
    "content": "/* @flow */\nimport AWS from 'aws-sdk';\nimport { json, stats, warning, invariant } from '../Global';\nimport Delay from '../utils/Delay';\nimport Async from 'async';\nimport type {\n  DynamoDBOptions,\n  DescribeTableRequest,\n  DescribeTableResponse,\n  UpdateTableRequest,\n  UpdateTableResponse,\n  ListTablesRequest,\n  ListTablesResponse,\n} from 'aws-sdk';\n\nexport default class DynamoDB {\n  _db: AWS.DynamoDB;\n  _updatePool: Object;\n\n  constructor(dynamoOptions: DynamoDBOptions) {\n    invariant(dynamoOptions != null, 'Parameter \\'dynamoOptions\\' is not set');\n    this._db = new AWS.DynamoDB(dynamoOptions);\n    this._updatePool = Async.queue(async (params, callback) => {\n      let result = await this.updateTableAndWaitAsync(params, true);\n      callback(result);\n    }, 10);\n  }\n\n  static create(region: string): DynamoDB {\n    var options = {\n      region,\n      apiVersion: '2012-08-10',\n      dynamoDbCrc32: false,\n      httpOptions: { timeout: 5000 }\n    };\n\n    return new DynamoDB(options);\n  }\n\n  async listTablesAsync(params: ?ListTablesRequest): Promise<ListTablesResponse> {\n    let sw = stats.timer('DynamoDB.listTablesAsync').start();\n    try {\n      return await this._db.listTables(params).promise();\n    } catch (ex) {\n      warning(JSON.stringify({\n        class: 'DynamoDB',\n        function: 'listTablesAsync'\n      }, null, json.padding));\n      throw ex;\n    } finally {\n      sw.end();\n    }\n  }\n\n  async listAllTableNamesAsync(): Promise<string[]> {\n    let tableNames = [];\n    let lastTable;\n    do {\n      let listTablesResponse = await this.listTablesAsync({ ExclusiveStartTableName: lastTable });\n      tableNames = tableNames.concat(listTablesResponse.TableNames);\n      lastTable = listTablesResponse.LastEvaluatedTableName;\n    } while (lastTable);\n    return tableNames;\n  }\n\n  async describeTableAsync(params: DescribeTableRequest): Promise<DescribeTableResponse> {\n    let sw = stats.timer('DynamoDB.describeTableAsync').start();\n    try {\n      invariant(params != null, 'Parameter \\'params\\' is not set');\n      return await this._db.describeTable(params).promise();\n    } catch (ex) {\n      warning(JSON.stringify({\n        class: 'DynamoDB',\n        function: 'describeTableAsync',\n        params\n      }, null, json.padding));\n      throw ex;\n    } finally {\n      sw.end();\n    }\n  }\n\n  async delayUntilTableIsActiveAsync(tableName: string): Promise<void> {\n    let isActive = false;\n    let attempt = 0;\n    do {\n      let result = await this.describeTableAsync({ TableName: tableName });\n      isActive = result.Table.TableStatus === 'ACTIVE';\n      if (!isActive) {\n        await Delay.delayAsync(1000);\n        attempt++;\n      }\n    } while (!isActive && attempt < 10);\n  }\n\n  updateTableWithRateLimitAsync(params: UpdateTableRequest,\n    isRateLimited: boolean): Promise<UpdateTableResponse> {\n\n    if (!isRateLimited) {\n      return this.updateTableAndWaitAsync(params, isRateLimited);\n    }\n\n    return new Promise((resolve, reject) => {\n      let sw = stats.timer('DynamoDB.updateTableAsync').start();\n      try {\n        invariant(params != null, 'Parameter \\'params\\' is not set');\n        this._updatePool.push(params, resolve);\n      } catch (ex) {\n        warning(JSON.stringify({\n          class: 'DynamoDB',\n          function: 'updateTableAsync',\n          params\n        }, null, json.padding));\n        reject(ex);\n      } finally {\n        sw.end();\n      }\n    });\n  }\n\n  async updateTableAndWaitAsync(params: UpdateTableRequest,\n    isRateLimited: boolean): Promise<UpdateTableResponse> {\n\n    let response = await this._db.updateTable(params).promise();\n    if (isRateLimited) {\n      await this.delayUntilTableIsActiveAsync(params.TableName);\n    }\n\n    return response;\n  }\n\n  async updateTableAsync(params: UpdateTableRequest): Promise<UpdateTableResponse> {\n    let sw = stats.timer('DynamoDB.updateTableAsync').start();\n    try {\n      invariant(params != null, 'Parameter \\'params\\' is not set');\n      return await this._db.updateTable(params).promise();\n    } catch (ex) {\n      warning(JSON.stringify({\n        class: 'DynamoDB',\n        function: 'updateTableAsync',\n        params\n      }, null, json.padding));\n      throw ex;\n    } finally {\n      sw.end();\n    }\n  }\n}\n"
  },
  {
    "path": "src/capacity/CapacityCalculatorBase.js",
    "content": "/* @flow */\nimport { json, stats, warning, invariant } from '../Global';\nimport CloudWatch from '../aws/CloudWatch';\nimport type {\n  TableConsumedCapacityDescription,\n  StatisticSettings,\n  ConsumedCapacityDesc,\n} from '../flow/FlowTypes';\nimport type {\n  TableDescription,\n  GetMetricStatisticsResponse,\n  Dimension,\n} from 'aws-sdk';\n\nexport default class CapacityCalculatorBase {\n  cw: CloudWatch;\n\n  constructor() {\n    this.cw = CloudWatch.create(this.getCloudWatchRegion());\n  }\n\n  // Get the region\n  getCloudWatchRegion(): string {\n    invariant(false, 'The method \\'getCloudWatchRegion\\' was not implemented');\n  }\n\n  // Gets the settings used to fetch the consumed throughput statistics\n  getStatisticSettings(): StatisticSettings {\n    invariant(false, 'The method \\'getStatisticSettings\\' was not implemented');\n  }\n\n  // Gets the settings used to fetch the throttled events statistics\n  getThrottledEventStatisticSettings(): StatisticSettings {\n    invariant(false, 'The method \\'getThrottledEventStatisticSettings\\' was not implemented');\n  }\n\n  // Gets the projected capacity value based on the cloudwatch datapoints\n  // eslint-disable-next-line no-unused-vars\n  getProjectedValue(settings: StatisticSettings, data: GetMetricStatisticsResponse): number {\n    invariant(false, 'The method \\'getProjectedValue\\' was not implemented');\n  }\n\n  async describeTableConsumedCapacityAsync(params: TableDescription)\n    : Promise<TableConsumedCapacityDescription> {\n    let sw = stats\n      .timer('DynamoDB.describeTableConsumedCapacityAsync')\n      .start();\n\n    try {\n      invariant(params != null, 'Parameter \\'params\\' is not set');\n\n      // Make all the requests concurrently\n      let tableRead = this.getConsumedCapacityAsync(true, params.TableName, null);\n      let tableWrite = this.getConsumedCapacityAsync(false, params.TableName, null);\n\n      let gsiReads = (params.GlobalSecondaryIndexes || [])\n        .map(gsi => this.getConsumedCapacityAsync(true, params.TableName, gsi.IndexName));\n\n      let gsiWrites = (params.GlobalSecondaryIndexes || [])\n        .map(gsi => this.getConsumedCapacityAsync(false, params.TableName, gsi.IndexName));\n\n      let tableTRead = this.getThrottledEventsAsync(true, params.TableName, null);\n      let tableTWrites = this.getThrottledEventsAsync(false, params.TableName, null);\n\n      let gsiTReads = (params.GlobalSecondaryIndexes || [])\n        .map(gsi => this.getThrottledEventsAsync(true, params.TableName, gsi.IndexName));\n\n      let gsiTWrites = (params.GlobalSecondaryIndexes || [])\n        .map(gsi => this.getThrottledEventsAsync(false, params.TableName, gsi.IndexName));\n\n      // Await on the results\n      let tableConsumedRead = await tableRead;\n      let tableConsumedWrite = await tableWrite;\n      let gsiConsumedReads = await Promise.all(gsiReads);\n      let gsiConsumedWrites = await Promise.all(gsiWrites);\n\n      // Await on throttled info\n      let tableThrottledRead = await tableTRead;\n      let tableThrottledWrite = await tableTWrites;\n      let gsiThrottledReads = await Promise.all(gsiTReads);\n      let gsiThrottledWrites = await Promise.all(gsiTWrites);\n\n      // Format results\n      let gsis = gsiConsumedReads.map((read, i) => {\n        let write = gsiConsumedWrites[i];\n        let throttledWrite = gsiThrottledWrites[i];\n        let throttledRead = gsiThrottledReads[i];\n        let gsiIndexName = read.globalSecondaryIndexName;\n        invariant(gsiIndexName != null, '\\'gsiIndexName\\' was null');\n        return {\n          IndexName: gsiIndexName,\n          ConsumedThroughput: {\n            ReadCapacityUnits: read.value,\n            WriteCapacityUnits: write.value\n          },\n          ThrottledEvents: {\n            ThrottledReadEvents: throttledRead,\n            ThrottledWriteEvents: throttledWrite\n          }\n        };\n      });\n\n      return {\n        TableName: params.TableName,\n        ConsumedThroughput: {\n          ReadCapacityUnits: tableConsumedRead.value,\n          WriteCapacityUnits: tableConsumedWrite.value\n        },\n        ThrottledEvents: {\n          ThrottledReadEvents: tableThrottledRead,\n          ThrottledWriteEvents: tableThrottledWrite\n        },\n        GlobalSecondaryIndexes: gsis\n      };\n    } catch (ex) {\n      warning(JSON.stringify({\n        class: 'CapacityCalculator',\n        function: 'describeTableConsumedCapacityAsync',\n        params,\n      }, null, json.padding));\n      throw ex;\n    } finally {\n      sw.end();\n    }\n  }\n\n  async getConsumedCapacityAsync(\n    isRead: boolean, tableName: string, globalSecondaryIndexName: ?string):\n    Promise<ConsumedCapacityDesc> {\n    try {\n      invariant(isRead != null, 'Parameter \\'isRead\\' is not set');\n      invariant(tableName != null, 'Parameter \\'tableName\\' is not set');\n\n      let settings = this.getStatisticSettings();\n\n      let EndTime = new Date();\n      let StartTime = new Date();\n      StartTime.setTime(EndTime - (60000 * settings.spanMinutes * settings.count));\n      let MetricName = isRead ? 'ConsumedReadCapacityUnits' : 'ConsumedWriteCapacityUnits';\n      let Dimensions = this.getDimensions(tableName, globalSecondaryIndexName);\n      let period = (settings.spanMinutes * 60);\n      let params = {\n        Namespace: 'AWS/DynamoDB',\n        MetricName,\n        Dimensions,\n        StartTime,\n        EndTime,\n        Period: period,\n        Statistics: [ settings.type ],\n        Unit: 'Count'\n      };\n\n      let statistics = await this.cw.getMetricStatisticsAsync(params);\n      let value = this.getProjectedValue(settings, statistics);\n      let result: ConsumedCapacityDesc = {\n        tableName,\n        globalSecondaryIndexName,\n        value\n      };\n\n/*\n      log(JSON.stringify({\n        ...result,\n        statistics: statistics.Datapoints.map(dp => dp.Sum / (settings.spanMinutes * 60)),\n      }));\n*/\n\n      return result;\n    } catch (ex) {\n      warning(JSON.stringify({\n        class: 'CapacityCalculator',\n        function: 'getConsumedCapacityAsync',\n        isRead, tableName, globalSecondaryIndexName,\n      }, null, json.padding));\n      throw ex;\n    }\n  }\n\n  async getThrottledEventsAsync(\n    isRead: boolean, tableName: string, globalSecondaryIndexName: ?string):\n    Promise<number> {\n    try {\n      invariant(isRead != null, 'Parameter \\'isRead\\' is not set');\n      invariant(tableName != null, 'Parameter \\'tableName\\' is not set');\n\n      let settings = this.getThrottledEventStatisticSettings();\n\n      let EndTime = new Date();\n      let StartTime = new Date();\n      StartTime.setTime(EndTime - (60000 * settings.spanMinutes * settings.count));\n      let MetricName = isRead ? 'ReadThrottleEvents' : 'WriteThrottleEvents';\n      let Dimensions = this.getDimensions(tableName, globalSecondaryIndexName);\n      let period = (settings.spanMinutes * 60);\n      let params = {\n        Namespace: 'AWS/DynamoDB',\n        MetricName,\n        Dimensions,\n        StartTime,\n        EndTime,\n        Period: period,\n        Statistics: [ settings.type ],\n        Unit: 'Count'\n      };\n\n      let statistics = await this.cw.getMetricStatisticsAsync(params);\n      let value = this.getProjectedValue(settings, statistics);\n\n      return value;\n    } catch (ex) {\n      warning(JSON.stringify({\n        class: 'CapacityCalculator',\n        function: 'getThrottledEventsAsync',\n        isRead, tableName, globalSecondaryIndexName,\n      }, null, json.padding));\n      throw ex;\n    }\n  }\n\n  getDimensions(tableName: string, globalSecondaryIndexName: ?string): Dimension[] {\n    if (globalSecondaryIndexName) {\n      return [\n        { Name: 'TableName', Value: tableName},\n        { Name: 'GlobalSecondaryIndexName', Value: globalSecondaryIndexName}\n      ];\n    }\n\n    return [ { Name: 'TableName', Value: tableName} ];\n  }\n}\n"
  },
  {
    "path": "src/configuration/ClimbingProvisioner.json",
    "content": "{\n  \"ReadCapacity\": {\n    \"Min\": 1,\n    \"Max\": 10,\n    \"Increment\": {\n      \"When\": {\n        \"UtilisationIsAbovePercent\": 80\n      },\n      \"By\": {\n        \"ConsumedPercent\": 30,\n        \"Units\": 3\n      }\n    },\n    \"Decrement\": {\n      \"When\": {\n        \"UtilisationIsBelowPercent\": 30,\n        \"AfterLastIncrementMinutes\": 60,\n        \"AfterLastDecrementMinutes\": 60,\n        \"UnitAdjustmentGreaterThan\": 5\n      },\n      \"To\": {\n        \"ConsumedPercent\": 100\n      }\n    }\n  },\n  \"WriteCapacity\": {\n    \"Min\": 1,\n    \"Max\": 10,\n    \"Increment\": {\n      \"When\": {\n        \"UtilisationIsAbovePercent\": 80\n      },\n      \"By\": {\n        \"ProvisionedPercent\": 30,\n        \"Units\": 3\n      }\n    },\n    \"Decrement\": {\n      \"When\": {\n        \"UtilisationIsBelowPercent\": 30,\n        \"AfterLastIncrementMinutes\": 60,\n        \"AfterLastDecrementMinutes\": 60,\n        \"UnitAdjustmentGreaterThan\": 5\n      },\n      \"To\": {\n        \"ConsumedPercent\": 100\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/configuration/DefaultProvisioner.json",
    "content": "{\n  \"ReadCapacity\": {\n    \"Min\": 1,\n    \"Max\": 100,\n    \"Increment\": {\n      \"When\": {\n        \"UtilisationIsAbovePercent\": 75,\n        \"ThrottledEventsPerMinuteIsAbove\": 25\n      },\n      \"By\": {\n        \"Units\": 3,\n        \"ProvisionedPercent\": 30,\n        \"ThrottledEventsWithMultiplier\": 0.7\n      },\n      \"To\": {\n        \"ConsumedPercent\": 130\n      }\n    },\n    \"Decrement\": {\n      \"When\": {\n        \"UtilisationIsBelowPercent\": 30,\n        \"AfterLastIncrementMinutes\": 60,\n        \"AfterLastDecrementMinutes\": 60,\n        \"UnitAdjustmentGreaterThan\": 5\n      },\n      \"To\": {\n        \"ConsumedPercent\": 100\n      }\n    }\n  },\n  \"WriteCapacity\": {\n    \"Min\": 1,\n    \"Max\": 100,\n    \"Increment\": {\n      \"When\": {\n        \"UtilisationIsAbovePercent\": 75,\n        \"ThrottledEventsPerMinuteIsAbove\": 25\n      },\n      \"By\": {\n        \"Units\": 3,\n        \"ProvisionedPercent\": 30,\n        \"ThrottledEventsWithMultiplier\": 0.7\n      },\n      \"To\": {\n        \"ConsumedPercent\": 130\n      }\n    },\n    \"Decrement\": {\n      \"When\": {\n        \"UtilisationIsBelowPercent\": 30,\n        \"AfterLastIncrementMinutes\": 60,\n        \"AfterLastDecrementMinutes\": 60,\n        \"UnitAdjustmentGreaterThan\": 5\n      },\n      \"To\": {\n        \"ConsumedPercent\": 100\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/configuration/FixedProvisioner.json",
    "content": "{\n  \"ReadCapacity\": {\n    \"Min\": 1,\n    \"Max\": 1\n  },\n  \"WriteCapacity\": {\n    \"Min\": 1,\n    \"Max\": 1\n  }\n}\n"
  },
  {
    "path": "src/configuration/Region.json",
    "content": "{\n  \"Region\": \"us-east-1\"\n}\n"
  },
  {
    "path": "src/flow/FlowTypes.js",
    "content": "/* @flow */\nimport type { ProvisionedThroughput, Throughput } from 'aws-sdk';\n\nexport type ThrottledEventsDescription = {\n  ThrottledReadEvents: number,\n  ThrottledWriteEvents: number\n}\n\nexport type TableProvisionedAndConsumedThroughput = {\n  TableName: string,\n  IndexName?: string,\n  ProvisionedThroughput: ProvisionedThroughput,\n  ConsumedThroughput: Throughput,\n  ThrottledEvents: ThrottledEventsDescription\n};\n\nexport type GlobalSecondaryIndexConsumedThroughput = {\n  IndexName: string,\n  ConsumedThroughput: Throughput,\n  ThrottledEvents: ThrottledEventsDescription,\n};\n\nexport type TableConsumedCapacityDescription = {\n  TableName: string,\n  ConsumedThroughput: Throughput,\n  GlobalSecondaryIndexes: GlobalSecondaryIndexConsumedThroughput[],\n  ThrottledEvents: ThrottledEventsDescription,\n};\n\nexport type ConsumedCapacityDesc = {\n  tableName: string,\n  globalSecondaryIndexName: ?string,\n  value: number,\n};\n\nexport type ProvisionerConfig = {\n  ReadCapacity: CapacityConfig,\n  WriteCapacity: CapacityConfig,\n};\n\nexport type CapacityConfig = {\n  Min?: number,\n  Max?: number,\n  Increment?: CapacityAdjustmentConfig,\n  Decrement?: CapacityAdjustmentConfig,\n};\n\nexport type CapacityAdjustmentConfig = {\n  When: WhenConfig,\n  By?: ByToConfig,\n  To?: ByToConfig,\n};\n\nexport type WhenConfig = {\n  UtilisationIsAbovePercent?: number,\n  UtilisationIsBelowPercent?: number,\n  ThrottledEventsPerMinuteIsAbove?: number,\n  AfterLastIncrementMinutes?: number,\n  AfterLastDecrementMinutes?: number,\n  UnitAdjustmentGreaterThan?: number,\n};\n\nexport type ByToConfig = {\n  ConsumedPercent?: number,\n  ProvisionedPercent?: number,\n  Units?: number,\n  ThrottledEventsWithMultiplier?: number,\n};\n\nexport type StatisticSettings = {\n  count: number,\n  spanMinutes: number,\n  type: 'Average' | 'Sum',\n};\n\nexport type AdjustmentContext = {\n  TableName: string,\n  IndexName?: string,\n  CapacityType: 'read' | 'write',\n  AdjustmentType: 'increment' | 'decrement',\n  ProvisionedValue: number,\n  ConsumedValue: number,\n  ThrottledEvents: number,\n  UtilisationPercent: number,\n  CapacityConfig: CapacityConfig,\n  CapacityAdjustmentConfig?: CapacityAdjustmentConfig,\n};\n\nexport type AdjustmentData = {\n  isAboveMax: boolean,\n  isBelowMin: boolean,\n  isAboveThreshold: boolean,\n  isBelowThreshold: boolean,\n  isAboveThrottledEventThreshold: boolean,\n  isAfterLastDecreaseGracePeriod: boolean,\n  isAfterLastIncreaseGracePeriod: boolean,\n  isAdjustmentWanted: boolean,\n  isAdjustmentAllowed: boolean\n};\n"
  },
  {
    "path": "src/provisioning/ProvisionerBase.js",
    "content": "/* @flow */\n/* eslint-disable no-unused-vars */\nimport { invariant } from '../Global';\nimport type { TableDescription, UpdateTableRequest } from 'aws-sdk';\nimport type { TableConsumedCapacityDescription } from '../flow/FlowTypes';\nimport DynamoDB from '../aws/DynamoDB';\nimport CloudWatch from '../aws/CloudWatch';\n\nexport default class ProvisionerBase {\n  db: DynamoDB;\n\n  constructor() {\n    this.db = DynamoDB.create(this.getDynamoDBRegion());\n  }\n\n  getDynamoDBRegion(): string {\n    invariant(false, 'The method \\'getDynamoDBRegion\\' was not implemented');\n  }\n\n  async getTableNamesAsync(): Promise<string[]> {\n    invariant(false, 'The method \\'getTableNamesAsync\\' was not implemented');\n  }\n\n  async getTableUpdateAsync(\n    tableDescription: TableDescription,\n    tableConsumedCapacityDescription: TableConsumedCapacityDescription):\n    Promise<?UpdateTableRequest> {\n    invariant(false, 'The method \\'getTableUpdateAsync\\' was not implemented');\n  }\n}\n"
  },
  {
    "path": "src/provisioning/ProvisionerConfigurableBase.js",
    "content": "/* @flow */\nimport { json, warning, invariant } from '../Global';\nimport ProvisionerBase from '../provisioning/ProvisionerBase';\nimport type {\n  TableDescription,\n  GlobalSecondaryIndex,\n  UpdateTableRequest,\n  GlobalSecondaryIndexUpdate,\n  Throughput,\n} from 'aws-sdk';\nimport type {\n  TableProvisionedAndConsumedThroughput,\n  TableConsumedCapacityDescription,\n} from '../flow/FlowTypes';\n\nexport default class ProvisionerConfigurableBase extends ProvisionerBase {\n\n  // eslint-disable-next-line no-unused-vars\n  isReadCapacityIncrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {\n    invariant(false, 'The method \\'isReadCapacityIncrementRequired\\' was not implemented');\n  }\n\n  // eslint-disable-next-line no-unused-vars\n  calculateIncrementedReadCapacityValue(data: TableProvisionedAndConsumedThroughput): number {\n    invariant(false, 'The method \\'calculateIncrementedReadCapacityValue\\' was not implemented');\n  }\n\n  // eslint-disable-next-line no-unused-vars\n  isReadCapacityDecrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {\n    invariant(false, 'The method \\'isReadCapacityDecrementRequired\\' was not implemented');\n  }\n\n  // eslint-disable-next-line no-unused-vars\n  calculateDecrementedReadCapacityValue(data: TableProvisionedAndConsumedThroughput): number {\n    invariant(false, 'The method \\'calculateDecrementedReadCapacityValue\\' was not implemented');\n  }\n\n  // eslint-disable-next-line no-unused-vars\n  isWriteCapacityIncrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {\n    invariant(false, 'The method \\'isWriteCapacityIncrementRequired\\' was not implemented');\n  }\n\n  // eslint-disable-next-line no-unused-vars\n  calculateIncrementedWriteCapacityValue(data: TableProvisionedAndConsumedThroughput): number {\n    invariant(false, 'The method \\'calculateIncrementedWriteCapacityValue\\' was not implemented');\n  }\n\n  // eslint-disable-next-line no-unused-vars\n  isWriteCapacityDecrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {\n    invariant(false, 'The method \\'isWriteCapacityDecrementRequired\\' was not implemented');\n  }\n\n  // eslint-disable-next-line no-unused-vars\n  calculateDecrementedWriteCapacityValue(data: TableProvisionedAndConsumedThroughput): number {\n    invariant(false, 'The method \\'calculateDecrementedWriteCapacityValue\\' was not implemented');\n  }\n\n  async getTableNamesAsync(): Promise<string[]> {\n    invariant(false, 'The method \\'getTableNamesAsync\\' was not implemented');\n  }\n\n  async getTableUpdateAsync(tableDescription: TableDescription,\n    tableConsumedCapacityDescription: TableConsumedCapacityDescription) :\n    Promise<?UpdateTableRequest> {\n    try {\n      invariant(tableDescription != null, 'Parameter \\'tableDescription\\' is not set');\n      invariant(tableConsumedCapacityDescription != null,\n        'Parameter \\'tableConsumedCapacityDescription\\' is not set');\n\n      let tableData = {\n        TableName: tableDescription.TableName,\n        ProvisionedThroughput: tableDescription.ProvisionedThroughput,\n        ConsumedThroughput: tableConsumedCapacityDescription.ConsumedThroughput,\n        ThrottledEvents: tableConsumedCapacityDescription.ThrottledEvents\n      };\n\n      let provisionedThroughput = this.getUpdatedProvisionedThroughput(tableData);\n\n      let gsis = tableDescription.GlobalSecondaryIndexes || [];\n      let globalSecondaryIndexUpdates = gsis\n        // $FlowIgnore\n        .map(gsi => this.getGlobalSecondaryIndexUpdate(\n          tableDescription, tableConsumedCapacityDescription, gsi))\n        .filter(i => i !== null);\n\n      // eslint-disable-next-line eqeqeq\n      if (!provisionedThroughput && (globalSecondaryIndexUpdates == null ||\n        globalSecondaryIndexUpdates.length === 0)) {\n        return null;\n      }\n\n      let result: UpdateTableRequest = {\n        TableName: tableDescription.TableName\n      };\n\n      if (provisionedThroughput) {\n        result.ProvisionedThroughput = provisionedThroughput;\n      }\n\n      if (globalSecondaryIndexUpdates && globalSecondaryIndexUpdates.length > 0) {\n        result.GlobalSecondaryIndexUpdates = globalSecondaryIndexUpdates;\n      }\n\n      return result;\n    } catch (e) {\n      warning(JSON.stringify({\n        class: 'ConfigurableProvisioner',\n        function: 'getTableUpdate',\n        tableDescription,\n        tableConsumedCapacityDescription\n      }, null, json.padding));\n      throw e;\n    }\n  }\n\n  getUpdatedProvisionedThroughput(params: TableProvisionedAndConsumedThroughput)\n    : ?Throughput {\n    try {\n      invariant(params != null, 'Parameter \\'params\\' is not set');\n\n      let newProvisionedThroughput = {\n        ReadCapacityUnits: params.ProvisionedThroughput.ReadCapacityUnits,\n        WriteCapacityUnits: params.ProvisionedThroughput.WriteCapacityUnits\n      };\n\n      // Adjust read capacity\n      if (this.isReadCapacityIncrementRequired(params)) {\n        newProvisionedThroughput.ReadCapacityUnits = this\n          .calculateIncrementedReadCapacityValue(params);\n\n      } else if (this.isReadCapacityDecrementRequired(params)) {\n        newProvisionedThroughput.ReadCapacityUnits = this\n          .calculateDecrementedReadCapacityValue(params);\n      }\n\n      // Adjust write capacity\n      if (this.isWriteCapacityIncrementRequired(params)) {\n        newProvisionedThroughput.WriteCapacityUnits = this\n          .calculateIncrementedWriteCapacityValue(params);\n\n      } else if (this.isWriteCapacityDecrementRequired(params)) {\n        newProvisionedThroughput.WriteCapacityUnits = this\n          .calculateDecrementedWriteCapacityValue(params);\n      }\n\n      if (newProvisionedThroughput.ReadCapacityUnits ===\n        params.ProvisionedThroughput.ReadCapacityUnits &&\n        newProvisionedThroughput.WriteCapacityUnits ===\n        params.ProvisionedThroughput.WriteCapacityUnits) {\n        return null;\n      }\n\n      return newProvisionedThroughput;\n    } catch (e) {\n      warning(JSON.stringify({\n        class: 'ConfigurableProvisioner',\n        function: 'getUpdatedProvisionedThroughput', params\n      }, null, json.padding));\n      throw e;\n    }\n  }\n\n  getGlobalSecondaryIndexUpdate(\n    tableDescription: TableDescription,\n    tableConsumedCapacityDescription: TableConsumedCapacityDescription,\n    gsi: GlobalSecondaryIndex): ?GlobalSecondaryIndexUpdate {\n    try {\n      invariant(tableDescription != null, 'Parameter \\'tableDescription\\' is not set');\n      invariant(tableConsumedCapacityDescription != null,\n        'Parameter \\'tableConsumedCapacityDescription\\' is not set');\n      invariant(gsi != null, 'Parameter \\'gsi\\' is not set');\n\n      let gsicc = tableConsumedCapacityDescription\n        .GlobalSecondaryIndexes\n        .find(i => i.IndexName === gsi.IndexName);\n\n      invariant(gsicc != null, 'Specified GSI could not be found');\n      let provisionedThroughput = this.getUpdatedProvisionedThroughput({\n        TableName: tableDescription.TableName,\n        IndexName: gsicc.IndexName,\n        ProvisionedThroughput: gsi.ProvisionedThroughput,\n        ConsumedThroughput: gsicc.ConsumedThroughput,\n        ThrottledEvents: gsicc.ThrottledEvents\n      });\n\n      // eslint-disable-next-line eqeqeq\n      if (provisionedThroughput == null) {\n        return null;\n      }\n\n      return {\n        Update: {\n          IndexName: gsi.IndexName,\n          ProvisionedThroughput: provisionedThroughput\n        }\n      };\n    } catch (e) {\n      warning(JSON.stringify({\n        class: 'ConfigurableProvisioner',\n        function: 'getGlobalSecondaryIndexUpdate',\n        tableDescription,\n        tableConsumedCapacityDescription,\n        gsi\n      }, null, json.padding));\n      throw e;\n    }\n  }\n}\n"
  },
  {
    "path": "src/provisioning/ProvisionerLogging.js",
    "content": "/* @flow */\nimport { log } from '../Global';\nimport type {\n  AdjustmentContext,\n  AdjustmentData,\n} from '../flow/FlowTypes';\n\nexport default class ConfigLogging {\n  static isAdjustmentRequiredLog(\n    adjustmentContext: AdjustmentContext,\n    adjustmentData: AdjustmentData,\n  ) {\n\n    let logMessage = typeof adjustmentContext.IndexName === 'undefined' ?\n      adjustmentContext.TableName : adjustmentContext.TableName + '.' + adjustmentContext.IndexName;\n    logMessage += ' is consuming ' + adjustmentContext.ConsumedValue + ' of ' +\n      adjustmentContext.ProvisionedValue + ' (' + adjustmentContext.UtilisationPercent +\n      '%) ' + adjustmentContext.CapacityType + ' capacity units';\n\n    if (adjustmentContext.CapacityConfig.Max != null && adjustmentData.isAboveMax) {\n      logMessage += ' and is above max allowed ' + adjustmentContext.CapacityConfig.Max + ' units';\n    }\n\n    if (adjustmentContext.CapacityAdjustmentConfig != null &&\n      adjustmentContext.CapacityAdjustmentConfig.When.UtilisationIsAbovePercent != null &&\n      adjustmentData.isAboveThreshold && !adjustmentData.isAboveMax) {\n      logMessage += ' and is above maximum threshold of ' +\n        adjustmentContext.CapacityAdjustmentConfig.When.UtilisationIsAbovePercent + '%';\n    }\n\n    if (adjustmentContext.CapacityConfig.Min != null && adjustmentData.isBelowMin) {\n      logMessage += ' and is below the min allowed ' + adjustmentContext.CapacityConfig.Min +\n        ' units';\n    }\n\n    if (adjustmentContext.CapacityAdjustmentConfig != null &&\n      adjustmentContext.CapacityAdjustmentConfig.When.UtilisationIsBelowPercent != null &&\n      adjustmentData.isBelowThreshold && !adjustmentData.isBelowMin) {\n      logMessage += ' and is below minimum threshold of ' +\n        adjustmentContext.CapacityAdjustmentConfig.When.UtilisationIsBelowPercent + '%';\n    }\n\n    if (adjustmentContext.CapacityAdjustmentConfig != null &&\n      adjustmentContext.CapacityAdjustmentConfig.When.ThrottledEventsPerMinuteIsAbove != null &&\n      adjustmentData.isAboveThrottledEventThreshold) {\n      logMessage += ' and throttled events per minute is above ' +\n        adjustmentContext.CapacityAdjustmentConfig.When.ThrottledEventsPerMinuteIsAbove + ' events';\n    }\n\n    if (adjustmentData.isAdjustmentWanted) {\n      logMessage += adjustmentContext.AdjustmentType === 'increment' ?\n        ' so an increment is WANTED' : ' so a decrement is WANTED';\n      if (adjustmentData.isAdjustmentAllowed) {\n        logMessage += ' and is ALLOWED';\n      } else if (!adjustmentData.isAfterLastDecreaseGracePeriod) {\n        logMessage += ' but is DISALLOWED due to \\'AfterLastDecrementMinutes\\' grace period';\n      } else if (!adjustmentData.isAfterLastIncreaseGracePeriod) {\n        logMessage += ' but is DISALLOWED due to \\'AfterLastIncreaseMinutes\\' grace period';\n      } else {\n        logMessage += ' but is DISALLOWED';\n      }\n    } else {\n      logMessage += adjustmentContext.AdjustmentType === 'increment' ?\n        ' so an increment is not required' : ' so a decrement is not required';\n    }\n\n    log(logMessage);\n  }\n}\n"
  },
  {
    "path": "src/utils/CostEstimation.js",
    "content": "/* @flow */\nimport { json, warning, invariant } from '../Global';\nimport type {\n  Throughput,\n} from 'aws-sdk';\n\nexport default class CostEstimation {\n\n  static getMonthlyEstimatedTableCost(provisionedThroughput: Throughput) {\n    try {\n      invariant(provisionedThroughput != null, 'Parameter \\'provisionedThroughput\\' is not set');\n\n      const averageHoursPerMonth = 720;\n      const readCostPerHour = 0.0065;\n      const readCostUnits = 50;\n      const writeCostPerHour = 0.0065;\n      const writeCostUnits = 10;\n\n      let readCost = provisionedThroughput.ReadCapacityUnits /\n        readCostUnits * readCostPerHour * averageHoursPerMonth;\n\n      let writeCost = provisionedThroughput.WriteCapacityUnits /\n        writeCostUnits * writeCostPerHour * averageHoursPerMonth;\n\n      return readCost + writeCost;\n    } catch (ex) {\n      warning(JSON.stringify({\n        class: 'CostEstimation',\n        function: 'getMonthlyEstimatedTableCost',\n        provisionedThroughput\n      }, null, json.padding));\n      throw ex;\n    }\n  }\n}\n"
  },
  {
    "path": "src/utils/Delay.js",
    "content": "/* @flow */\nimport { invariant } from '../Global';\n\nexport default class Delay {\n\n  static delayAsync(ms: number) {\n    invariant(typeof ms === 'number', 'Argument \\'ms\\' is not a number');\n\n    return new Promise(resolve => {\n      setTimeout(resolve, ms);\n    });\n  }\n}\n"
  },
  {
    "path": "src/utils/RateLimitedDecrement.js",
    "content": "/* @flow */\nimport { invariant } from '../Global';\nimport type {\n  TableProvisionedAndConsumedThroughput,\n  AdjustmentContext\n} from '../flow/FlowTypes';\n\nexport default class RateLimitedDecrement {\n\n  static isDecrementAllowed(\n    data: TableProvisionedAndConsumedThroughput,\n    adjustmentContext: AdjustmentContext,\n    calcNewValueFunc: (data: TableProvisionedAndConsumedThroughput) => number) {\n\n    invariant(data != null, 'Parameter \\'data\\' is not set');\n    invariant(adjustmentContext != null, 'Parameter \\'adjustmentContext\\' is not set');\n    invariant(calcNewValueFunc != null, 'Parameter \\'calcNewValueFunc\\' is not set');\n\n    if (this.getNextAllowedDecrementDate(data, adjustmentContext) > this.getNowDate()) {\n      // Disallow if we havent crossed one of four time barriers\n      return false;\n    }\n\n    let adjustment = Math.abs(adjustmentContext.ProvisionedValue) -\n      Math.abs(calcNewValueFunc(data));\n\n    if (adjustmentContext.CapacityAdjustmentConfig != null &&\n      adjustmentContext.CapacityAdjustmentConfig.When.UnitAdjustmentGreaterThan != null &&\n      adjustment <= adjustmentContext.CapacityAdjustmentConfig.When.UnitAdjustmentGreaterThan &&\n      this.getNowDate().valueOf() <\n      this.getLastAllowedDecrementDate().valueOf()) {\n      // Disallow if the adjustment is very small.\n      // However, if we have crossed the last time\n      // barrier of the day then we might as well allow it.\n      return false;\n    }\n\n    return true;\n  }\n\n  static getNextAllowedDecrementDate(\n    data: TableProvisionedAndConsumedThroughput,\n    adjustmentContext: AdjustmentContext) {\n\n    // Check if we have already had all the decreases we are allowed today\n    if (data.ProvisionedThroughput.NumberOfDecreasesToday >= 4) {\n      return this.getTomorrowDate();\n    }\n\n    // Get the last decrease or start of day\n    let lastDecrease = this.parseDate(data.ProvisionedThroughput.LastDecreaseDateTime);\n    let lastDecrementDate = this.getLastDecrementDate(lastDecrease);\n\n    // Get the next allowed decrement\n    let lastAllowedDecrementDate = this.getLastAllowedDecrementDate();\n    let periodMs = lastAllowedDecrementDate.valueOf() - lastDecrementDate.valueOf();\n    let periodMs2 = periodMs / (5 - data.ProvisionedThroughput.NumberOfDecreasesToday);\n    let nextDecrementDate = this.getLastDecrementDate(lastDecrease);\n    nextDecrementDate.setMilliseconds(nextDecrementDate.getMilliseconds() + periodMs2);\n\n    // Handle grace periods\n    let withIncrementGracePeriod = this.parseDate(data.ProvisionedThroughput.LastIncreaseDateTime);\n\n    if (adjustmentContext.CapacityAdjustmentConfig != null &&\n      adjustmentContext.CapacityAdjustmentConfig.When.AfterLastIncrementMinutes != null) {\n      let incMins = adjustmentContext.CapacityAdjustmentConfig.When.AfterLastIncrementMinutes;\n      withIncrementGracePeriod.setMinutes(withIncrementGracePeriod.getMinutes() + incMins);\n    }\n\n    let withDecrementGracePeriod = this.parseDate(data.ProvisionedThroughput.LastDecreaseDateTime);\n\n    if (adjustmentContext.CapacityAdjustmentConfig != null &&\n      adjustmentContext.CapacityAdjustmentConfig.When.AfterLastDecrementMinutes != null) {\n      let decMins = adjustmentContext.CapacityAdjustmentConfig.When.AfterLastDecrementMinutes;\n      withDecrementGracePeriod.setMinutes(withDecrementGracePeriod.getMinutes() + decMins);\n    }\n\n    let result = new Date(Math.max(\n      nextDecrementDate, withIncrementGracePeriod, withDecrementGracePeriod));\n\n    return result;\n  }\n\n  static getNowDate() {\n    return new Date(Date.now());\n  }\n\n  static getTodayDate() {\n    let value = this.getNowDate();\n    value.setHours(0, 0, 0, 0);\n    return value;\n  }\n\n  static getTomorrowDate() {\n    let value = this.getTodayDate();\n    value.setDate(value.getDate() + 1);\n    return value;\n  }\n\n  static getLastAllowedDecrementDate() {\n    let value = this.getTodayDate();\n    value.setHours(23, 30, 0, 0);\n    return value;\n  }\n\n  static getLastDecrementDate(lastDecrease) {\n    let today = this.getTodayDate();\n    return lastDecrease < today ? today : new Date(lastDecrease.valueOf());\n  }\n\n  static parseDate(value) {\n    // eslint-disable-next-line eqeqeq\n    if (typeof value === 'undefined' || value == null) {\n      return new Date(-8640000000000000);\n    }\n\n    return new Date(Date.parse(value));\n  }\n}\n"
  },
  {
    "path": "src/utils/Stats.js",
    "content": "/* @flow */\nimport measured from 'measured';\n\nexport default class Stats {\n  _stats: measured.MeasuredCollection;\n\n  constructor(stats: measured.MeasuredCollection) {\n    this._stats = stats;\n  }\n\n  reset() {\n    for(let name in this._stats._metrics) {\n      if ({}.hasOwnProperty.call(this._stats._metrics, name)) {\n        let metric = this._stats._metrics[name];\n        if (metric.unref) {\n          metric.unref();\n        }\n      }\n    }\n\n    this._stats._metrics = {};\n  }\n\n  toJSON(): any {\n    return this._stats.toJSON();\n  }\n\n  getSummaries() {\n    let statsData = this._stats.toJSON();\n    let statsSummary = Object\n    .keys(statsData)\n    .map(name => {\n      let mean = this.to2Dec(statsData[name].histogram.mean);\n      let count = statsData[name].meter.count;\n      return {name, mean, count};\n    });\n\n    statsSummary.sort((a, b) => {\n      if (a.mean < b.mean) {\n        return 1;\n      }\n      if (a.mean > b.mean) {\n        return -1;\n      }\n      return 0;\n    });\n\n    let nameLen = Math.max.apply(Math, statsSummary.map(i => i.name.length));\n    let statsAsStrings = statsSummary.map(s =>\n      this.padRight(s.name, nameLen + 2) +\n      this.padRight(s.mean + 'ms', 10) +\n      ' ' +\n      s.count);\n\n    return statsAsStrings;\n  }\n\n  padRight(value: string, length: number) {\n    return value + Array(length - value.length).join(' ');\n  }\n\n  padLeft(value: string, paddingValue: string) {\n    return String(paddingValue + value).slice(-paddingValue.length);\n  }\n\n  to2Dec(value: number) {\n    return parseFloat(parseFloat(Math.round(value * 100) / 100).toFixed(2));\n  }\n}\n"
  },
  {
    "path": "src/utils/Throughput.js",
    "content": "/* @flow */\n/* eslint-disable max-len */\nimport { json, warning, invariant } from '../Global';\nimport type { TableDescription, DynamoDBProvisionedThroughput } from 'aws-sdk';\nimport type { TableProvisionedAndConsumedThroughput, AdjustmentContext } from '../flow/FlowTypes';\n\nexport default class Throughput {\n\n  static getReadCapacityUtilisationPercent(data: TableProvisionedAndConsumedThroughput) {\n    invariant(data != null, 'Parameter \\'data\\' is not set');\n\n    return (\n      data.ConsumedThroughput.ReadCapacityUnits /\n      data.ProvisionedThroughput.ReadCapacityUnits) * 100;\n  }\n\n  static getWriteCapacityUtilisationPercent(data: TableProvisionedAndConsumedThroughput) {\n    invariant(data != null, 'Parameter \\'data\\' is not set');\n\n    return (\n      data.ConsumedThroughput.WriteCapacityUnits /\n      data.ProvisionedThroughput.WriteCapacityUnits) * 100;\n  }\n\n  static getAdjustedCapacityUnits(adjustmentContext: AdjustmentContext): number {\n    invariant(adjustmentContext != null, 'Parameter \\'adjustmentContext\\' is not set');\n\n    // If the provisioned units is less than minimum then simply return the minimum allowed\n    if (adjustmentContext.CapacityConfig.Min != null &&\n      adjustmentContext.ProvisionedValue < adjustmentContext.CapacityConfig.Min) {\n      return adjustmentContext.CapacityConfig.Min;\n    }\n\n    // If the provisioned units is greater than maximum then simply return the maximum allowed\n    if (adjustmentContext.CapacityConfig.Max != null &&\n      adjustmentContext.ProvisionedValue > adjustmentContext.CapacityConfig.Max) {\n      return adjustmentContext.CapacityConfig.Max;\n    }\n\n    let direction = adjustmentContext.AdjustmentType === 'increment' ? 1 : -1;\n\n    // Increment 'by' throttled events and configured mutliplier, increments only!\n    let byTE = (direction === 1 && adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.By != null && adjustmentContext.CapacityAdjustmentConfig.By.ThrottledEventsWithMultiplier != null) ?\n      (adjustmentContext.ThrottledEvents * adjustmentContext.CapacityAdjustmentConfig.By.ThrottledEventsWithMultiplier) :\n      0;\n    let byTEVal = adjustmentContext.ProvisionedValue + byTE;\n\n    // Increment 'by' percentage of provisioned\n    let byP = (adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.By != null && adjustmentContext.CapacityAdjustmentConfig.By.ProvisionedPercent != null) ?\n      (((adjustmentContext.ProvisionedValue / 100) * adjustmentContext.CapacityAdjustmentConfig.By.ProvisionedPercent) * direction) :\n      0;\n    let byPVal = adjustmentContext.ProvisionedValue + byP + byTE;\n\n    // Increment 'by' percentage of consumed\n    let byC = (adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.By != null && adjustmentContext.CapacityAdjustmentConfig.By.ConsumedPercent != null) ?\n      (((adjustmentContext.ConsumedValue / 100) * adjustmentContext.CapacityAdjustmentConfig.By.ConsumedPercent) * direction) :\n      0;\n    let byCVal = adjustmentContext.ProvisionedValue + byC + byTE;\n\n    // Increment 'by' unit value\n    let byU = (adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.By != null && adjustmentContext.CapacityAdjustmentConfig.By.Units != null) ?\n      (adjustmentContext.CapacityAdjustmentConfig.By.Units * direction) :\n      0;\n    let byUVal = adjustmentContext.ProvisionedValue + byU + byTE;\n\n    // Increment 'to' percentage of provisioned\n    let toP = (adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.To != null && adjustmentContext.CapacityAdjustmentConfig.To.ProvisionedPercent != null) ?\n      (adjustmentContext.ProvisionedValue / 100) * adjustmentContext.CapacityAdjustmentConfig.To.ProvisionedPercent :\n      adjustmentContext.ProvisionedValue;\n\n    // Increment 'to' percentage of consumed\n    let toC = (adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.To != null && adjustmentContext.CapacityAdjustmentConfig.To.ConsumedPercent != null) ?\n      (adjustmentContext.ConsumedValue / 100) * adjustmentContext.CapacityAdjustmentConfig.To.ConsumedPercent :\n      adjustmentContext.ProvisionedValue;\n\n    // Increment 'to' unit value\n    let toU = (adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.To != null && adjustmentContext.CapacityAdjustmentConfig.To.Units != null) ?\n      adjustmentContext.CapacityAdjustmentConfig.To.Units :\n      adjustmentContext.ProvisionedValue;\n\n    // Select the greatest calculated increment\n    let newValue = adjustmentContext.AdjustmentType === 'increment' ?\n      Math.max(byPVal, byCVal, byUVal, byTEVal, toP, toC, toU) :\n      Math.min(byPVal, byCVal, byUVal, byTEVal, toP, toC, toU);\n\n    // Limit to 'max' if it is specified\n    if (adjustmentContext.CapacityConfig.Max != null) {\n      newValue = Math.min(newValue, adjustmentContext.CapacityConfig.Max);\n    }\n\n    // Limit to 'min' if it is specified\n    if (adjustmentContext.CapacityConfig.Min != null) {\n      newValue = Math.max(newValue, adjustmentContext.CapacityConfig.Min, 1);\n    }\n\n    // Ensure we return a whole number\n    return Math.round(newValue);\n  }\n\n  static getTotalTableProvisionedThroughput(params: TableDescription)\n    : DynamoDBProvisionedThroughput {\n    try {\n      invariant(typeof params !== 'undefined', 'Parameter \\'params\\' is not set');\n\n      let ReadCapacityUnits = params.ProvisionedThroughput.ReadCapacityUnits;\n      let WriteCapacityUnits = params.ProvisionedThroughput.WriteCapacityUnits;\n\n      if (params.GlobalSecondaryIndexes) {\n        ReadCapacityUnits += params.GlobalSecondaryIndexes\n          .reduce((prev, curr) =>\n            prev + curr.ProvisionedThroughput.ReadCapacityUnits, 0);\n\n        WriteCapacityUnits += params.GlobalSecondaryIndexes\n          .reduce((prev, curr) =>\n            prev + curr.ProvisionedThroughput.WriteCapacityUnits, 0);\n      }\n\n      return {\n        ReadCapacityUnits,\n        WriteCapacityUnits\n      };\n    } catch (ex) {\n      warning(JSON.stringify({\n        class: 'Throughput',\n        function: 'getTotalTableProvisionedThroughput',\n        params\n      }, null, json.padding));\n      throw ex;\n    }\n  }\n}\n"
  },
  {
    "path": "webpack-dev.config.js",
    "content": "module.exports = require(\"./make-webpack-config\")({\n\tdevtool: \"source-map\",\n\tdebug: true\n});\n"
  },
  {
    "path": "webpack-prod.config.js",
    "content": "module.exports = require(\"./make-webpack-config\")({\n\tminimize: true\n});\n"
  },
  {
    "path": "webpack.config.js",
    "content": "module.exports = require(\"./make-webpack-config\")({\n\n});\n"
  }
]