├── .github └── workflows │ ├── cloud_ci.yml │ └── local_ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── api ├── api.ts ├── collection.ts ├── mod.ts ├── operation.ts └── shape.ts ├── client ├── aws_signature_v4.ts ├── base_fetch.ts ├── base_op.ts ├── converter.ts ├── create_cache.ts ├── create_headers.ts ├── derive_config.ts ├── mod.ts └── translator.ts ├── deps.ts ├── make_the_docs.ts ├── mod.ts ├── start_db.sh ├── test ├── cloud.ts ├── cloud_setup.yml ├── local.ts └── signv4.ts └── util.ts /.github/workflows/cloud_ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | release: 8 | types: 9 | - created 10 | 11 | jobs: 12 | test_cloud: 13 | name: test dynamodb cloud 14 | runs-on: ubuntu-latest 15 | env: 16 | AWS_REGION: us-east-1 17 | TABLE_NAME: testing_table 18 | steps: 19 | - name: clone repo 20 | uses: actions/checkout@v2.1.0 21 | - name: install deno 22 | uses: denolib/setup-deno@master 23 | - name: configure aws credentials 24 | uses: aws-actions/configure-aws-credentials@v1 25 | with: 26 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 27 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 28 | aws-region: ${{ env.AWS_REGION }} 29 | - name: run tests 30 | run: deno test --allow-env --allow-read=$HOME/.aws --allow-net ./test/cloud.ts -------------------------------------------------------------------------------- /.github/workflows/local_ci.yml: -------------------------------------------------------------------------------- 1 | name: local ci 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test_signv4: 9 | name: test dynamodb signature v4 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: clone repo 13 | uses: actions/checkout@v2.1.0 14 | - name: install deno 15 | uses: denolib/setup-deno@master 16 | - name: run tests 17 | run: deno run --allow-env ./test/signv4.ts 18 | test_local: 19 | name: test dynamodb local 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: clone repo 23 | uses: actions/checkout@v2.1.0 24 | - name: install deno 25 | uses: denolib/setup-deno@master 26 | - name: start a local dynamodb 27 | run: curl -fsSL https://denopkg.com/chiefbiiko/dynamodb@v0.2.0/start_db.sh | bash 28 | - name: sleep 2 allow db startup 29 | run: sleep 4.19 30 | - name: run tests 31 | run: deno test --allow-env --allow-net ./test/signv4.ts ./test/local.ts -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dynamodb_local_latest* 2 | shared-local-instance.db* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-2020 Noah Anabiik Schwarz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dynamodb 2 | 3 | ![ci](https://github.com/chiefbiiko/dynamodb/workflows/ci/badge.svg?branch=master) 4 | 5 | DynamoDB client. 6 | 7 | ## Usage 8 | 9 | ``` ts 10 | import { createClient } from "https://denopkg.com/chiefbiiko/dynamodb/mod.ts"; 11 | 12 | // if config/credentials not passed they will be read from the env/fs 13 | const dyno = createClient(); 14 | 15 | // the client has all of DynamoDB's operations as camelCased async methods 16 | const result = await dyno.listTables(); 17 | ``` 18 | 19 | The client config can be omitted entirely when calling `createClient`. If that is the case the config will be derived from the environment and filesystem, in that order, using [`get-aws-config`](https://github.com/chiefbiiko/get-aws-config). 20 | 21 | Prefer using temporary credentials and a session token. 22 | 23 | ## API 24 | 25 | ### Contents 26 | 27 | 1. [Basics](#Basics) 28 | 29 | 2. [Factory](#Factory) 30 | 31 | 3. [Ops](#Ops) 32 | 33 | + [BatchGetItem](#BatchGetItem) 34 | 35 | + [BatchWriteItem](#BatchWriteItem) 36 | 37 | + [CreateBackup](#CreateBackup) 38 | 39 | + [CreateGlobalTable](#CreateGlobalTable) 40 | 41 | + [CreateTable](#CreateTable) 42 | 43 | + [DeleteBackup](#DeleteBackup) 44 | 45 | + [DeleteItem](#DeleteItem) 46 | 47 | + [DeleteTable](#DeleteTable) 48 | 49 | + [DescribeBackup](#DescribeBackup) 50 | 51 | + [DescribeContinuousBackups](#DescribeContinuousBackups) 52 | 53 | + [DescribeEndpoints](#DescribeEndpoints) 54 | 55 | + [DescribeGlobalTable](#DescribeGlobalTable) 56 | 57 | + [DescribeGlobalTableSettings](#DescribeGlobalTableSettings) 58 | 59 | + [DescribeLimits](#DescribeLimits) 60 | 61 | + [DescribeTable](#DescribeTable) 62 | 63 | + [DescribeTimeToLive](#DescribeTimeToLive) 64 | 65 | + [GetItem](#GetItem) 66 | 67 | + [ListBackups](#ListBackups) 68 | 69 | + [ListGlobalTables](#ListGlobalTables) 70 | 71 | + [ListTables](#ListTables) 72 | 73 | + [ListTagsOfResource](#ListTagsOfResource) 74 | 75 | + [PutItem](#PutItem) 76 | 77 | + [Query](#Query) 78 | 79 | + [RestoreTableFromBackup](#RestoreTableFromBackup) 80 | 81 | + [RestoreTableToPointInTime](#RestoreTableToPointInTime) 82 | 83 | + [Scan](#Scan) 84 | 85 | + [TagResource](#TagResource) 86 | 87 | + [TransactGetItems](#TransactGetItems) 88 | 89 | + [TransactWriteItems](#TransactWriteItems) 90 | 91 | + [UntagResource](#UntagResource) 92 | 93 | + [UpdateContinuousBackups](#UpdateContinuousBackups) 94 | 95 | + [UpdateGlobalTable](#UpdateGlobalTable) 96 | 97 | + [UpdateGlobalTableSettings](#UpdateGlobalTableSettings) 98 | 99 | + [UpdateItem](#UpdateItem) 100 | 101 | + [UpdateTable](#UpdateTable) 102 | 103 | + [UpdateTimeToLive](#UpdateTimeToLive) 104 | 105 | ### Basics 106 | 107 | ``` ts 108 | /** Generic document. */ 109 | export interface Doc { 110 | [key: string]: any; 111 | } 112 | 113 | /** Generic representation of a DynamoDB client. */ 114 | export interface DynamoDBClient { 115 | describeEndpoints: (options?: Doc) => Promise; 116 | describeLimits: (options?: Doc) => Promise; 117 | listTables: (options?: Doc) => Promise; 118 | scan: ( 119 | params: Doc, 120 | options?: Doc 121 | ) => Promise>; 122 | query: ( 123 | params: Doc, 124 | options?: Doc 125 | ) => Promise>; 126 | [key: string]: (params: Doc, options?: Doc) => Promise; 127 | } 128 | 129 | /** Credentials. */ 130 | export interface Credentials { 131 | accessKeyId: string; // AKIAIOSFODNN7EXAMPLE 132 | secretAccessKey: string; // wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 133 | sessionToken?: string; // somesessiontoken 134 | } 135 | 136 | /** Client configuration. */ 137 | export interface ClientConfig { 138 | credentials?: Credentials | (() => Credentials | Promise); 139 | region?: string; // us-west-2 140 | profile?: string; // default 141 | canonicalUri?: string; // fx /path/to/somewhere 142 | port?: number; // 80 143 | host?: string; // localhost 144 | } 145 | 146 | /** Op options. */ 147 | export interface OpOptions { 148 | wrapNumbers?: boolean, // wrap numbers to a special number value type? [false] 149 | convertEmptyValues?: boolean, // convert empty strings and binaries? [false] 150 | translateJSON?: boolean, // translate I/O JSON schemas? [true] 151 | iteratePages?: boolean // if a result is paged, async-iterate it? [true] 152 | } 153 | ``` 154 | 155 | ### Factory 156 | 157 | #### createClient 158 | 159 | ##### `createClient(conf: ClientConfig): DynamoDBClient` 160 | 161 | Creates a DynamoDB client. 162 | 163 | ### Ops 164 | 165 | The client supports all DynamoDB operations. Check the linked aws docs for info about parameters of a specific operation. 166 | 167 | #### BatchGetItem 168 | 169 | ##### `batchGetItem(params: Doc, options?: OpOptions): Promise` 170 | 171 | [aws BatchGetItem docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html) 172 | 173 | #### BatchWriteItem 174 | 175 | ##### `batchWriteItem(params: Doc, options?: OpOptions): Promise` 176 | 177 | [aws BatchWriteItem docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html) 178 | 179 | #### CreateBackup 180 | 181 | ##### `createBackup(params: Doc, options?: OpOptions): Promise` 182 | 183 | [aws CreateBackup docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateBackup.html) 184 | 185 | #### CreateGlobalTable 186 | 187 | ##### `createGlobalTable(params: Doc, options?: OpOptions): Promise` 188 | 189 | [aws CreateGlobalTable docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateGlobalTable.html) 190 | 191 | #### CreateTable 192 | 193 | ##### `createTable(params: Doc, options?: OpOptions): Promise` 194 | 195 | [aws CreateTable docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html) 196 | 197 | #### DeleteBackup 198 | 199 | ##### `deleteBackup(params: Doc, options?: OpOptions): Promise` 200 | 201 | [aws DeleteBackup docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteBackup.html) 202 | 203 | #### DeleteItem 204 | 205 | ##### `deleteItem(params: Doc, options?: OpOptions): Promise` 206 | 207 | [aws DeleteItem docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html) 208 | 209 | #### DeleteTable 210 | 211 | ##### `deleteTable(params: Doc, options?: OpOptions): Promise` 212 | 213 | [aws DeleteTable docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteTable.html) 214 | 215 | #### DescribeBackup 216 | 217 | ##### `describeBackup(params: Doc, options?: OpOptions): Promise` 218 | 219 | [aws DescribeBackup docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeBackup.html) 220 | 221 | #### DescribeContinuousBackups 222 | 223 | ##### `describeContinuousBackups(params: Doc, options?: OpOptions): Promise` 224 | 225 | [aws DescribeContinuousBackups docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeContinuousBackups.html) 226 | 227 | #### DescribeEndpoints 228 | 229 | ##### `describeEndpoints(options?: OpOptions): Promise` 230 | 231 | [aws DescribeEndpoints docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeEndpoints.html) 232 | 233 | #### DescribeGlobalTable 234 | 235 | ##### `describeGlobalTable(params: Doc, options?: OpOptions): Promise` 236 | 237 | [aws DescribeGlobalTable docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeGlobalTable.html) 238 | 239 | #### DescribeGlobalTableSettings 240 | 241 | ##### `describeGlobalTableSettings(params: Doc, options?: OpOptions): Promise` 242 | 243 | [aws DescribeGlobalTableSettings docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeGlobalTableSettings.html) 244 | 245 | #### DescribeLimits 246 | 247 | ##### `describeLimits(options?: OpOptions): Promise` 248 | 249 | [aws DescribeLimits docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeLimits.html) 250 | 251 | #### DescribeTable 252 | 253 | ##### `describeTable(params: Doc, options?: OpOptions): Promise` 254 | 255 | [aws DescribeTable docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeTable.html) 256 | 257 | #### DescribeTimeToLive 258 | 259 | ##### `describeTimeToLive(params: Doc, options?: OpOptions): Promise` 260 | 261 | [aws DescribeTimeToLive docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeTimeToLive.html) 262 | 263 | #### GetItem 264 | 265 | ##### `getItem(params: Doc, options?: OpOptions): Promise` 266 | 267 | [aws GetItem docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) 268 | 269 | #### ListBackups 270 | 271 | ##### `listBackups(params: Doc, options?: OpOptions): Promise` 272 | 273 | [aws ListBackups docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ListBackups.html) 274 | 275 | #### ListGlobalTables 276 | 277 | ##### `listGlobalTables(params: Doc, options?: OpOptions): Promise` 278 | 279 | [aws ListGlobalTables docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ListGlobalTables.html) 280 | 281 | #### ListTables 282 | 283 | ##### `listTables(options?: OpOptions): Promise` 284 | 285 | [aws ListTables docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ListTables.html) 286 | 287 | #### ListTagsOfResource 288 | 289 | ##### `listTagsOfResource(params: Doc, options?: OpOptions): Promise` 290 | 291 | [aws ListTagsOfResource docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ListTagsOfResource.html) 292 | 293 | #### PutItem 294 | 295 | ##### `putItem(params: Doc, options?: OpOptions): Promise` 296 | 297 | [aws PutItem docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) 298 | 299 | #### Query 300 | 301 | ##### `query(params: Doc, options?: OpOptions): Promise>` 302 | 303 | [aws Query docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) 304 | 305 | #### RestoreTableFromBackup 306 | 307 | ##### `restoreTableFromBackup(params: Doc, options?: OpOptions): Promise` 308 | 309 | [aws RestoreTableFromBackup docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_RestoreTableFromBackup.html) 310 | 311 | #### RestoreTableToPointInTime 312 | 313 | ##### `restoreTableToPointInTime(params: Doc, options?: OpOptions): Promise` 314 | 315 | [aws RestoreTableToPointInTime docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_RestoreTableToPointInTime.html) 316 | 317 | #### Scan 318 | 319 | ##### `scan(params: Doc, options?: OpOptions): Promise>` 320 | 321 | [aws Scan docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html) 322 | 323 | #### TagResource 324 | 325 | ##### `tagResource(params: Doc, options?: OpOptions): Promise` 326 | 327 | [aws TagResource docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TagResource.html) 328 | 329 | #### TransactGetItems 330 | 331 | ##### `transactGetItems(params: Doc, options?: OpOptions): Promise` 332 | 333 | [aws TransactGetItems docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactGetItems.html) 334 | 335 | #### TransactWriteItems 336 | 337 | ##### `transactWriteItems(params: Doc, options?: OpOptions): Promise` 338 | 339 | [aws TransactWriteItems docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) 340 | 341 | #### UntagResource 342 | 343 | ##### `untagResource(params: Doc, options?: OpOptions): Promise` 344 | 345 | [aws UntagResource docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UntagResource.html) 346 | 347 | #### UpdateContinuousBackups 348 | 349 | ##### `updateContinuousBackups(params: Doc, options?: OpOptions): Promise` 350 | 351 | [aws UpdateContinuousBackups docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateContinuousBackups.html) 352 | 353 | #### UpdateGlobalTable 354 | 355 | ##### `updateGlobalTable(params: Doc, options?: OpOptions): Promise` 356 | 357 | [aws UpdateGlobalTable docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateGlobalTable.html) 358 | 359 | #### UpdateGlobalTableSettings 360 | 361 | ##### `updateGlobalTableSettings(params: Doc, options?: OpOptions): Promise` 362 | 363 | [aws UpdateGlobalTableSettings docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateGlobalTableSettings.html) 364 | 365 | #### UpdateItem 366 | 367 | ##### `updateItem(params: Doc, options?: OpOptions): Promise` 368 | 369 | [aws UpdateItem docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) 370 | 371 | #### UpdateTable 372 | 373 | ##### `updateTable(params: Doc, options?: OpOptions): Promise` 374 | 375 | [aws UpdateTable docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTable.html) 376 | 377 | #### UpdateTimeToLive 378 | 379 | ##### `updateTimeToLive(params: Doc, options?: OpOptions): Promise` 380 | 381 | [aws UpdateTimeToLive docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTimeToLive.html) 382 | 383 | ## FYI 384 | 385 | Don't want to do all development against the real AWS cloud? 386 | 387 | + [DynamoDB Local](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html) 388 | 389 | + [DynamoDB GUI](https://github.com/Arattian/DynamoDb-GUI-Client) 390 | 391 | ## License 392 | 393 | [MIT](./LICENSE) 394 | -------------------------------------------------------------------------------- /api/api.ts: -------------------------------------------------------------------------------- 1 | import { Collection } from "./collection.ts"; 2 | import { Operation } from "./operation.ts"; 3 | import { Shape } from "./shape.ts"; 4 | import { 5 | Doc, 6 | memoizedProperty, 7 | property, /*, string as stringUtil*/ 8 | } from "../util.ts"; 9 | 10 | // NOTE: 2 run in ts strict-mode (bypassing TS7009) 11 | const _Collection: any = Collection; 12 | const _Operation: any = Operation; 13 | 14 | // var Paginator = require('./paginator'); 15 | // var ResourceWaiter = require('./resource_waiter'); 16 | 17 | export function Api(this: any, api: Doc = {}, options: Doc = {}) { 18 | const self: any = this; 19 | // api = api || {}; 20 | // options = options || {}; 21 | options.api = this; 22 | 23 | api.metadata = api.metadata || {}; 24 | 25 | property(this, "isApi", true, false); 26 | property(this, "apiVersion", api.metadata.apiVersion); 27 | property(this, "endpointPrefix", api.metadata.endpointPrefix); 28 | property(this, "signingName", api.metadata.signingName); 29 | property(this, "globalEndpoint", api.metadata.globalEndpoint); 30 | property(this, "signatureVersion", api.metadata.signatureVersion); 31 | property(this, "jsonVersion", api.metadata.jsonVersion); 32 | property(this, "targetPrefix", api.metadata.targetPrefix); 33 | property(this, "protocol", api.metadata.protocol); 34 | property(this, "timestampFormat", api.metadata.timestampFormat); 35 | property(this, "xmlNamespaceUri", api.metadata.xmlNamespace); 36 | property(this, "abbreviation", api.metadata.serviceAbbreviation); 37 | property(this, "fullName", api.metadata.serviceFullName); 38 | property(this, "serviceId", api.metadata.serviceId); 39 | 40 | memoizedProperty(this, "className", function (): string { 41 | let name: string = api.metadata.serviceAbbreviation || 42 | api.metadata.serviceFullName; 43 | 44 | if (!name) { 45 | return ""; 46 | } 47 | 48 | name = name.replace(/^Amazon|AWS\s*|\(.*|\s+|\W+/g, ""); 49 | 50 | if (name === "ElasticLoadBalancing") { 51 | name = "ELB"; 52 | } 53 | 54 | return name; 55 | }); 56 | 57 | function addEndpointOperation(name: string, operation: Doc): void { 58 | if (operation.endpointoperation) { 59 | property(self, "endpointOperation", /*stringUtil.lowerFirst(name)*/ name); 60 | } 61 | } 62 | 63 | property( 64 | this, 65 | "operations", 66 | new _Collection( 67 | api.operations, 68 | options, 69 | function (name: string, operation: Doc): any { 70 | return new _Operation(name, operation, options); 71 | }, /*, stringUtil.lowerFirst*/ 72 | addEndpointOperation, 73 | ), 74 | ); 75 | 76 | property( 77 | this, 78 | "shapes", 79 | new _Collection(api.shapes, options, function ( 80 | name: string, 81 | shape: Doc, 82 | ): any { 83 | return Shape.create(shape, options); 84 | }), 85 | ); 86 | 87 | // property(this, 'paginators', new Collection(api.paginators, options, function(name, paginator) { 88 | // return new Paginator(name, paginator, options); 89 | // })); 90 | // 91 | // property(this, 'waiters', new Collection(api.waiters, options, function(name, waiter) { 92 | // return new ResourceWaiter(name, waiter, options); 93 | // }, util.string.lowerFirst)); 94 | 95 | if (options.documentation) { 96 | property(this, "documentation", api.documentation); 97 | property(this, "documentationUrl", api.documentationUrl); 98 | } 99 | } 100 | 101 | // /** 102 | // * @api private 103 | // */ 104 | // module.exports = Api; 105 | -------------------------------------------------------------------------------- /api/collection.ts: -------------------------------------------------------------------------------- 1 | // var memoizedProperty = require('../util').memoizedProperty; 2 | // import { Doc} from "../types.ts" 3 | import { Doc, memoizedProperty, noop } from "../util.ts"; 4 | 5 | function memoize( 6 | this: any, 7 | name: string, 8 | value: any, 9 | factory: ( 10 | name: string, 11 | value: any, 12 | ) => any, /*, nameTr: (name:string)=> string*/ 13 | ): void { 14 | memoizedProperty(this, name, /*nameTr(name)*/ function (): any { 15 | return factory(name, value); 16 | }); 17 | } 18 | 19 | export function Collection( 20 | this: any, 21 | iterable: any, 22 | options: Doc, 23 | factory: ( 24 | name: string, 25 | value: any, 26 | ) => any, /*, nameTr: (name:string)=> string = String*/ 27 | callback: (name: string, value: any) => any = noop, 28 | ) { 29 | // nameTr = nameTr || String; 30 | // var self = this; 31 | 32 | for (const id in iterable) { 33 | if (Object.prototype.hasOwnProperty.call(iterable, id)) { 34 | memoize.call(this, id, iterable[id], factory /*, nameTr*/); 35 | 36 | if (callback) { 37 | callback(id, iterable[id]); 38 | } 39 | } 40 | } 41 | } 42 | 43 | // export class Collection { 44 | // 45 | // constructor(iterable: any, options: Doc, factory:(name: string, value: any)=> any/*, nameTr: (name:string)=> string = String*/, callback: (name: string, value: any) => any=noop) { 46 | // // nameTr = nameTr || String; 47 | // // var self = this; 48 | // 49 | // for (const id in iterable) { 50 | // if (Object.prototype.hasOwnProperty.call(iterable, id)) { 51 | // memoize.call(this, id, iterable[id], factory/*, nameTr*/); 52 | // 53 | // if (callback) {callback(id, iterable[id]);} 54 | // } 55 | // } 56 | // } 57 | // } 58 | // // /** 59 | // * @api private 60 | // */ 61 | // module.exports = Collection; 62 | -------------------------------------------------------------------------------- /api/mod.ts: -------------------------------------------------------------------------------- 1 | import { Api } from "./api.ts"; 2 | 3 | const _Api: any = Api; 4 | 5 | export const API: any = new _Api(JSON.parse(` 6 | { 7 | "version": "2.0", 8 | "metadata": { 9 | "apiVersion": "2012-08-10", 10 | "endpointPrefix": "dynamodb", 11 | "jsonVersion": "1.0", 12 | "protocol": "json", 13 | "serviceAbbreviation": "DynamoDB", 14 | "serviceFullName": "Amazon DynamoDB", 15 | "serviceId": "DynamoDB", 16 | "signatureVersion": "v4", 17 | "targetPrefix": "DynamoDB_20120810", 18 | "uid": "dynamodb-2012-08-10" 19 | }, 20 | "operations": { 21 | "BatchGetItem": { 22 | "input": { 23 | "type": "structure", 24 | "required": [ 25 | "RequestItems" 26 | ], 27 | "members": { 28 | "RequestItems": { 29 | "shape": "S2" 30 | }, 31 | "ReturnConsumedCapacity": {} 32 | } 33 | }, 34 | "output": { 35 | "type": "structure", 36 | "members": { 37 | "Responses": { 38 | "type": "map", 39 | "key": {}, 40 | "value": { 41 | "shape": "Sr" 42 | } 43 | }, 44 | "UnprocessedKeys": { 45 | "shape": "S2" 46 | }, 47 | "ConsumedCapacity": { 48 | "shape": "St" 49 | } 50 | } 51 | }, 52 | "endpointdiscovery": {} 53 | }, 54 | "BatchWriteItem": { 55 | "input": { 56 | "type": "structure", 57 | "required": [ 58 | "RequestItems" 59 | ], 60 | "members": { 61 | "RequestItems": { 62 | "shape": "S10" 63 | }, 64 | "ReturnConsumedCapacity": {}, 65 | "ReturnItemCollectionMetrics": {} 66 | } 67 | }, 68 | "output": { 69 | "type": "structure", 70 | "members": { 71 | "UnprocessedItems": { 72 | "shape": "S10" 73 | }, 74 | "ItemCollectionMetrics": { 75 | "shape": "S18" 76 | }, 77 | "ConsumedCapacity": { 78 | "shape": "St" 79 | } 80 | } 81 | }, 82 | "endpointdiscovery": {} 83 | }, 84 | "CreateBackup": { 85 | "input": { 86 | "type": "structure", 87 | "required": [ 88 | "TableName", 89 | "BackupName" 90 | ], 91 | "members": { 92 | "TableName": {}, 93 | "BackupName": {} 94 | } 95 | }, 96 | "output": { 97 | "type": "structure", 98 | "members": { 99 | "BackupDetails": { 100 | "shape": "S1h" 101 | } 102 | } 103 | }, 104 | "endpointdiscovery": {} 105 | }, 106 | "CreateGlobalTable": { 107 | "input": { 108 | "type": "structure", 109 | "required": [ 110 | "GlobalTableName", 111 | "ReplicationGroup" 112 | ], 113 | "members": { 114 | "GlobalTableName": {}, 115 | "ReplicationGroup": { 116 | "shape": "S1p" 117 | } 118 | } 119 | }, 120 | "output": { 121 | "type": "structure", 122 | "members": { 123 | "GlobalTableDescription": { 124 | "shape": "S1t" 125 | } 126 | } 127 | }, 128 | "endpointdiscovery": {} 129 | }, 130 | "CreateTable": { 131 | "input": { 132 | "type": "structure", 133 | "required": [ 134 | "AttributeDefinitions", 135 | "TableName", 136 | "KeySchema" 137 | ], 138 | "members": { 139 | "AttributeDefinitions": { 140 | "shape": "S1z" 141 | }, 142 | "TableName": {}, 143 | "KeySchema": { 144 | "shape": "S23" 145 | }, 146 | "LocalSecondaryIndexes": { 147 | "type": "list", 148 | "member": { 149 | "type": "structure", 150 | "required": [ 151 | "IndexName", 152 | "KeySchema", 153 | "Projection" 154 | ], 155 | "members": { 156 | "IndexName": {}, 157 | "KeySchema": { 158 | "shape": "S23" 159 | }, 160 | "Projection": { 161 | "shape": "S28" 162 | } 163 | } 164 | } 165 | }, 166 | "GlobalSecondaryIndexes": { 167 | "type": "list", 168 | "member": { 169 | "type": "structure", 170 | "required": [ 171 | "IndexName", 172 | "KeySchema", 173 | "Projection" 174 | ], 175 | "members": { 176 | "IndexName": {}, 177 | "KeySchema": { 178 | "shape": "S23" 179 | }, 180 | "Projection": { 181 | "shape": "S28" 182 | }, 183 | "ProvisionedThroughput": { 184 | "shape": "S2e" 185 | } 186 | } 187 | } 188 | }, 189 | "BillingMode": {}, 190 | "ProvisionedThroughput": { 191 | "shape": "S2e" 192 | }, 193 | "StreamSpecification": { 194 | "shape": "S2h" 195 | }, 196 | "SSESpecification": { 197 | "shape": "S2k" 198 | }, 199 | "Tags": { 200 | "shape": "S2o" 201 | } 202 | } 203 | }, 204 | "output": { 205 | "type": "structure", 206 | "members": { 207 | "TableDescription": { 208 | "shape": "S2t" 209 | } 210 | } 211 | }, 212 | "endpointdiscovery": {} 213 | }, 214 | "DeleteBackup": { 215 | "input": { 216 | "type": "structure", 217 | "required": [ 218 | "BackupArn" 219 | ], 220 | "members": { 221 | "BackupArn": {} 222 | } 223 | }, 224 | "output": { 225 | "type": "structure", 226 | "members": { 227 | "BackupDescription": { 228 | "shape": "S3g" 229 | } 230 | } 231 | }, 232 | "endpointdiscovery": {} 233 | }, 234 | "DeleteItem": { 235 | "input": { 236 | "type": "structure", 237 | "required": [ 238 | "TableName", 239 | "Key" 240 | ], 241 | "members": { 242 | "TableName": {}, 243 | "Key": { 244 | "shape": "S6" 245 | }, 246 | "Expected": { 247 | "shape": "S3t" 248 | }, 249 | "ConditionalOperator": {}, 250 | "ReturnValues": {}, 251 | "ReturnConsumedCapacity": {}, 252 | "ReturnItemCollectionMetrics": {}, 253 | "ConditionExpression": {}, 254 | "ExpressionAttributeNames": { 255 | "shape": "Sm" 256 | }, 257 | "ExpressionAttributeValues": { 258 | "shape": "S41" 259 | } 260 | } 261 | }, 262 | "output": { 263 | "type": "structure", 264 | "members": { 265 | "Attributes": { 266 | "shape": "Ss" 267 | }, 268 | "ConsumedCapacity": { 269 | "shape": "Su" 270 | }, 271 | "ItemCollectionMetrics": { 272 | "shape": "S1a" 273 | } 274 | } 275 | }, 276 | "endpointdiscovery": {} 277 | }, 278 | "DeleteTable": { 279 | "input": { 280 | "type": "structure", 281 | "required": [ 282 | "TableName" 283 | ], 284 | "members": { 285 | "TableName": {} 286 | } 287 | }, 288 | "output": { 289 | "type": "structure", 290 | "members": { 291 | "TableDescription": { 292 | "shape": "S2t" 293 | } 294 | } 295 | }, 296 | "endpointdiscovery": {} 297 | }, 298 | "DescribeBackup": { 299 | "input": { 300 | "type": "structure", 301 | "required": [ 302 | "BackupArn" 303 | ], 304 | "members": { 305 | "BackupArn": {} 306 | } 307 | }, 308 | "output": { 309 | "type": "structure", 310 | "members": { 311 | "BackupDescription": { 312 | "shape": "S3g" 313 | } 314 | } 315 | }, 316 | "endpointdiscovery": {} 317 | }, 318 | "DescribeContinuousBackups": { 319 | "input": { 320 | "type": "structure", 321 | "required": [ 322 | "TableName" 323 | ], 324 | "members": { 325 | "TableName": {} 326 | } 327 | }, 328 | "output": { 329 | "type": "structure", 330 | "members": { 331 | "ContinuousBackupsDescription": { 332 | "shape": "S4a" 333 | } 334 | } 335 | }, 336 | "endpointdiscovery": {} 337 | }, 338 | "DescribeEndpoints": { 339 | "input": { 340 | "type": "structure", 341 | "members": {} 342 | }, 343 | "output": { 344 | "type": "structure", 345 | "required": [ 346 | "Endpoints" 347 | ], 348 | "members": { 349 | "Endpoints": { 350 | "type": "list", 351 | "member": { 352 | "type": "structure", 353 | "required": [ 354 | "Address", 355 | "CachePeriodInMinutes" 356 | ], 357 | "members": { 358 | "Address": {}, 359 | "CachePeriodInMinutes": { 360 | "type": "long" 361 | } 362 | } 363 | } 364 | } 365 | } 366 | }, 367 | "endpointoperation": true 368 | }, 369 | "DescribeGlobalTable": { 370 | "input": { 371 | "type": "structure", 372 | "required": [ 373 | "GlobalTableName" 374 | ], 375 | "members": { 376 | "GlobalTableName": {} 377 | } 378 | }, 379 | "output": { 380 | "type": "structure", 381 | "members": { 382 | "GlobalTableDescription": { 383 | "shape": "S1t" 384 | } 385 | } 386 | }, 387 | "endpointdiscovery": {} 388 | }, 389 | "DescribeGlobalTableSettings": { 390 | "input": { 391 | "type": "structure", 392 | "required": [ 393 | "GlobalTableName" 394 | ], 395 | "members": { 396 | "GlobalTableName": {} 397 | } 398 | }, 399 | "output": { 400 | "type": "structure", 401 | "members": { 402 | "GlobalTableName": {}, 403 | "ReplicaSettings": { 404 | "shape": "S4m" 405 | } 406 | } 407 | }, 408 | "endpointdiscovery": {} 409 | }, 410 | "DescribeLimits": { 411 | "input": { 412 | "type": "structure", 413 | "members": {} 414 | }, 415 | "output": { 416 | "type": "structure", 417 | "members": { 418 | "AccountMaxReadCapacityUnits": { 419 | "type": "long" 420 | }, 421 | "AccountMaxWriteCapacityUnits": { 422 | "type": "long" 423 | }, 424 | "TableMaxReadCapacityUnits": { 425 | "type": "long" 426 | }, 427 | "TableMaxWriteCapacityUnits": { 428 | "type": "long" 429 | } 430 | } 431 | }, 432 | "endpointdiscovery": {} 433 | }, 434 | "DescribeTable": { 435 | "input": { 436 | "type": "structure", 437 | "required": [ 438 | "TableName" 439 | ], 440 | "members": { 441 | "TableName": {} 442 | } 443 | }, 444 | "output": { 445 | "type": "structure", 446 | "members": { 447 | "Table": { 448 | "shape": "S2t" 449 | } 450 | } 451 | }, 452 | "endpointdiscovery": {} 453 | }, 454 | "DescribeTimeToLive": { 455 | "input": { 456 | "type": "structure", 457 | "required": [ 458 | "TableName" 459 | ], 460 | "members": { 461 | "TableName": {} 462 | } 463 | }, 464 | "output": { 465 | "type": "structure", 466 | "members": { 467 | "TimeToLiveDescription": { 468 | "shape": "S3p" 469 | } 470 | } 471 | }, 472 | "endpointdiscovery": {} 473 | }, 474 | "GetItem": { 475 | "input": { 476 | "type": "structure", 477 | "required": [ 478 | "TableName", 479 | "Key" 480 | ], 481 | "members": { 482 | "TableName": {}, 483 | "Key": { 484 | "shape": "S6" 485 | }, 486 | "AttributesToGet": { 487 | "shape": "Sj" 488 | }, 489 | "ConsistentRead": { 490 | "type": "boolean" 491 | }, 492 | "ReturnConsumedCapacity": {}, 493 | "ProjectionExpression": {}, 494 | "ExpressionAttributeNames": { 495 | "shape": "Sm" 496 | } 497 | } 498 | }, 499 | "output": { 500 | "type": "structure", 501 | "members": { 502 | "Item": { 503 | "shape": "Ss" 504 | }, 505 | "ConsumedCapacity": { 506 | "shape": "Su" 507 | } 508 | } 509 | }, 510 | "endpointdiscovery": {} 511 | }, 512 | "ListBackups": { 513 | "input": { 514 | "type": "structure", 515 | "members": { 516 | "TableName": {}, 517 | "Limit": { 518 | "type": "integer" 519 | }, 520 | "TimeRangeLowerBound": { 521 | "type": "timestamp" 522 | }, 523 | "TimeRangeUpperBound": { 524 | "type": "timestamp" 525 | }, 526 | "ExclusiveStartBackupArn": {}, 527 | "BackupType": {} 528 | } 529 | }, 530 | "output": { 531 | "type": "structure", 532 | "members": { 533 | "BackupSummaries": { 534 | "type": "list", 535 | "member": { 536 | "type": "structure", 537 | "members": { 538 | "TableName": {}, 539 | "TableId": {}, 540 | "TableArn": {}, 541 | "BackupArn": {}, 542 | "BackupName": {}, 543 | "BackupCreationDateTime": { 544 | "type": "timestamp" 545 | }, 546 | "BackupExpiryDateTime": { 547 | "type": "timestamp" 548 | }, 549 | "BackupStatus": {}, 550 | "BackupType": {}, 551 | "BackupSizeBytes": { 552 | "type": "long" 553 | } 554 | } 555 | } 556 | }, 557 | "LastEvaluatedBackupArn": {} 558 | } 559 | }, 560 | "endpointdiscovery": {} 561 | }, 562 | "ListGlobalTables": { 563 | "input": { 564 | "type": "structure", 565 | "members": { 566 | "ExclusiveStartGlobalTableName": {}, 567 | "Limit": { 568 | "type": "integer" 569 | }, 570 | "RegionName": {} 571 | } 572 | }, 573 | "output": { 574 | "type": "structure", 575 | "members": { 576 | "GlobalTables": { 577 | "type": "list", 578 | "member": { 579 | "type": "structure", 580 | "members": { 581 | "GlobalTableName": {}, 582 | "ReplicationGroup": { 583 | "shape": "S1p" 584 | } 585 | } 586 | } 587 | }, 588 | "LastEvaluatedGlobalTableName": {} 589 | } 590 | }, 591 | "endpointdiscovery": {} 592 | }, 593 | "ListTables": { 594 | "input": { 595 | "type": "structure", 596 | "members": { 597 | "ExclusiveStartTableName": {}, 598 | "Limit": { 599 | "type": "integer" 600 | } 601 | } 602 | }, 603 | "output": { 604 | "type": "structure", 605 | "members": { 606 | "TableNames": { 607 | "type": "list", 608 | "member": {} 609 | }, 610 | "LastEvaluatedTableName": {} 611 | } 612 | }, 613 | "endpointdiscovery": {} 614 | }, 615 | "ListTagsOfResource": { 616 | "input": { 617 | "type": "structure", 618 | "required": [ 619 | "ResourceArn" 620 | ], 621 | "members": { 622 | "ResourceArn": {}, 623 | "NextToken": {} 624 | } 625 | }, 626 | "output": { 627 | "type": "structure", 628 | "members": { 629 | "Tags": { 630 | "shape": "S2o" 631 | }, 632 | "NextToken": {} 633 | } 634 | }, 635 | "endpointdiscovery": {} 636 | }, 637 | "PutItem": { 638 | "input": { 639 | "type": "structure", 640 | "required": [ 641 | "TableName", 642 | "Item" 643 | ], 644 | "members": { 645 | "TableName": {}, 646 | "Item": { 647 | "shape": "S14" 648 | }, 649 | "Expected": { 650 | "shape": "S3t" 651 | }, 652 | "ReturnValues": {}, 653 | "ReturnConsumedCapacity": {}, 654 | "ReturnItemCollectionMetrics": {}, 655 | "ConditionalOperator": {}, 656 | "ConditionExpression": {}, 657 | "ExpressionAttributeNames": { 658 | "shape": "Sm" 659 | }, 660 | "ExpressionAttributeValues": { 661 | "shape": "S41" 662 | } 663 | } 664 | }, 665 | "output": { 666 | "type": "structure", 667 | "members": { 668 | "Attributes": { 669 | "shape": "Ss" 670 | }, 671 | "ConsumedCapacity": { 672 | "shape": "Su" 673 | }, 674 | "ItemCollectionMetrics": { 675 | "shape": "S1a" 676 | } 677 | } 678 | }, 679 | "endpointdiscovery": {} 680 | }, 681 | "Query": { 682 | "input": { 683 | "type": "structure", 684 | "required": [ 685 | "TableName" 686 | ], 687 | "members": { 688 | "TableName": {}, 689 | "IndexName": {}, 690 | "Select": {}, 691 | "AttributesToGet": { 692 | "shape": "Sj" 693 | }, 694 | "Limit": { 695 | "type": "integer" 696 | }, 697 | "ConsistentRead": { 698 | "type": "boolean" 699 | }, 700 | "KeyConditions": { 701 | "type": "map", 702 | "key": {}, 703 | "value": { 704 | "shape": "S5w" 705 | } 706 | }, 707 | "QueryFilter": { 708 | "shape": "S5x" 709 | }, 710 | "ConditionalOperator": {}, 711 | "ScanIndexForward": { 712 | "type": "boolean" 713 | }, 714 | "ExclusiveStartKey": { 715 | "shape": "S6" 716 | }, 717 | "ReturnConsumedCapacity": {}, 718 | "ProjectionExpression": {}, 719 | "FilterExpression": {}, 720 | "KeyConditionExpression": {}, 721 | "ExpressionAttributeNames": { 722 | "shape": "Sm" 723 | }, 724 | "ExpressionAttributeValues": { 725 | "shape": "S41" 726 | } 727 | } 728 | }, 729 | "output": { 730 | "type": "structure", 731 | "members": { 732 | "Items": { 733 | "shape": "Sr" 734 | }, 735 | "Count": { 736 | "type": "integer" 737 | }, 738 | "ScannedCount": { 739 | "type": "integer" 740 | }, 741 | "LastEvaluatedKey": { 742 | "shape": "S6" 743 | }, 744 | "ConsumedCapacity": { 745 | "shape": "Su" 746 | } 747 | } 748 | }, 749 | "endpointdiscovery": {} 750 | }, 751 | "RestoreTableFromBackup": { 752 | "input": { 753 | "type": "structure", 754 | "required": [ 755 | "TargetTableName", 756 | "BackupArn" 757 | ], 758 | "members": { 759 | "TargetTableName": {}, 760 | "BackupArn": {} 761 | } 762 | }, 763 | "output": { 764 | "type": "structure", 765 | "members": { 766 | "TableDescription": { 767 | "shape": "S2t" 768 | } 769 | } 770 | }, 771 | "endpointdiscovery": {} 772 | }, 773 | "RestoreTableToPointInTime": { 774 | "input": { 775 | "type": "structure", 776 | "required": [ 777 | "SourceTableName", 778 | "TargetTableName" 779 | ], 780 | "members": { 781 | "SourceTableName": {}, 782 | "TargetTableName": {}, 783 | "UseLatestRestorableTime": { 784 | "type": "boolean" 785 | }, 786 | "RestoreDateTime": { 787 | "type": "timestamp" 788 | } 789 | } 790 | }, 791 | "output": { 792 | "type": "structure", 793 | "members": { 794 | "TableDescription": { 795 | "shape": "S2t" 796 | } 797 | } 798 | }, 799 | "endpointdiscovery": {} 800 | }, 801 | "Scan": { 802 | "input": { 803 | "type": "structure", 804 | "required": [ 805 | "TableName" 806 | ], 807 | "members": { 808 | "TableName": {}, 809 | "IndexName": {}, 810 | "AttributesToGet": { 811 | "shape": "Sj" 812 | }, 813 | "Limit": { 814 | "type": "integer" 815 | }, 816 | "Select": {}, 817 | "ScanFilter": { 818 | "shape": "S5x" 819 | }, 820 | "ConditionalOperator": {}, 821 | "ExclusiveStartKey": { 822 | "shape": "S6" 823 | }, 824 | "ReturnConsumedCapacity": {}, 825 | "TotalSegments": { 826 | "type": "integer" 827 | }, 828 | "Segment": { 829 | "type": "integer" 830 | }, 831 | "ProjectionExpression": {}, 832 | "FilterExpression": {}, 833 | "ExpressionAttributeNames": { 834 | "shape": "Sm" 835 | }, 836 | "ExpressionAttributeValues": { 837 | "shape": "S41" 838 | }, 839 | "ConsistentRead": { 840 | "type": "boolean" 841 | } 842 | } 843 | }, 844 | "output": { 845 | "type": "structure", 846 | "members": { 847 | "Items": { 848 | "shape": "Sr" 849 | }, 850 | "Count": { 851 | "type": "integer" 852 | }, 853 | "ScannedCount": { 854 | "type": "integer" 855 | }, 856 | "LastEvaluatedKey": { 857 | "shape": "S6" 858 | }, 859 | "ConsumedCapacity": { 860 | "shape": "Su" 861 | } 862 | } 863 | }, 864 | "endpointdiscovery": {} 865 | }, 866 | "TagResource": { 867 | "input": { 868 | "type": "structure", 869 | "required": [ 870 | "ResourceArn", 871 | "Tags" 872 | ], 873 | "members": { 874 | "ResourceArn": {}, 875 | "Tags": { 876 | "shape": "S2o" 877 | } 878 | } 879 | }, 880 | "endpointdiscovery": {} 881 | }, 882 | "TransactGetItems": { 883 | "input": { 884 | "type": "structure", 885 | "required": [ 886 | "TransactItems" 887 | ], 888 | "members": { 889 | "TransactItems": { 890 | "type": "list", 891 | "member": { 892 | "type": "structure", 893 | "required": [ 894 | "Get" 895 | ], 896 | "members": { 897 | "Get": { 898 | "type": "structure", 899 | "required": [ 900 | "Key", 901 | "TableName" 902 | ], 903 | "members": { 904 | "Key": { 905 | "shape": "S6" 906 | }, 907 | "TableName": {}, 908 | "ProjectionExpression": {}, 909 | "ExpressionAttributeNames": { 910 | "shape": "Sm" 911 | } 912 | } 913 | } 914 | } 915 | } 916 | }, 917 | "ReturnConsumedCapacity": {} 918 | } 919 | }, 920 | "output": { 921 | "type": "structure", 922 | "members": { 923 | "ConsumedCapacity": { 924 | "shape": "St" 925 | }, 926 | "Responses": { 927 | "type": "list", 928 | "member": { 929 | "type": "structure", 930 | "members": { 931 | "Item": { 932 | "shape": "Ss" 933 | } 934 | } 935 | } 936 | } 937 | } 938 | }, 939 | "endpointdiscovery": {} 940 | }, 941 | "TransactWriteItems": { 942 | "input": { 943 | "type": "structure", 944 | "required": [ 945 | "TransactItems" 946 | ], 947 | "members": { 948 | "TransactItems": { 949 | "type": "list", 950 | "member": { 951 | "type": "structure", 952 | "members": { 953 | "ConditionCheck": { 954 | "type": "structure", 955 | "required": [ 956 | "Key", 957 | "TableName", 958 | "ConditionExpression" 959 | ], 960 | "members": { 961 | "Key": { 962 | "shape": "S6" 963 | }, 964 | "TableName": {}, 965 | "ConditionExpression": {}, 966 | "ExpressionAttributeNames": { 967 | "shape": "Sm" 968 | }, 969 | "ExpressionAttributeValues": { 970 | "shape": "S41" 971 | }, 972 | "ReturnValuesOnConditionCheckFailure": {} 973 | } 974 | }, 975 | "Put": { 976 | "type": "structure", 977 | "required": [ 978 | "Item", 979 | "TableName" 980 | ], 981 | "members": { 982 | "Item": { 983 | "shape": "S14" 984 | }, 985 | "TableName": {}, 986 | "ConditionExpression": {}, 987 | "ExpressionAttributeNames": { 988 | "shape": "Sm" 989 | }, 990 | "ExpressionAttributeValues": { 991 | "shape": "S41" 992 | }, 993 | "ReturnValuesOnConditionCheckFailure": {} 994 | } 995 | }, 996 | "Delete": { 997 | "type": "structure", 998 | "required": [ 999 | "Key", 1000 | "TableName" 1001 | ], 1002 | "members": { 1003 | "Key": { 1004 | "shape": "S6" 1005 | }, 1006 | "TableName": {}, 1007 | "ConditionExpression": {}, 1008 | "ExpressionAttributeNames": { 1009 | "shape": "Sm" 1010 | }, 1011 | "ExpressionAttributeValues": { 1012 | "shape": "S41" 1013 | }, 1014 | "ReturnValuesOnConditionCheckFailure": {} 1015 | } 1016 | }, 1017 | "Update": { 1018 | "type": "structure", 1019 | "required": [ 1020 | "Key", 1021 | "UpdateExpression", 1022 | "TableName" 1023 | ], 1024 | "members": { 1025 | "Key": { 1026 | "shape": "S6" 1027 | }, 1028 | "UpdateExpression": {}, 1029 | "TableName": {}, 1030 | "ConditionExpression": {}, 1031 | "ExpressionAttributeNames": { 1032 | "shape": "Sm" 1033 | }, 1034 | "ExpressionAttributeValues": { 1035 | "shape": "S41" 1036 | }, 1037 | "ReturnValuesOnConditionCheckFailure": {} 1038 | } 1039 | } 1040 | } 1041 | } 1042 | }, 1043 | "ReturnConsumedCapacity": {}, 1044 | "ReturnItemCollectionMetrics": {}, 1045 | "ClientRequestToken": { 1046 | "idempotencyToken": true 1047 | } 1048 | } 1049 | }, 1050 | "output": { 1051 | "type": "structure", 1052 | "members": { 1053 | "ConsumedCapacity": { 1054 | "shape": "St" 1055 | }, 1056 | "ItemCollectionMetrics": { 1057 | "shape": "S18" 1058 | } 1059 | } 1060 | }, 1061 | "endpointdiscovery": {} 1062 | }, 1063 | "UntagResource": { 1064 | "input": { 1065 | "type": "structure", 1066 | "required": [ 1067 | "ResourceArn", 1068 | "TagKeys" 1069 | ], 1070 | "members": { 1071 | "ResourceArn": {}, 1072 | "TagKeys": { 1073 | "type": "list", 1074 | "member": {} 1075 | } 1076 | } 1077 | }, 1078 | "endpointdiscovery": {} 1079 | }, 1080 | "UpdateContinuousBackups": { 1081 | "input": { 1082 | "type": "structure", 1083 | "required": [ 1084 | "TableName", 1085 | "PointInTimeRecoverySpecification" 1086 | ], 1087 | "members": { 1088 | "TableName": {}, 1089 | "PointInTimeRecoverySpecification": { 1090 | "type": "structure", 1091 | "required": [ 1092 | "PointInTimeRecoveryEnabled" 1093 | ], 1094 | "members": { 1095 | "PointInTimeRecoveryEnabled": { 1096 | "type": "boolean" 1097 | } 1098 | } 1099 | } 1100 | } 1101 | }, 1102 | "output": { 1103 | "type": "structure", 1104 | "members": { 1105 | "ContinuousBackupsDescription": { 1106 | "shape": "S4a" 1107 | } 1108 | } 1109 | }, 1110 | "endpointdiscovery": {} 1111 | }, 1112 | "UpdateGlobalTable": { 1113 | "input": { 1114 | "type": "structure", 1115 | "required": [ 1116 | "GlobalTableName", 1117 | "ReplicaUpdates" 1118 | ], 1119 | "members": { 1120 | "GlobalTableName": {}, 1121 | "ReplicaUpdates": { 1122 | "type": "list", 1123 | "member": { 1124 | "type": "structure", 1125 | "members": { 1126 | "Create": { 1127 | "type": "structure", 1128 | "required": [ 1129 | "RegionName" 1130 | ], 1131 | "members": { 1132 | "RegionName": {} 1133 | } 1134 | }, 1135 | "Delete": { 1136 | "type": "structure", 1137 | "required": [ 1138 | "RegionName" 1139 | ], 1140 | "members": { 1141 | "RegionName": {} 1142 | } 1143 | } 1144 | } 1145 | } 1146 | } 1147 | } 1148 | }, 1149 | "output": { 1150 | "type": "structure", 1151 | "members": { 1152 | "GlobalTableDescription": { 1153 | "shape": "S1t" 1154 | } 1155 | } 1156 | }, 1157 | "endpointdiscovery": {} 1158 | }, 1159 | "UpdateGlobalTableSettings": { 1160 | "input": { 1161 | "type": "structure", 1162 | "required": [ 1163 | "GlobalTableName" 1164 | ], 1165 | "members": { 1166 | "GlobalTableName": {}, 1167 | "GlobalTableBillingMode": {}, 1168 | "GlobalTableProvisionedWriteCapacityUnits": { 1169 | "type": "long" 1170 | }, 1171 | "GlobalTableProvisionedWriteCapacityAutoScalingSettingsUpdate": { 1172 | "shape": "S74" 1173 | }, 1174 | "GlobalTableGlobalSecondaryIndexSettingsUpdate": { 1175 | "type": "list", 1176 | "member": { 1177 | "type": "structure", 1178 | "required": [ 1179 | "IndexName" 1180 | ], 1181 | "members": { 1182 | "IndexName": {}, 1183 | "ProvisionedWriteCapacityUnits": { 1184 | "type": "long" 1185 | }, 1186 | "ProvisionedWriteCapacityAutoScalingSettingsUpdate": { 1187 | "shape": "S74" 1188 | } 1189 | } 1190 | } 1191 | }, 1192 | "ReplicaSettingsUpdate": { 1193 | "type": "list", 1194 | "member": { 1195 | "type": "structure", 1196 | "required": [ 1197 | "RegionName" 1198 | ], 1199 | "members": { 1200 | "RegionName": {}, 1201 | "ReplicaProvisionedReadCapacityUnits": { 1202 | "type": "long" 1203 | }, 1204 | "ReplicaProvisionedReadCapacityAutoScalingSettingsUpdate": { 1205 | "shape": "S74" 1206 | }, 1207 | "ReplicaGlobalSecondaryIndexSettingsUpdate": { 1208 | "type": "list", 1209 | "member": { 1210 | "type": "structure", 1211 | "required": [ 1212 | "IndexName" 1213 | ], 1214 | "members": { 1215 | "IndexName": {}, 1216 | "ProvisionedReadCapacityUnits": { 1217 | "type": "long" 1218 | }, 1219 | "ProvisionedReadCapacityAutoScalingSettingsUpdate": { 1220 | "shape": "S74" 1221 | } 1222 | } 1223 | } 1224 | } 1225 | } 1226 | } 1227 | } 1228 | } 1229 | }, 1230 | "output": { 1231 | "type": "structure", 1232 | "members": { 1233 | "GlobalTableName": {}, 1234 | "ReplicaSettings": { 1235 | "shape": "S4m" 1236 | } 1237 | } 1238 | }, 1239 | "endpointdiscovery": {} 1240 | }, 1241 | "UpdateItem": { 1242 | "input": { 1243 | "type": "structure", 1244 | "required": [ 1245 | "TableName", 1246 | "Key" 1247 | ], 1248 | "members": { 1249 | "TableName": {}, 1250 | "Key": { 1251 | "shape": "S6" 1252 | }, 1253 | "AttributeUpdates": { 1254 | "type": "map", 1255 | "key": {}, 1256 | "value": { 1257 | "type": "structure", 1258 | "members": { 1259 | "Value": { 1260 | "shape": "S8" 1261 | }, 1262 | "Action": {} 1263 | } 1264 | } 1265 | }, 1266 | "Expected": { 1267 | "shape": "S3t" 1268 | }, 1269 | "ConditionalOperator": {}, 1270 | "ReturnValues": {}, 1271 | "ReturnConsumedCapacity": {}, 1272 | "ReturnItemCollectionMetrics": {}, 1273 | "UpdateExpression": {}, 1274 | "ConditionExpression": {}, 1275 | "ExpressionAttributeNames": { 1276 | "shape": "Sm" 1277 | }, 1278 | "ExpressionAttributeValues": { 1279 | "shape": "S41" 1280 | } 1281 | } 1282 | }, 1283 | "output": { 1284 | "type": "structure", 1285 | "members": { 1286 | "Attributes": { 1287 | "shape": "Ss" 1288 | }, 1289 | "ConsumedCapacity": { 1290 | "shape": "Su" 1291 | }, 1292 | "ItemCollectionMetrics": { 1293 | "shape": "S1a" 1294 | } 1295 | } 1296 | }, 1297 | "endpointdiscovery": {} 1298 | }, 1299 | "UpdateTable": { 1300 | "input": { 1301 | "type": "structure", 1302 | "required": [ 1303 | "TableName" 1304 | ], 1305 | "members": { 1306 | "AttributeDefinitions": { 1307 | "shape": "S1z" 1308 | }, 1309 | "TableName": {}, 1310 | "BillingMode": {}, 1311 | "ProvisionedThroughput": { 1312 | "shape": "S2e" 1313 | }, 1314 | "GlobalSecondaryIndexUpdates": { 1315 | "type": "list", 1316 | "member": { 1317 | "type": "structure", 1318 | "members": { 1319 | "Update": { 1320 | "type": "structure", 1321 | "required": [ 1322 | "IndexName", 1323 | "ProvisionedThroughput" 1324 | ], 1325 | "members": { 1326 | "IndexName": {}, 1327 | "ProvisionedThroughput": { 1328 | "shape": "S2e" 1329 | } 1330 | } 1331 | }, 1332 | "Create": { 1333 | "type": "structure", 1334 | "required": [ 1335 | "IndexName", 1336 | "KeySchema", 1337 | "Projection" 1338 | ], 1339 | "members": { 1340 | "IndexName": {}, 1341 | "KeySchema": { 1342 | "shape": "S23" 1343 | }, 1344 | "Projection": { 1345 | "shape": "S28" 1346 | }, 1347 | "ProvisionedThroughput": { 1348 | "shape": "S2e" 1349 | } 1350 | } 1351 | }, 1352 | "Delete": { 1353 | "type": "structure", 1354 | "required": [ 1355 | "IndexName" 1356 | ], 1357 | "members": { 1358 | "IndexName": {} 1359 | } 1360 | } 1361 | } 1362 | } 1363 | }, 1364 | "StreamSpecification": { 1365 | "shape": "S2h" 1366 | }, 1367 | "SSESpecification": { 1368 | "shape": "S2k" 1369 | } 1370 | } 1371 | }, 1372 | "output": { 1373 | "type": "structure", 1374 | "members": { 1375 | "TableDescription": { 1376 | "shape": "S2t" 1377 | } 1378 | } 1379 | }, 1380 | "endpointdiscovery": {} 1381 | }, 1382 | "UpdateTimeToLive": { 1383 | "input": { 1384 | "type": "structure", 1385 | "required": [ 1386 | "TableName", 1387 | "TimeToLiveSpecification" 1388 | ], 1389 | "members": { 1390 | "TableName": {}, 1391 | "TimeToLiveSpecification": { 1392 | "shape": "S7s" 1393 | } 1394 | } 1395 | }, 1396 | "output": { 1397 | "type": "structure", 1398 | "members": { 1399 | "TimeToLiveSpecification": { 1400 | "shape": "S7s" 1401 | } 1402 | } 1403 | }, 1404 | "endpointdiscovery": {} 1405 | } 1406 | }, 1407 | "shapes": { 1408 | "S2": { 1409 | "type": "map", 1410 | "key": {}, 1411 | "value": { 1412 | "type": "structure", 1413 | "required": [ 1414 | "Keys" 1415 | ], 1416 | "members": { 1417 | "Keys": { 1418 | "type": "list", 1419 | "member": { 1420 | "shape": "S6" 1421 | } 1422 | }, 1423 | "AttributesToGet": { 1424 | "shape": "Sj" 1425 | }, 1426 | "ConsistentRead": { 1427 | "type": "boolean" 1428 | }, 1429 | "ProjectionExpression": {}, 1430 | "ExpressionAttributeNames": { 1431 | "shape": "Sm" 1432 | } 1433 | } 1434 | } 1435 | }, 1436 | "S6": { 1437 | "type": "map", 1438 | "key": {}, 1439 | "value": { 1440 | "shape": "S8" 1441 | } 1442 | }, 1443 | "S8": { 1444 | "type": "structure", 1445 | "members": { 1446 | "S": {}, 1447 | "N": {}, 1448 | "B": { 1449 | "type": "blob" 1450 | }, 1451 | "SS": { 1452 | "type": "list", 1453 | "member": {} 1454 | }, 1455 | "NS": { 1456 | "type": "list", 1457 | "member": {} 1458 | }, 1459 | "BS": { 1460 | "type": "list", 1461 | "member": { 1462 | "type": "blob" 1463 | } 1464 | }, 1465 | "M": { 1466 | "type": "map", 1467 | "key": {}, 1468 | "value": { 1469 | "shape": "S8" 1470 | } 1471 | }, 1472 | "L": { 1473 | "type": "list", 1474 | "member": { 1475 | "shape": "S8" 1476 | } 1477 | }, 1478 | "NULL": { 1479 | "type": "boolean" 1480 | }, 1481 | "BOOL": { 1482 | "type": "boolean" 1483 | } 1484 | } 1485 | }, 1486 | "Sj": { 1487 | "type": "list", 1488 | "member": {} 1489 | }, 1490 | "Sm": { 1491 | "type": "map", 1492 | "key": {}, 1493 | "value": {} 1494 | }, 1495 | "Sr": { 1496 | "type": "list", 1497 | "member": { 1498 | "shape": "Ss" 1499 | } 1500 | }, 1501 | "Ss": { 1502 | "type": "map", 1503 | "key": {}, 1504 | "value": { 1505 | "shape": "S8" 1506 | } 1507 | }, 1508 | "St": { 1509 | "type": "list", 1510 | "member": { 1511 | "shape": "Su" 1512 | } 1513 | }, 1514 | "Su": { 1515 | "type": "structure", 1516 | "members": { 1517 | "TableName": {}, 1518 | "CapacityUnits": { 1519 | "type": "double" 1520 | }, 1521 | "ReadCapacityUnits": { 1522 | "type": "double" 1523 | }, 1524 | "WriteCapacityUnits": { 1525 | "type": "double" 1526 | }, 1527 | "Table": { 1528 | "shape": "Sw" 1529 | }, 1530 | "LocalSecondaryIndexes": { 1531 | "shape": "Sx" 1532 | }, 1533 | "GlobalSecondaryIndexes": { 1534 | "shape": "Sx" 1535 | } 1536 | } 1537 | }, 1538 | "Sw": { 1539 | "type": "structure", 1540 | "members": { 1541 | "ReadCapacityUnits": { 1542 | "type": "double" 1543 | }, 1544 | "WriteCapacityUnits": { 1545 | "type": "double" 1546 | }, 1547 | "CapacityUnits": { 1548 | "type": "double" 1549 | } 1550 | } 1551 | }, 1552 | "Sx": { 1553 | "type": "map", 1554 | "key": {}, 1555 | "value": { 1556 | "shape": "Sw" 1557 | } 1558 | }, 1559 | "S10": { 1560 | "type": "map", 1561 | "key": {}, 1562 | "value": { 1563 | "type": "list", 1564 | "member": { 1565 | "type": "structure", 1566 | "members": { 1567 | "PutRequest": { 1568 | "type": "structure", 1569 | "required": [ 1570 | "Item" 1571 | ], 1572 | "members": { 1573 | "Item": { 1574 | "shape": "S14" 1575 | } 1576 | } 1577 | }, 1578 | "DeleteRequest": { 1579 | "type": "structure", 1580 | "required": [ 1581 | "Key" 1582 | ], 1583 | "members": { 1584 | "Key": { 1585 | "shape": "S6" 1586 | } 1587 | } 1588 | } 1589 | } 1590 | } 1591 | } 1592 | }, 1593 | "S14": { 1594 | "type": "map", 1595 | "key": {}, 1596 | "value": { 1597 | "shape": "S8" 1598 | } 1599 | }, 1600 | "S18": { 1601 | "type": "map", 1602 | "key": {}, 1603 | "value": { 1604 | "type": "list", 1605 | "member": { 1606 | "shape": "S1a" 1607 | } 1608 | } 1609 | }, 1610 | "S1a": { 1611 | "type": "structure", 1612 | "members": { 1613 | "ItemCollectionKey": { 1614 | "type": "map", 1615 | "key": {}, 1616 | "value": { 1617 | "shape": "S8" 1618 | } 1619 | }, 1620 | "SizeEstimateRangeGB": { 1621 | "type": "list", 1622 | "member": { 1623 | "type": "double" 1624 | } 1625 | } 1626 | } 1627 | }, 1628 | "S1h": { 1629 | "type": "structure", 1630 | "required": [ 1631 | "BackupArn", 1632 | "BackupName", 1633 | "BackupStatus", 1634 | "BackupType", 1635 | "BackupCreationDateTime" 1636 | ], 1637 | "members": { 1638 | "BackupArn": {}, 1639 | "BackupName": {}, 1640 | "BackupSizeBytes": { 1641 | "type": "long" 1642 | }, 1643 | "BackupStatus": {}, 1644 | "BackupType": {}, 1645 | "BackupCreationDateTime": { 1646 | "type": "timestamp" 1647 | }, 1648 | "BackupExpiryDateTime": { 1649 | "type": "timestamp" 1650 | } 1651 | } 1652 | }, 1653 | "S1p": { 1654 | "type": "list", 1655 | "member": { 1656 | "type": "structure", 1657 | "members": { 1658 | "RegionName": {} 1659 | } 1660 | } 1661 | }, 1662 | "S1t": { 1663 | "type": "structure", 1664 | "members": { 1665 | "ReplicationGroup": { 1666 | "type": "list", 1667 | "member": { 1668 | "type": "structure", 1669 | "members": { 1670 | "RegionName": {} 1671 | } 1672 | } 1673 | }, 1674 | "GlobalTableArn": {}, 1675 | "CreationDateTime": { 1676 | "type": "timestamp" 1677 | }, 1678 | "GlobalTableStatus": {}, 1679 | "GlobalTableName": {} 1680 | } 1681 | }, 1682 | "S1z": { 1683 | "type": "list", 1684 | "member": { 1685 | "type": "structure", 1686 | "required": [ 1687 | "AttributeName", 1688 | "AttributeType" 1689 | ], 1690 | "members": { 1691 | "AttributeName": {}, 1692 | "AttributeType": {} 1693 | } 1694 | } 1695 | }, 1696 | "S23": { 1697 | "type": "list", 1698 | "member": { 1699 | "type": "structure", 1700 | "required": [ 1701 | "AttributeName", 1702 | "KeyType" 1703 | ], 1704 | "members": { 1705 | "AttributeName": {}, 1706 | "KeyType": {} 1707 | } 1708 | } 1709 | }, 1710 | "S28": { 1711 | "type": "structure", 1712 | "members": { 1713 | "ProjectionType": {}, 1714 | "NonKeyAttributes": { 1715 | "type": "list", 1716 | "member": {} 1717 | } 1718 | } 1719 | }, 1720 | "S2e": { 1721 | "type": "structure", 1722 | "required": [ 1723 | "ReadCapacityUnits", 1724 | "WriteCapacityUnits" 1725 | ], 1726 | "members": { 1727 | "ReadCapacityUnits": { 1728 | "type": "long" 1729 | }, 1730 | "WriteCapacityUnits": { 1731 | "type": "long" 1732 | } 1733 | } 1734 | }, 1735 | "S2h": { 1736 | "type": "structure", 1737 | "members": { 1738 | "StreamEnabled": { 1739 | "type": "boolean" 1740 | }, 1741 | "StreamViewType": {} 1742 | } 1743 | }, 1744 | "S2k": { 1745 | "type": "structure", 1746 | "members": { 1747 | "Enabled": { 1748 | "type": "boolean" 1749 | }, 1750 | "SSEType": {}, 1751 | "KMSMasterKeyId": {} 1752 | } 1753 | }, 1754 | "S2o": { 1755 | "type": "list", 1756 | "member": { 1757 | "type": "structure", 1758 | "required": [ 1759 | "Key", 1760 | "Value" 1761 | ], 1762 | "members": { 1763 | "Key": {}, 1764 | "Value": {} 1765 | } 1766 | } 1767 | }, 1768 | "S2t": { 1769 | "type": "structure", 1770 | "members": { 1771 | "AttributeDefinitions": { 1772 | "shape": "S1z" 1773 | }, 1774 | "TableName": {}, 1775 | "KeySchema": { 1776 | "shape": "S23" 1777 | }, 1778 | "TableStatus": {}, 1779 | "CreationDateTime": { 1780 | "type": "timestamp" 1781 | }, 1782 | "ProvisionedThroughput": { 1783 | "shape": "S2v" 1784 | }, 1785 | "TableSizeBytes": { 1786 | "type": "long" 1787 | }, 1788 | "ItemCount": { 1789 | "type": "long" 1790 | }, 1791 | "TableArn": {}, 1792 | "TableId": {}, 1793 | "BillingModeSummary": { 1794 | "shape": "S30" 1795 | }, 1796 | "LocalSecondaryIndexes": { 1797 | "type": "list", 1798 | "member": { 1799 | "type": "structure", 1800 | "members": { 1801 | "IndexName": {}, 1802 | "KeySchema": { 1803 | "shape": "S23" 1804 | }, 1805 | "Projection": { 1806 | "shape": "S28" 1807 | }, 1808 | "IndexSizeBytes": { 1809 | "type": "long" 1810 | }, 1811 | "ItemCount": { 1812 | "type": "long" 1813 | }, 1814 | "IndexArn": {} 1815 | } 1816 | } 1817 | }, 1818 | "GlobalSecondaryIndexes": { 1819 | "type": "list", 1820 | "member": { 1821 | "type": "structure", 1822 | "members": { 1823 | "IndexName": {}, 1824 | "KeySchema": { 1825 | "shape": "S23" 1826 | }, 1827 | "Projection": { 1828 | "shape": "S28" 1829 | }, 1830 | "IndexStatus": {}, 1831 | "Backfilling": { 1832 | "type": "boolean" 1833 | }, 1834 | "ProvisionedThroughput": { 1835 | "shape": "S2v" 1836 | }, 1837 | "IndexSizeBytes": { 1838 | "type": "long" 1839 | }, 1840 | "ItemCount": { 1841 | "type": "long" 1842 | }, 1843 | "IndexArn": {} 1844 | } 1845 | } 1846 | }, 1847 | "StreamSpecification": { 1848 | "shape": "S2h" 1849 | }, 1850 | "LatestStreamLabel": {}, 1851 | "LatestStreamArn": {}, 1852 | "RestoreSummary": { 1853 | "type": "structure", 1854 | "required": [ 1855 | "RestoreDateTime", 1856 | "RestoreInProgress" 1857 | ], 1858 | "members": { 1859 | "SourceBackupArn": {}, 1860 | "SourceTableArn": {}, 1861 | "RestoreDateTime": { 1862 | "type": "timestamp" 1863 | }, 1864 | "RestoreInProgress": { 1865 | "type": "boolean" 1866 | } 1867 | } 1868 | }, 1869 | "SSEDescription": { 1870 | "shape": "S3b" 1871 | } 1872 | } 1873 | }, 1874 | "S2v": { 1875 | "type": "structure", 1876 | "members": { 1877 | "LastIncreaseDateTime": { 1878 | "type": "timestamp" 1879 | }, 1880 | "LastDecreaseDateTime": { 1881 | "type": "timestamp" 1882 | }, 1883 | "NumberOfDecreasesToday": { 1884 | "type": "long" 1885 | }, 1886 | "ReadCapacityUnits": { 1887 | "type": "long" 1888 | }, 1889 | "WriteCapacityUnits": { 1890 | "type": "long" 1891 | } 1892 | } 1893 | }, 1894 | "S30": { 1895 | "type": "structure", 1896 | "members": { 1897 | "BillingMode": {}, 1898 | "LastUpdateToPayPerRequestDateTime": { 1899 | "type": "timestamp" 1900 | } 1901 | } 1902 | }, 1903 | "S3b": { 1904 | "type": "structure", 1905 | "members": { 1906 | "Status": {}, 1907 | "SSEType": {}, 1908 | "KMSMasterKeyArn": {} 1909 | } 1910 | }, 1911 | "S3g": { 1912 | "type": "structure", 1913 | "members": { 1914 | "BackupDetails": { 1915 | "shape": "S1h" 1916 | }, 1917 | "SourceTableDetails": { 1918 | "type": "structure", 1919 | "required": [ 1920 | "TableName", 1921 | "TableId", 1922 | "KeySchema", 1923 | "TableCreationDateTime", 1924 | "ProvisionedThroughput" 1925 | ], 1926 | "members": { 1927 | "TableName": {}, 1928 | "TableId": {}, 1929 | "TableArn": {}, 1930 | "TableSizeBytes": { 1931 | "type": "long" 1932 | }, 1933 | "KeySchema": { 1934 | "shape": "S23" 1935 | }, 1936 | "TableCreationDateTime": { 1937 | "type": "timestamp" 1938 | }, 1939 | "ProvisionedThroughput": { 1940 | "shape": "S2e" 1941 | }, 1942 | "ItemCount": { 1943 | "type": "long" 1944 | }, 1945 | "BillingMode": {} 1946 | } 1947 | }, 1948 | "SourceTableFeatureDetails": { 1949 | "type": "structure", 1950 | "members": { 1951 | "LocalSecondaryIndexes": { 1952 | "type": "list", 1953 | "member": { 1954 | "type": "structure", 1955 | "members": { 1956 | "IndexName": {}, 1957 | "KeySchema": { 1958 | "shape": "S23" 1959 | }, 1960 | "Projection": { 1961 | "shape": "S28" 1962 | } 1963 | } 1964 | } 1965 | }, 1966 | "GlobalSecondaryIndexes": { 1967 | "type": "list", 1968 | "member": { 1969 | "type": "structure", 1970 | "members": { 1971 | "IndexName": {}, 1972 | "KeySchema": { 1973 | "shape": "S23" 1974 | }, 1975 | "Projection": { 1976 | "shape": "S28" 1977 | }, 1978 | "ProvisionedThroughput": { 1979 | "shape": "S2e" 1980 | } 1981 | } 1982 | } 1983 | }, 1984 | "StreamDescription": { 1985 | "shape": "S2h" 1986 | }, 1987 | "TimeToLiveDescription": { 1988 | "shape": "S3p" 1989 | }, 1990 | "SSEDescription": { 1991 | "shape": "S3b" 1992 | } 1993 | } 1994 | } 1995 | } 1996 | }, 1997 | "S3p": { 1998 | "type": "structure", 1999 | "members": { 2000 | "TimeToLiveStatus": {}, 2001 | "AttributeName": {} 2002 | } 2003 | }, 2004 | "S3t": { 2005 | "type": "map", 2006 | "key": {}, 2007 | "value": { 2008 | "type": "structure", 2009 | "members": { 2010 | "Value": { 2011 | "shape": "S8" 2012 | }, 2013 | "Exists": { 2014 | "type": "boolean" 2015 | }, 2016 | "ComparisonOperator": {}, 2017 | "AttributeValueList": { 2018 | "shape": "S3x" 2019 | } 2020 | } 2021 | } 2022 | }, 2023 | "S3x": { 2024 | "type": "list", 2025 | "member": { 2026 | "shape": "S8" 2027 | } 2028 | }, 2029 | "S41": { 2030 | "type": "map", 2031 | "key": {}, 2032 | "value": { 2033 | "shape": "S8" 2034 | } 2035 | }, 2036 | "S4a": { 2037 | "type": "structure", 2038 | "required": [ 2039 | "ContinuousBackupsStatus" 2040 | ], 2041 | "members": { 2042 | "ContinuousBackupsStatus": {}, 2043 | "PointInTimeRecoveryDescription": { 2044 | "type": "structure", 2045 | "members": { 2046 | "PointInTimeRecoveryStatus": {}, 2047 | "EarliestRestorableDateTime": { 2048 | "type": "timestamp" 2049 | }, 2050 | "LatestRestorableDateTime": { 2051 | "type": "timestamp" 2052 | } 2053 | } 2054 | } 2055 | } 2056 | }, 2057 | "S4m": { 2058 | "type": "list", 2059 | "member": { 2060 | "type": "structure", 2061 | "required": [ 2062 | "RegionName" 2063 | ], 2064 | "members": { 2065 | "RegionName": {}, 2066 | "ReplicaStatus": {}, 2067 | "ReplicaBillingModeSummary": { 2068 | "shape": "S30" 2069 | }, 2070 | "ReplicaProvisionedReadCapacityUnits": { 2071 | "type": "long" 2072 | }, 2073 | "ReplicaProvisionedReadCapacityAutoScalingSettings": { 2074 | "shape": "S4p" 2075 | }, 2076 | "ReplicaProvisionedWriteCapacityUnits": { 2077 | "type": "long" 2078 | }, 2079 | "ReplicaProvisionedWriteCapacityAutoScalingSettings": { 2080 | "shape": "S4p" 2081 | }, 2082 | "ReplicaGlobalSecondaryIndexSettings": { 2083 | "type": "list", 2084 | "member": { 2085 | "type": "structure", 2086 | "required": [ 2087 | "IndexName" 2088 | ], 2089 | "members": { 2090 | "IndexName": {}, 2091 | "IndexStatus": {}, 2092 | "ProvisionedReadCapacityUnits": { 2093 | "type": "long" 2094 | }, 2095 | "ProvisionedReadCapacityAutoScalingSettings": { 2096 | "shape": "S4p" 2097 | }, 2098 | "ProvisionedWriteCapacityUnits": { 2099 | "type": "long" 2100 | }, 2101 | "ProvisionedWriteCapacityAutoScalingSettings": { 2102 | "shape": "S4p" 2103 | } 2104 | } 2105 | } 2106 | } 2107 | } 2108 | } 2109 | }, 2110 | "S4p": { 2111 | "type": "structure", 2112 | "members": { 2113 | "MinimumUnits": { 2114 | "type": "long" 2115 | }, 2116 | "MaximumUnits": { 2117 | "type": "long" 2118 | }, 2119 | "AutoScalingDisabled": { 2120 | "type": "boolean" 2121 | }, 2122 | "AutoScalingRoleArn": {}, 2123 | "ScalingPolicies": { 2124 | "type": "list", 2125 | "member": { 2126 | "type": "structure", 2127 | "members": { 2128 | "PolicyName": {}, 2129 | "TargetTrackingScalingPolicyConfiguration": { 2130 | "type": "structure", 2131 | "required": [ 2132 | "TargetValue" 2133 | ], 2134 | "members": { 2135 | "DisableScaleIn": { 2136 | "type": "boolean" 2137 | }, 2138 | "ScaleInCooldown": { 2139 | "type": "integer" 2140 | }, 2141 | "ScaleOutCooldown": { 2142 | "type": "integer" 2143 | }, 2144 | "TargetValue": { 2145 | "type": "double" 2146 | } 2147 | } 2148 | } 2149 | } 2150 | } 2151 | } 2152 | } 2153 | }, 2154 | "S5w": { 2155 | "type": "structure", 2156 | "required": [ 2157 | "ComparisonOperator" 2158 | ], 2159 | "members": { 2160 | "AttributeValueList": { 2161 | "shape": "S3x" 2162 | }, 2163 | "ComparisonOperator": {} 2164 | } 2165 | }, 2166 | "S5x": { 2167 | "type": "map", 2168 | "key": {}, 2169 | "value": { 2170 | "shape": "S5w" 2171 | } 2172 | }, 2173 | "S74": { 2174 | "type": "structure", 2175 | "members": { 2176 | "MinimumUnits": { 2177 | "type": "long" 2178 | }, 2179 | "MaximumUnits": { 2180 | "type": "long" 2181 | }, 2182 | "AutoScalingDisabled": { 2183 | "type": "boolean" 2184 | }, 2185 | "AutoScalingRoleArn": {}, 2186 | "ScalingPolicyUpdate": { 2187 | "type": "structure", 2188 | "required": [ 2189 | "TargetTrackingScalingPolicyConfiguration" 2190 | ], 2191 | "members": { 2192 | "PolicyName": {}, 2193 | "TargetTrackingScalingPolicyConfiguration": { 2194 | "type": "structure", 2195 | "required": [ 2196 | "TargetValue" 2197 | ], 2198 | "members": { 2199 | "DisableScaleIn": { 2200 | "type": "boolean" 2201 | }, 2202 | "ScaleInCooldown": { 2203 | "type": "integer" 2204 | }, 2205 | "ScaleOutCooldown": { 2206 | "type": "integer" 2207 | }, 2208 | "TargetValue": { 2209 | "type": "double" 2210 | } 2211 | } 2212 | } 2213 | } 2214 | } 2215 | } 2216 | }, 2217 | "S7s": { 2218 | "type": "structure", 2219 | "required": [ 2220 | "Enabled", 2221 | "AttributeName" 2222 | ], 2223 | "members": { 2224 | "Enabled": { 2225 | "type": "boolean" 2226 | }, 2227 | "AttributeName": {} 2228 | } 2229 | } 2230 | } 2231 | } 2232 | `)); 2233 | -------------------------------------------------------------------------------- /api/operation.ts: -------------------------------------------------------------------------------- 1 | // var Shape = require('./shape'); 2 | import { Shape } from "./shape.ts"; 3 | // var util = require('../util'); 4 | // var property = util.property; 5 | // var memoizedProperty = util.memoizedProperty; 6 | import { Doc, memoizedProperty, property } from "../util.ts"; 7 | // import { Doc} from "../types.ts" 8 | 9 | export function Operation( 10 | this: any, 11 | name: string, 12 | operation: Doc, 13 | options: Doc = {}, 14 | ) { 15 | const self: any = this; 16 | // options = options || {}; 17 | 18 | property(this, "name", operation.name || name); 19 | property(this, "api", options.api, false); 20 | 21 | operation.http = operation.http || {}; 22 | 23 | property(this, "endpoint", operation.endpoint); 24 | property(this, "httpMethod", operation.http.method || "POST"); 25 | property(this, "httpPath", operation.http.requestUri || "/"); 26 | property(this, "authtype", operation.authtype || ""); 27 | property( 28 | this, 29 | "endpointDiscoveryRequired", 30 | operation.endpointdiscovery 31 | ? operation.endpointdiscovery.required ? "REQUIRED" : "OPTIONAL" 32 | : "NULL", 33 | ); 34 | 35 | memoizedProperty(this, "input", function (): any { 36 | if (!operation.input) { 37 | return /*new*/ Shape.create({ type: "structure" }, options); 38 | } 39 | 40 | return Shape.create(operation.input, options); 41 | }); 42 | 43 | memoizedProperty(this, "output", function (): any { 44 | if (!operation.output) { 45 | return /*new*/ Shape.create({ type: "structure" }, options); 46 | } 47 | 48 | return Shape.create(operation.output, options); 49 | }); 50 | 51 | memoizedProperty(this, "errors", function (): any[] { 52 | if (!operation.errors) { 53 | return []; 54 | } 55 | 56 | return operation.errors.map((error: any): any => 57 | Shape.create(error, options) 58 | ); 59 | 60 | // const list: any[] = []; 61 | // 62 | // for (let i: number = 0; i < operation.errors.length; i++) { 63 | // list.push(Shape.create(operation.errors[i], options)); 64 | // } 65 | // 66 | // return list; 67 | }); 68 | 69 | memoizedProperty(this, "paginator", function (): any { 70 | return options.api.paginators[name]; 71 | }); 72 | 73 | if (options.documentation) { 74 | property(this, "documentation", operation.documentation); 75 | property(this, "documentationUrl", operation.documentationUrl); 76 | } 77 | 78 | // idempotentMembers only tracks top-level input shapes 79 | memoizedProperty(this, "idempotentMembers", function (): string[] { 80 | // const idempotentMembers: string[] = []; 81 | // const input: Doc = self.input; 82 | // const members: Doc = input.members; 83 | 84 | if (!self.input.members) { 85 | return []; //idempotentMembers; 86 | } 87 | 88 | return Object.entries(self.input.members) 89 | .filter(([_, value]: [string, any]): boolean => value.isIdempotent) 90 | .map(([key, _]: [string, any]): string => key); 91 | 92 | // for (const name in members) { 93 | // if (!members.hasOwnProperty(name)) { 94 | // continue; 95 | // } 96 | // 97 | // if (members[name].isIdempotent) { 98 | // idempotentMembers.push(name); 99 | // } 100 | // } 101 | // 102 | // return idempotentMembers; 103 | }); 104 | 105 | memoizedProperty(this, "hasEventOutput", function (): boolean { 106 | // var output = self.output; 107 | return hasEventStream(self.output); 108 | }); 109 | } 110 | 111 | function hasEventStream(topLevelShape: Doc): boolean { 112 | const members: Doc = topLevelShape.members; 113 | const payload: string = topLevelShape.payload; 114 | 115 | if (!topLevelShape.members) { 116 | return false; 117 | } 118 | 119 | if (payload) { 120 | const payloadMember: Doc = members[payload]; 121 | return payloadMember.isEventStream; 122 | } 123 | 124 | // check if any member is an event stream 125 | for (const name in members) { 126 | if (!members.hasOwnProperty(name) && members[name].isEventStream) { 127 | // if (members[name].isEventStream === true) { 128 | // return true; 129 | // } 130 | return true; 131 | } 132 | } 133 | 134 | return false; 135 | } 136 | 137 | // /** 138 | // * @api private 139 | // */ 140 | // module.exports = Operation; 141 | -------------------------------------------------------------------------------- /api/shape.ts: -------------------------------------------------------------------------------- 1 | import { base64ToUint8Array, base64FromUint8Array } from "../deps.ts"; 2 | import { Collection } from "./collection.ts"; 3 | // import { Doc } from "../types.ts" 4 | import { 5 | Doc, 6 | date, 7 | memoizedProperty as utilMemoizedProperty, 8 | property as utilProperty, 9 | } from "../util.ts"; 10 | 11 | const _Collection: any = Collection; 12 | 13 | function property( 14 | this: any, 15 | obj: any, 16 | name: string, 17 | value: any, 18 | enumerable?: boolean, 19 | isValue?: boolean, 20 | ): void { 21 | if (value !== null && value !== undefined) { 22 | utilProperty.apply(this, arguments as any); 23 | } 24 | } 25 | 26 | function memoizedProperty( 27 | this: any, 28 | obj: any, 29 | name: string, 30 | get: () => any, 31 | enumerable?: boolean, 32 | ): void { 33 | if (!obj.constructor.prototype[name]) { 34 | utilMemoizedProperty.apply(this, arguments as any); 35 | } 36 | } 37 | 38 | export function Shape( 39 | this: any, 40 | shape: Doc, 41 | options: Doc = {}, 42 | memberName: string, 43 | ) { 44 | property(this, "shape", shape.shape); 45 | property(this, "api", options.api, false); 46 | property(this, "type", shape.type); 47 | property(this, "enum", shape.enum); 48 | property(this, "min", shape.min); 49 | property(this, "max", shape.max); 50 | property(this, "pattern", shape.pattern); 51 | property(this, "location", shape.location || this.location || "body"); 52 | property( 53 | this, 54 | "name", 55 | this.name || 56 | shape.xmlName || 57 | shape.queryName || 58 | shape.locationName || 59 | memberName, 60 | ); 61 | property(this, "isStreaming", shape.streaming || this.isStreaming || false); 62 | property(this, "requiresLength", shape.requiresLength, false); 63 | property(this, "isComposite", shape.isComposite || false); 64 | property(this, "isShape", true, false); 65 | property(this, "isQueryName", Boolean(shape.queryName), false); 66 | property(this, "isLocationName", Boolean(shape.locationName), false); 67 | property(this, "isIdempotent", shape.idempotencyToken); 68 | property(this, "isJsonValue", shape.jsonvalue); 69 | property( 70 | this, 71 | "isSensitive", 72 | shape.sensitive || (shape.prototype && shape.prototype.sensitive), 73 | ); 74 | property(this, "isEventStream", Boolean(shape.eventstream), false); 75 | property(this, "isEvent", Boolean(shape.event), false); 76 | property(this, "isEventPayload", Boolean(shape.eventpayload), false); 77 | property(this, "isEventHeader", Boolean(shape.eventheader), false); 78 | property( 79 | this, 80 | "isTimestampFormatSet", 81 | Boolean(shape.timestampFormat) || 82 | (shape.prototype && shape.prototype.isTimestampFormatSet), 83 | false, 84 | ); 85 | property( 86 | this, 87 | "endpointDiscoveryId", 88 | Boolean(shape.endpointdiscoveryid), 89 | false, 90 | ); 91 | property(this, "hostLabel", Boolean(shape.hostLabel), false); 92 | 93 | if (options.documentation) { 94 | property(this, "documentation", shape.documentation); 95 | property(this, "documentationUrl", shape.documentationUrl); 96 | } 97 | 98 | if (shape.xmlAttribute) { 99 | property(this, "isXmlAttribute", shape.xmlAttribute || false); 100 | } 101 | 102 | // type conversion and parsing 103 | property(this, "defaultValue", null); 104 | 105 | this.toWireFormat = function (value: any): any { 106 | if (value === null || value === undefined) { 107 | return ""; 108 | } 109 | 110 | return value; 111 | }; 112 | 113 | this.toType = function (value: any): any { 114 | return value; 115 | }; 116 | } 117 | 118 | /** 119 | * @api private 120 | */ 121 | Shape.normalizedTypes = { 122 | character: "string", 123 | double: "float", 124 | long: "integer", 125 | short: "integer", 126 | biginteger: "integer", 127 | bigdecimal: "float", 128 | blob: "binary", 129 | }; 130 | 131 | /** 132 | * @api private 133 | */ 134 | Shape.types = { 135 | structure: StructureShape, 136 | list: ListShape, 137 | map: MapShape, 138 | boolean: BooleanShape, 139 | timestamp: TimestampShape, 140 | float: FloatShape, 141 | integer: IntegerShape, 142 | string: StringShape, 143 | base64: Base64Shape, 144 | binary: BinaryShape, 145 | }; 146 | 147 | Shape.resolve = function resolve(shape: Doc, options: Doc = {}): Doc | null { 148 | if (shape.shape) { 149 | const refShape: Doc = options.api.shapes[shape.shape]; 150 | 151 | if (!refShape) { 152 | throw new Error(`Cannot find shape reference: ${shape.shape}`); 153 | } 154 | 155 | return refShape; 156 | } else { 157 | return null; 158 | } 159 | }; 160 | 161 | Shape.create = function create( 162 | shape: Doc, 163 | options: Doc = {}, 164 | memberName: string = "", 165 | ): any { 166 | if (shape.isShape) { 167 | return shape; 168 | } 169 | 170 | const refShape: Doc | null = Shape.resolve(shape, options); 171 | 172 | if (refShape) { 173 | let filteredKeys: string[] = Object.keys(shape); 174 | 175 | if (!options.documentation) { 176 | filteredKeys = filteredKeys.filter(function (name: string): boolean { 177 | return !name.match(/documentation/); 178 | }); 179 | } 180 | 181 | // create an inline shape with extra members 182 | const InlineShape: any = function (this: any): void { 183 | refShape.constructor.call(this, shape, options, memberName); 184 | }; 185 | 186 | InlineShape.prototype = refShape; 187 | return new InlineShape(); 188 | } else { 189 | // set type if not set 190 | if (!shape.type) { 191 | if (shape.members) { 192 | shape.type = "structure"; 193 | } else if (shape.member) { 194 | shape.type = "list"; 195 | } else if (shape.key) { 196 | shape.type = "map"; 197 | } else { 198 | shape.type = "string"; 199 | } 200 | } 201 | 202 | // normalize types 203 | const origType: string = shape.type; 204 | 205 | if ((Shape.normalizedTypes as any)[shape.type]) { 206 | shape.type = (Shape.normalizedTypes as any)[shape.type]; 207 | } 208 | 209 | if ((Shape.types as any)[shape.type]) { 210 | return new (Shape.types as any)[shape.type](shape, options, memberName); 211 | } else { 212 | throw new Error("Unrecognized shape type: " + origType); 213 | } 214 | } 215 | }; 216 | 217 | function CompositeShape(this: any, shape: Doc) { 218 | Shape.apply(this, arguments as any); 219 | property(this, "isComposite", true); 220 | 221 | if (shape.flattened) { 222 | property(this, "flattened", shape.flattened || false); 223 | } 224 | } 225 | 226 | function StructureShape(this: any, shape: Doc, options: Doc = {}) { 227 | const self: any = this; 228 | // let requiredMap: null | Doc = null; 229 | const firstInit: boolean = !this.isShape; 230 | 231 | CompositeShape.apply(this, arguments as any); 232 | 233 | if (firstInit) { 234 | property(this, "defaultValue", function () { 235 | return {}; 236 | }); 237 | property(this, "members", {}); 238 | property(this, "memberNames", []); 239 | property(this, "required", []); 240 | property(this, "isRequired", function () { 241 | return false; 242 | }); 243 | } 244 | 245 | if (shape.members) { 246 | property( 247 | this, 248 | "members", 249 | new _Collection(shape.members, options, function ( 250 | name: string, 251 | member: Doc, 252 | ): any { 253 | return Shape.create(member, options, name); 254 | }), 255 | ); 256 | 257 | memoizedProperty(this, "memberNames", function (): string[] { 258 | return shape.xmlOrder || Object.keys(shape.members); 259 | }); 260 | 261 | if (shape.event) { 262 | memoizedProperty(this, "eventPayloadMemberName", function (): string { 263 | const members: Doc = self.members; 264 | const memberNames: string[] = self.memberNames; 265 | 266 | // iterate over members to find ones that are event payloads 267 | for (let i: number = 0, iLen = memberNames.length; i < iLen; i++) { 268 | if (members[memberNames[i]].isEventPayload) { 269 | return memberNames[i]; 270 | } 271 | } 272 | 273 | return ""; 274 | }); 275 | 276 | memoizedProperty(this, "eventHeaderMemberNames", function (): string[] { 277 | const members: Doc = self.members; 278 | const memberNames: string[] = self.memberNames; 279 | const eventHeaderMemberNames: string[] = []; 280 | 281 | // iterate over members to find ones that are event headers 282 | for (let i: number = 0, iLen = memberNames.length; i < iLen; i++) { 283 | if (members[memberNames[i]].isEventHeader) { 284 | eventHeaderMemberNames.push(memberNames[i]); 285 | } 286 | } 287 | 288 | return eventHeaderMemberNames; 289 | }); 290 | } 291 | } 292 | 293 | if (shape.required) { 294 | property(this, "required", shape.required); 295 | 296 | const requiredMap = shape.required.reduce((acc: Doc, req: string): Doc => { 297 | acc[req] = true; 298 | return acc; 299 | }, {}); 300 | 301 | property( 302 | this, 303 | "isRequired", 304 | function (name: string): boolean { 305 | // if (!requiredMap) { 306 | // // requiredMap = {}; 307 | // // 308 | // // for (let i:number = 0; i < shape.required.length; i++) { 309 | // // requiredMap[shape.required[i]] = true; 310 | // // } 311 | // requiredMap = shape.required.reduce((acc: Doc, req: string): Doc => { 312 | // acc[req] = true; 313 | // return acc; 314 | // }, {}); 315 | // } 316 | 317 | return requiredMap[name]; 318 | }, 319 | false, 320 | true, 321 | ); 322 | } 323 | 324 | property(this, "resultWrapper", shape.resultWrapper || null); 325 | 326 | if (shape.payload) { 327 | property(this, "payload", shape.payload); 328 | } 329 | 330 | if (typeof shape.xmlNamespace === "string") { 331 | property(this, "xmlNamespaceUri", shape.xmlNamespace); 332 | } else if (typeof shape.xmlNamespace === "object") { 333 | property(this, "xmlNamespacePrefix", shape.xmlNamespace.prefix); 334 | property(this, "xmlNamespaceUri", shape.xmlNamespace.uri); 335 | } 336 | } 337 | 338 | function ListShape(this: any, shape: Doc, options: Doc = {}) { 339 | const self: any = this; 340 | const firstInit: boolean = !this.isShape; 341 | 342 | CompositeShape.apply(this, arguments as any); 343 | 344 | if (firstInit) { 345 | property(this, "defaultValue", function (): any[] { 346 | return []; 347 | }); 348 | } 349 | 350 | if (shape.member) { 351 | memoizedProperty(this, "member", function (): any { 352 | return Shape.create(shape.member, options); 353 | }); 354 | } 355 | 356 | if (this.flattened) { 357 | const oldName: string = this.name; 358 | 359 | memoizedProperty(this, "name", function (): string { 360 | return self.member.name || oldName; 361 | }); 362 | } 363 | } 364 | 365 | function MapShape(this: any, shape: Doc, options: Doc = {}) { 366 | const firstInit: boolean = !this.isShape; 367 | 368 | CompositeShape.apply(this, arguments as any); 369 | 370 | if (firstInit) { 371 | property(this, "defaultValue", function (): Doc { 372 | return {}; 373 | }); 374 | property(this, "key", Shape.create({ type: "string" }, options)); 375 | property(this, "value", Shape.create({ type: "string" }, options)); 376 | } 377 | 378 | if (shape.key) { 379 | memoizedProperty(this, "key", function (): any { 380 | return Shape.create(shape.key, options); 381 | }); 382 | } 383 | 384 | if (shape.value) { 385 | memoizedProperty(this, "value", function (): any { 386 | return Shape.create(shape.value, options); 387 | }); 388 | } 389 | } 390 | 391 | function TimestampShape(this: any, shape: Doc) { 392 | const self: any = this; 393 | 394 | Shape.apply(this, arguments as any); 395 | 396 | if (shape.timestampFormat) { 397 | property(this, "timestampFormat", shape.timestampFormat); 398 | } else if (self.isTimestampFormatSet && this.timestampFormat) { 399 | property(this, "timestampFormat", this.timestampFormat); 400 | } else if (this.location === "header") { 401 | property(this, "timestampFormat", "rfc822"); 402 | } else if (this.location === "querystring") { 403 | property(this, "timestampFormat", "iso8601"); 404 | } else if (this.api) { 405 | switch (this.api.protocol) { 406 | case "json": 407 | case "rest-json": 408 | property(this, "timestampFormat", "unixTimestamp"); 409 | break; 410 | case "rest-xml": 411 | case "query": 412 | case "ec2": 413 | property(this, "timestampFormat", "iso8601"); 414 | break; 415 | } 416 | } 417 | 418 | this.toType = function (value: any): undefined | Date { 419 | if (value === null || value === undefined) { 420 | return undefined; 421 | } 422 | 423 | if (typeof value.toISOString === "function") { 424 | return value as Date; 425 | } 426 | 427 | if (typeof value === "string" || typeof value === "number") { 428 | return date.parseTimestamp(value); 429 | } 430 | 431 | return undefined; 432 | // return typeof value === "string" || typeof value === "number" 433 | // ? date.parseTimestamp(value) 434 | // : null; 435 | }; 436 | 437 | this.toWireFormat = function (value: any): string { 438 | return date.format(value, self.timestampFormat); 439 | }; 440 | } 441 | 442 | function StringShape(this: any) { 443 | Shape.apply(this, arguments as any); 444 | 445 | const nullLessProtocols: string[] = ["rest-xml", "query", "ec2"]; 446 | 447 | this.toType = function (value: any): any { 448 | value = this.api && nullLessProtocols.indexOf(this.api.protocol) > -1 449 | ? value || "" 450 | : value; 451 | 452 | if (this.isJsonValue) { 453 | return JSON.parse(value); 454 | } 455 | 456 | return value && typeof value.toString === "function" 457 | ? value.toString() 458 | : value; 459 | }; 460 | 461 | this.toWireFormat = function (value: any): any { 462 | return this.isJsonValue ? JSON.stringify(value) : value; 463 | }; 464 | } 465 | 466 | function FloatShape(this: any) { 467 | Shape.apply(this, arguments as any); 468 | 469 | this.toType = function (value: any): undefined | number { 470 | if (value === null || value === undefined) { 471 | return undefined; 472 | } 473 | 474 | return parseFloat(value); 475 | }; 476 | 477 | this.toWireFormat = this.toType; 478 | } 479 | 480 | function IntegerShape(this: any) { 481 | Shape.apply(this, arguments as any); 482 | 483 | this.toType = function (value: any): undefined | number { 484 | if (value === null || value === undefined) { 485 | return undefined; 486 | } 487 | 488 | return parseInt(value, 10); 489 | }; 490 | 491 | this.toWireFormat = this.toType; 492 | } 493 | 494 | function BinaryShape(this: any) { 495 | Shape.apply(this, arguments as any); 496 | 497 | this.toType = base64ToUint8Array; 498 | 499 | this.toWireFormat = base64FromUint8Array; 500 | } 501 | 502 | function Base64Shape(this: any) { 503 | BinaryShape.apply(this, arguments as any); 504 | } 505 | 506 | function BooleanShape(this: any) { 507 | Shape.apply(this, arguments as any); 508 | 509 | this.toType = function (value: any): undefined | boolean { 510 | if (typeof value === "boolean") { 511 | return value; 512 | } 513 | 514 | if (value === null || value === undefined) { 515 | return undefined; 516 | } 517 | 518 | return value === "true"; 519 | }; 520 | } 521 | 522 | /** 523 | * @api private 524 | */ 525 | Shape.shapes = { 526 | StructureShape: StructureShape, 527 | ListShape: ListShape, 528 | MapShape: MapShape, 529 | StringShape: StringShape, 530 | BooleanShape: BooleanShape, 531 | Base64Shape: Base64Shape, 532 | }; 533 | 534 | // /** 535 | // * @api private 536 | // */ 537 | // module.exports = Shape; 538 | -------------------------------------------------------------------------------- /client/aws_signature_v4.ts: -------------------------------------------------------------------------------- 1 | import { encode, decode, hmac } from "../deps.ts"; 2 | import { date } from "../util.ts"; 3 | 4 | /** Some magic bytes. */ 5 | const AWS4: Uint8Array = encode("AWS4", "utf8"); 6 | 7 | /** Creates a HMAC-SHA256 mac.*/ 8 | export function awsSignatureV4( 9 | key: Uint8Array, 10 | msg: Uint8Array, 11 | outputEncoding?: string, 12 | ): string | Uint8Array { 13 | return hmac("sha256", key, msg, undefined, outputEncoding); 14 | } 15 | 16 | /** Creates a key for generating an aws signature version 4. */ 17 | export function kdf( 18 | key: string | Uint8Array, 19 | dateStamp: Date | string, 20 | region: string, 21 | service: string, 22 | keyInputEncoding?: string, 23 | outputEncoding?: string, 24 | ): string | Uint8Array { 25 | if (typeof key === "string") { 26 | key = encode(key, keyInputEncoding) as Uint8Array; 27 | } 28 | 29 | if (typeof dateStamp !== "string") { 30 | dateStamp = date.format(dateStamp, "dateStamp"); 31 | } else if (!date.DATE_STAMP_REGEX.test(dateStamp)) { 32 | throw new TypeError("date stamp format must be yyyymmdd"); 33 | } 34 | 35 | const paddedKey: Uint8Array = new Uint8Array(4 + key.byteLength); 36 | 37 | paddedKey.set(AWS4, 0); 38 | paddedKey.set(key, 4); 39 | 40 | let mac: Uint8Array = hmac( 41 | "sha256", 42 | paddedKey, 43 | dateStamp as string, 44 | "utf8", 45 | ) as Uint8Array; 46 | 47 | mac = hmac("sha256", mac, region, "utf8") as Uint8Array; 48 | 49 | mac = hmac("sha256", mac, service, "utf8") as Uint8Array; 50 | 51 | mac = hmac("sha256", mac, "aws4_request", "utf8") as Uint8Array; 52 | 53 | return outputEncoding ? decode(mac, outputEncoding) : mac; 54 | } 55 | -------------------------------------------------------------------------------- /client/base_fetch.ts: -------------------------------------------------------------------------------- 1 | import { encode } from "../deps.ts"; 2 | import { HeadersConfig, createHeaders } from "./create_headers.ts"; 3 | import { Doc } from "../util.ts"; 4 | 5 | /** Base fetch. */ 6 | export async function baseFetch( 7 | conf: Doc, 8 | op: string, 9 | params: Doc, 10 | ): Promise { 11 | const payload: Uint8Array = encode(JSON.stringify(params), "utf8"); 12 | 13 | let headers: Headers = await createHeaders( 14 | op, 15 | payload, 16 | conf as HeadersConfig, 17 | ); 18 | 19 | let response: Response = await fetch(conf.endpoint, { 20 | method: conf.method, 21 | headers, 22 | body: payload, 23 | }); 24 | 25 | let body: Doc = await response.json(); 26 | 27 | if (!response.ok) { 28 | if (response.status === 403) { 29 | // retry once with refreshed credenttials 30 | headers = await createHeaders(op, payload, conf as HeadersConfig, true); 31 | 32 | response = await fetch(conf.endpoint, { 33 | method: conf.method, 34 | headers, 35 | body: payload, 36 | }); 37 | 38 | if (response.ok) { 39 | body = await response.json(); 40 | 41 | return body; 42 | } 43 | } 44 | 45 | throw new Error(body.message || body.Message); 46 | } 47 | 48 | return body; 49 | } 50 | -------------------------------------------------------------------------------- /client/base_op.ts: -------------------------------------------------------------------------------- 1 | import { baseFetch } from "./base_fetch.ts"; 2 | import { API } from "../api/mod.ts"; 3 | import { Translator } from "./translator.ts"; 4 | import { Doc } from "../util.ts"; 5 | 6 | // ts strict food 7 | const _Translator: any = Translator; 8 | 9 | /** Op options. */ 10 | export interface OpOptions { 11 | wrapNumbers?: boolean; // wrap numbers to a special number value type? [false] 12 | convertEmptyValues?: boolean; // convert empty strings and binaries? [false] 13 | translateJSON?: boolean; // translate I/O JSON schemas? [true] 14 | iteratePages?: boolean; // if a result is paged, async-iterate it? [true] 15 | } 16 | 17 | /** DynamoDB operations that do not take any parameters. */ 18 | export const NO_PARAMS_OPS: Set = new Set([ 19 | "DescribeEndpoints", 20 | "DescribeLimits", 21 | "ListTables", 22 | ]); 23 | 24 | /** Base shape of all DynamoDB query schemas. */ 25 | const ATTR_VALUE: string = 26 | API.operations.PutItem.input.members.Item.value.shape; 27 | 28 | /** Base op. */ 29 | export async function baseOp( 30 | conf: Doc, 31 | op: string, 32 | params: Doc = {}, 33 | { 34 | wrapNumbers = false, 35 | convertEmptyValues = false, 36 | translateJSON = true, 37 | iteratePages = true, 38 | }: OpOptions = NO_PARAMS_OPS.has(op) ? params || {} : {}, 39 | ): Promise { 40 | let translator: any; 41 | let outputShape: any; 42 | 43 | if (translateJSON) { 44 | translator = new _Translator({ 45 | wrapNumbers, 46 | convertEmptyValues, 47 | attrValue: ATTR_VALUE, 48 | }); 49 | 50 | outputShape = API.operations[op].output; 51 | 52 | params = translator.translateInput(params, API.operations[op].input); 53 | } else { 54 | params = { ...params }; 55 | } 56 | 57 | let rawResult: Doc = await baseFetch(conf, op, params); 58 | 59 | if (rawResult.LastEvaluatedKey && iteratePages) { 60 | let lastEvaluatedKey: any = rawResult.LastEvaluatedKey; 61 | let first: boolean = true; 62 | 63 | return { 64 | [Symbol.asyncIterator](): AsyncIterableIterator { 65 | return this as AsyncIterableIterator; 66 | }, 67 | async next(): Promise> { 68 | if (!lastEvaluatedKey) { 69 | return { value: {}, done: true }; 70 | } 71 | 72 | if (first) { 73 | first = false; 74 | 75 | lastEvaluatedKey = rawResult.LastEvaluatedKey; 76 | 77 | if (!translateJSON) { 78 | return { 79 | value: rawResult, 80 | done: false, 81 | }; 82 | } else { 83 | return { 84 | value: translator.translateOutput(rawResult, outputShape), 85 | done: false, 86 | }; 87 | } 88 | } else { 89 | params.ExclusiveStartKey = lastEvaluatedKey; 90 | } 91 | 92 | rawResult = await baseFetch(conf, op, params); 93 | 94 | lastEvaluatedKey = rawResult.LastEvaluatedKey; 95 | 96 | if (!translateJSON) { 97 | return { value: rawResult, done: false }; 98 | } 99 | 100 | return { 101 | value: translator.translateOutput(rawResult, outputShape), 102 | done: false, 103 | }; 104 | }, 105 | }; 106 | } 107 | 108 | if (!translateJSON) { 109 | return rawResult; 110 | } 111 | 112 | return translator.translateOutput(rawResult, outputShape); 113 | } 114 | -------------------------------------------------------------------------------- /client/converter.ts: -------------------------------------------------------------------------------- 1 | // import DynamoDB = require('../../clients/dynamodb'); 2 | import { base64ToUint8Array, base64FromUint8Array } from "../deps.ts"; 3 | import { Doc, DynamoDBSet, DynamoDBNumberValue, typeOf } from "../util.ts"; 4 | 5 | /** Formats a list. */ 6 | function formatList(data: any[], options: Doc = {}): Doc { 7 | const list: Doc = { L: [] }; 8 | 9 | for (let i: number = 0; i < data.length; i++) { 10 | list["L"].push(Converter.input(data[i], options)); 11 | } 12 | 13 | return list; 14 | } 15 | 16 | /** Converts a number. */ 17 | function convertNumber(value: string, wrapNumbers: boolean = false): any { 18 | return wrapNumbers ? new DynamoDBNumberValue(value) : Number(value); 19 | } 20 | 21 | /** Formats a map. */ 22 | function formatMap(data: Doc, options: Doc = {}): Doc { 23 | const map: Doc = { M: {} }; 24 | 25 | for (const key in data) { 26 | const formatted: Doc = Converter.input(data[key], options); 27 | 28 | if (formatted !== void 0) { 29 | map["M"][key] = formatted; 30 | } 31 | } 32 | 33 | return map; 34 | } 35 | 36 | /** Formats a set. */ 37 | function formatSet(data: Doc, options: Doc = {}): Doc { 38 | let values: any[] = data.values; 39 | 40 | if (options.convertEmptyValues) { 41 | values = filterEmptySetValues(data); 42 | 43 | if (values.length === 0) { 44 | return Converter.input(null); 45 | } 46 | } 47 | 48 | const map: Doc = {}; 49 | 50 | switch (data.type) { 51 | case "String": 52 | map["SS"] = values; 53 | break; 54 | case "Binary": 55 | map["BS"] = values; 56 | break; 57 | case "Number": 58 | map["NS"] = values.map(function (value) { 59 | return value.toString(); 60 | }); 61 | } 62 | 63 | return map; 64 | } 65 | 66 | /** Filters empty set values. */ 67 | function filterEmptySetValues(set: Doc): any[] { 68 | const nonEmptyValues: any[] = []; 69 | 70 | const potentiallyEmptyTypes: Doc = { 71 | String: true, 72 | Binary: true, 73 | Number: false, 74 | }; 75 | 76 | if (potentiallyEmptyTypes[set.type]) { 77 | for (let i: number = 0; i < set.values.length; i++) { 78 | if (set.values[i].length === 0) { 79 | continue; 80 | } 81 | 82 | nonEmptyValues.push(set.values[i]); 83 | } 84 | 85 | return nonEmptyValues; 86 | } 87 | 88 | return set.values; 89 | } 90 | 91 | /** aws DynamoDB req/res document converter. */ 92 | export class Converter { 93 | /** 94 | * Convert a JavaScript value to its equivalent DynamoDB AttributeValue type 95 | * 96 | * @param data [any] The data to convert to a DynamoDB AttributeValue 97 | * @param options [map] 98 | * @option options convertEmptyValues [Boolean] Whether to automatically 99 | * convert empty strings, blobs, 100 | * and sets to `null` 101 | * @option options wrapNumbers [Boolean] Whether to return numbers as a 102 | * NumberValue object instead of 103 | * converting them to native JavaScript 104 | * numbers. This allows for the safe 105 | * round-trip transport of numbers of 106 | * arbitrary size. 107 | * @return [map] An object in the Amazon DynamoDB AttributeValue format 108 | * 109 | * @see AWS.DynamoDB.Converter.marshall AWS.DynamoDB.Converter.marshall to 110 | * convert entire records (rather than individual attributes) 111 | */ 112 | static input(data: any, options: Doc = {}): Doc { 113 | const type: string = typeOf(data); 114 | 115 | if (type === "Object") { 116 | return formatMap(data, options); 117 | } else if (type === "Array") { 118 | return formatList(data, options); 119 | } else if (type === "Set") { 120 | return formatSet(data, options); 121 | } else if (type === "String") { 122 | if (data.length === 0 && options.convertEmptyValues) { 123 | return Converter.input(null); 124 | } 125 | return { S: data }; 126 | } else if (type === "Number" || type === "NumberValue") { 127 | return { N: data.toString() }; 128 | } else if (type === "Binary") { 129 | if (data.length === 0 && options.convertEmptyValues) { 130 | return Converter.input(null); 131 | } 132 | // return { B: data }; 133 | return { B: base64FromUint8Array(data) }; 134 | } else if (type === "Boolean") { 135 | return { BOOL: data }; 136 | } else if (type === "null") { 137 | return { NULL: true }; 138 | } else if (type !== "undefined" && type !== "Function") { 139 | // this value has a custom constructor 140 | return formatMap(data, options); 141 | } 142 | 143 | return {}; 144 | } 145 | 146 | /** 147 | * Convert a JavaScript object into a DynamoDB record. 148 | * 149 | * @param data [any] The data to convert to a DynamoDB record 150 | * @param options [map] 151 | * @option options convertEmptyValues [Boolean] Whether to automatically 152 | * convert empty strings, blobs, 153 | * and sets to `null` 154 | * @option options wrapNumbers [Boolean] Whether to return numbers as a 155 | * NumberValue object instead of 156 | * converting them to native JavaScript 157 | * numbers. This allows for the safe 158 | * round-trip transport of numbers of 159 | * arbitrary size. 160 | * 161 | * @return [map] An object in the DynamoDB record format. 162 | * 163 | * @example Convert a JavaScript object into a DynamoDB record 164 | * var marshalled = AWS.DynamoDB.Converter.marshall({ 165 | * string: 'foo', 166 | * list: ['fizz', 'buzz', 'pop'], 167 | * map: { 168 | * nestedMap: { 169 | * key: 'value', 170 | * } 171 | * }, 172 | * number: 123, 173 | * nullValue: null, 174 | * boolValue: true, 175 | * stringSet: new DynamoDBSet(['foo', 'bar', 'baz']) 176 | * }); 177 | */ 178 | static marshall(data: Doc, options?: Doc): Doc { 179 | return Converter.input(data, options).M; 180 | } 181 | 182 | /** 183 | * Convert a DynamoDB AttributeValue object to its equivalent JavaScript type. 184 | * 185 | * @param data [map] An object in the Amazon DynamoDB AttributeValue format 186 | * @param options [map] 187 | * @option options convertEmptyValues [Boolean] Whether to automatically 188 | * convert empty strings, blobs, 189 | * and sets to `null` 190 | * @option options wrapNumbers [Boolean] Whether to return numbers as a 191 | * NumberValue object instead of 192 | * converting them to native JavaScript 193 | * numbers. This allows for the safe 194 | * round-trip transport of numbers of 195 | * arbitrary size. 196 | * 197 | * @return [Object|Array|String|Number|Boolean|null] 198 | * 199 | * @see AWS.DynamoDB.Converter.unmarshall AWS.DynamoDB.Converter.unmarshall to 200 | * convert entire records (rather than individual attributes) 201 | */ 202 | static output(data: Doc, options: Doc = {}): any { 203 | for (const type in data) { 204 | const values: any = data[type]; 205 | 206 | if (type === "M") { 207 | const map: Doc = {}; 208 | 209 | for (const key in values) { 210 | map[key] = Converter.output(values[key], options); 211 | } 212 | 213 | return map; 214 | } else if (type === "L") { 215 | // list = []; 216 | // for (i = 0; i < values.length; i++) { 217 | // list.push(Converter.output(values[i], options)); 218 | // } 219 | // return list; 220 | return values.map((value: any): any => 221 | Converter.output(value, options) 222 | ); 223 | } else if (type === "SS") { 224 | // list = []; 225 | // for (i = 0; i < values.length; i++) { 226 | // list.push(values[i] + ''); 227 | // } 228 | // return new DynamoDBSet(list); 229 | return new DynamoDBSet(values.map(String)); 230 | } else if (type === "NS") { 231 | // list = []; 232 | // for (i = 0; i < values.length; i++) { 233 | // list.push(convertNumber(values[i], options.wrapNumbers)); 234 | // } 235 | // return new DynamoDBSet(list); 236 | return new DynamoDBSet( 237 | values.map((value: any): number => 238 | convertNumber(value, options.wrapNumbers) 239 | ), 240 | ); 241 | } else if (type === "BS") { 242 | // list = []; 243 | // for (i = 0; i < values.length; i++) { 244 | // list.push(base64ToUint8Array(values[i])); 245 | // } 246 | // return new DynamoDBSet(list); 247 | return new DynamoDBSet(values.map(base64ToUint8Array)); 248 | } else if (type === "S") { 249 | return String(values); 250 | } else if (type === "N") { 251 | return convertNumber(values, options.wrapNumbers); 252 | } else if (type === "B") { 253 | return base64ToUint8Array(values); 254 | } else if (type === "BOOL") { 255 | return values === "true" || values === "TRUE" || values === true; 256 | } else if (type === "NULL") { 257 | return null; 258 | } 259 | } 260 | } 261 | 262 | /** 263 | * Convert a DynamoDB record into a JavaScript object. 264 | * 265 | * @param data [any] The DynamoDB record 266 | * @param options [map] 267 | * @option options convertEmptyValues [Boolean] Whether to automatically 268 | * convert empty strings, blobs, 269 | * and sets to `null` 270 | * @option options wrapNumbers [Boolean] Whether to return numbers as a 271 | * NumberValue object instead of 272 | * converting them to native JavaScript 273 | * numbers. This allows for the safe 274 | * round-trip transport of numbers of 275 | * arbitrary size. 276 | * 277 | * @return [map] An object whose properties have been converted from 278 | * DynamoDB's AttributeValue format into their corresponding native 279 | * JavaScript types. 280 | * 281 | * @example Convert a record received from a DynamoDB stream 282 | * var unmarshalled = AWS.DynamoDB.Converter.unmarshall({ 283 | * string: {S: 'foo'}, 284 | * list: {L: [{S: 'fizz'}, {S: 'buzz'}, {S: 'pop'}]}, 285 | * map: { 286 | * M: { 287 | * nestedMap: { 288 | * M: { 289 | * key: {S: 'value'} 290 | * } 291 | * } 292 | * } 293 | * }, 294 | * number: {N: '123'}, 295 | * nullValue: {NULL: true}, 296 | * boolValue: {BOOL: true} 297 | * }); 298 | */ 299 | static unmarshall(data: Doc, options?: Doc): Doc { 300 | return Converter.output({ M: data }, options); 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /client/create_cache.ts: -------------------------------------------------------------------------------- 1 | import { ClientConfig, Credentials } from "../mod.ts"; 2 | import { kdf } from "./aws_signature_v4.ts"; 3 | import { Doc, date } from "../util.ts"; 4 | 5 | /** Service name. */ 6 | const SERVICE: string = "dynamodb"; 7 | 8 | /** Cache for credentialScope and expensive signature key. */ 9 | export function createCache(conf: ClientConfig): Doc { 10 | return { 11 | _credentialScope: "", 12 | _signingKey: null, 13 | _accessKeyId: "", 14 | _sessionToken: "", 15 | async refresh(): Promise { 16 | const dateStamp: string = date.format(new Date(), "dateStamp"); 17 | 18 | let credentials: Credentials; 19 | 20 | if (typeof conf.credentials === "function") { 21 | credentials = await conf.credentials(); 22 | } else { 23 | credentials = conf.credentials!; 24 | } 25 | 26 | this._signingKey = kdf( 27 | credentials.secretAccessKey, 28 | dateStamp, 29 | conf.region!, 30 | SERVICE, 31 | ) as Uint8Array; 32 | 33 | this._credentialScope = 34 | `${dateStamp}/${conf.region}/${SERVICE}/aws4_request`; 35 | this._accessKeyId = credentials.accessKeyId; 36 | this._sessionToken = credentials.sessionToken; 37 | }, 38 | get signingKey(): Uint8Array { 39 | return this._signingKey; 40 | }, 41 | get credentialScope(): string { 42 | return this._credentialScope; 43 | }, 44 | get accessKeyId(): string { 45 | return this._accessKeyId; 46 | }, 47 | get sessionToken(): string { 48 | return this._sessionToken; 49 | }, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /client/create_headers.ts: -------------------------------------------------------------------------------- 1 | import { sha256, encode } from "../deps.ts"; 2 | import { awsSignatureV4 } from "./aws_signature_v4.ts"; 3 | import { Doc, date } from "../util.ts"; 4 | import { ClientConfig } from "../mod.ts"; 5 | 6 | /** Algorithm identifer. */ 7 | const ALGORITHM: string = "AWS4-HMAC-SHA256"; 8 | 9 | /** Content type header value for POST requests. */ 10 | const CONTENT_TYPE: string = "application/x-amz-json-1.0"; 11 | 12 | /** Required configuration for assembling headers. */ 13 | export interface HeadersConfig extends ClientConfig { 14 | host: string; // dynamodb.us-west-2.amazonaws.com 15 | method: string; // POST 16 | cache: Doc; // internal cache for expensive-2-make signing key (& credScope) 17 | date?: Date; // allows reusing a date for 5min (max signature timestamp diff) 18 | } 19 | 20 | /** Assembles a header object for a DynamoDB request. */ 21 | export async function createHeaders( 22 | op: string, 23 | payload: Uint8Array, 24 | conf: HeadersConfig, 25 | refreshCredentials: boolean = !conf.cache.signingKey, 26 | ): Promise { 27 | if (refreshCredentials) { 28 | await conf.cache.refresh(); 29 | } 30 | 31 | const amzTarget: string = `DynamoDB_20120810.${op}`; 32 | 33 | const amzDate: string = date.format(conf.date || new Date(), "amz"); 34 | 35 | const canonicalUri: string = conf.canonicalUri || "/"; 36 | 37 | const canonicalHeaders: string = 38 | `content-type:${CONTENT_TYPE}\nhost:${conf.host}\nx-amz-date:${amzDate}\nx-amz-target:${amzTarget}\n`; 39 | 40 | const signedHeaders: string = "content-type;host;x-amz-date;x-amz-target"; 41 | 42 | const payloadHash: string = sha256(payload, undefined, "hex") as string; 43 | 44 | const canonicalRequest: string = 45 | `${conf.method}\n${canonicalUri}\n\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`; 46 | 47 | const canonicalRequestDigest: string = sha256( 48 | canonicalRequest, 49 | "utf8", 50 | "hex", 51 | ) as string; 52 | 53 | const msg: Uint8Array = encode( 54 | `${ALGORITHM}\n${amzDate}\n${conf.cache.credentialScope}\n${canonicalRequestDigest}`, 55 | "utf8", 56 | ); 57 | 58 | const signature: string = awsSignatureV4( 59 | conf.cache.signingKey, 60 | msg, 61 | "hex", 62 | ) as string; 63 | 64 | const authorizationHeader: string = 65 | `${ALGORITHM} Credential=${conf.cache.accessKeyId}/${conf.cache.credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`; 66 | 67 | const headers: Headers = new Headers({ 68 | "Content-Type": CONTENT_TYPE, 69 | "X-Amz-Date": amzDate, 70 | "X-Amz-Target": amzTarget, 71 | Authorization: authorizationHeader, 72 | }); 73 | 74 | // https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html 75 | if (conf.cache.sessionToken) { 76 | headers.append("X-Amz-Security-Token", conf.cache.sessionToken); 77 | } 78 | 79 | return headers; 80 | } 81 | -------------------------------------------------------------------------------- /client/derive_config.ts: -------------------------------------------------------------------------------- 1 | import { get } from "../deps.ts"; 2 | import { ClientConfig } from "../mod.ts"; 3 | import { Doc } from "../util.ts"; 4 | import { createCache } from "./create_cache.ts"; 5 | 6 | /** Derives host and endpoint. */ 7 | function deriveHostEndpoint( 8 | region: string, 9 | port = 8000, 10 | host = "localhost" 11 | ): { host: string; endpoint: string } { 12 | let _host: string = host; 13 | let endpoint: string; 14 | 15 | if (region === "local") { 16 | endpoint = `http://${host}:${port}/`; 17 | } else { 18 | _host = `dynamodb.${region}.amazonaws.com`; 19 | endpoint = `https://${_host}:443/`; 20 | } 21 | 22 | return { host: _host, endpoint }; 23 | } 24 | 25 | /** Derives an internal config object from a ClientConfig. */ 26 | export function deriveConfig(conf: ClientConfig = {}): Doc { 27 | const _conf: ClientConfig = { ...conf }; 28 | 29 | if ( 30 | _conf.profile || 31 | !_conf.region || 32 | !_conf.credentials || 33 | (typeof _conf.credentials !== "function" && 34 | (!_conf.credentials.accessKeyId || !_conf.credentials.secretAccessKey)) 35 | ) { 36 | const got: Doc = get({ profile: _conf.profile }); 37 | 38 | if (typeof _conf.credentials !== "function") { 39 | _conf.credentials = { 40 | accessKeyId: got.accessKeyId, 41 | secretAccessKey: got.secretAccessKey, 42 | sessionToken: got.sessionToken, 43 | ..._conf.credentials, 44 | }; 45 | } 46 | 47 | _conf.region = got.region; 48 | 49 | if ( 50 | typeof _conf.credentials !== "function" && 51 | (!_conf.region || 52 | !_conf.credentials.accessKeyId || 53 | !_conf.credentials.secretAccessKey) 54 | ) { 55 | throw new Error("unable to derive aws config"); 56 | } 57 | } 58 | 59 | return { 60 | ..._conf, 61 | cache: createCache(_conf), 62 | method: "POST", 63 | ...deriveHostEndpoint(_conf.region!, _conf.port!, _conf.host!), 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /client/mod.ts: -------------------------------------------------------------------------------- 1 | export { awsSignatureV4 } from "./aws_signature_v4.ts"; 2 | export { baseOp } from "./base_op.ts"; 3 | export { deriveConfig } from "./derive_config.ts"; 4 | export type { HeadersConfig } from "./create_headers.ts"; 5 | export { createHeaders } from "./create_headers.ts"; 6 | export { Translator } from "./translator.ts"; 7 | -------------------------------------------------------------------------------- /client/translator.ts: -------------------------------------------------------------------------------- 1 | // var util = require('../core').util; 2 | // var convert = require('./converter'); 3 | import { Doc } from "../util.ts"; 4 | import { Converter } from "./converter.ts"; 5 | 6 | export function Translator( 7 | this: any, 8 | { 9 | wrapNumbers, 10 | convertEmptyValues, 11 | attrValue, 12 | }: Doc = {}, 13 | ) { 14 | // options = options || {}; 15 | this.attrValue = attrValue; 16 | this.convertEmptyValues = Boolean(convertEmptyValues); 17 | this.wrapNumbers = Boolean(wrapNumbers); 18 | } 19 | 20 | Translator.prototype.translateInput = function (value: any, shape: any): any { 21 | this.mode = "input"; 22 | return this.translate(value, shape); 23 | }; 24 | 25 | Translator.prototype.translateOutput = function (value: any, shape: any): any { 26 | this.mode = "output"; 27 | return this.translate(value, shape); 28 | }; 29 | 30 | Translator.prototype.translate = function (value: any, shape: any): any { 31 | const self: any = this; 32 | 33 | if (!shape || value === undefined) { 34 | return undefined; 35 | } 36 | 37 | if (shape.shape === self.attrValue) { 38 | return (Converter as any)[self.mode](value, { 39 | convertEmptyValues: self.convertEmptyValues, 40 | wrapNumbers: self.wrapNumbers, 41 | }); 42 | } 43 | 44 | switch (shape.type) { 45 | case "structure": 46 | return self.translateStructure(value, shape); 47 | case "map": 48 | return self.translateMap(value, shape); 49 | case "list": 50 | return self.translateList(value, shape); 51 | default: 52 | return self.translateScalar(value, shape); 53 | } 54 | }; 55 | 56 | Translator.prototype.translateStructure = function ( 57 | structure: any, 58 | shape: any, 59 | ): undefined | Doc { 60 | const self: any = this; 61 | 62 | if (structure == null) { 63 | return undefined; 64 | } 65 | 66 | const struct: Doc = {}; 67 | // util.each(structure, function(name, value) { 68 | Object.entries(structure).forEach(([name, value]: [string, any]): void => { 69 | const memberShape: any = shape.members[name]; 70 | 71 | if (memberShape) { 72 | const result: any = self.translate(value, memberShape); 73 | 74 | if (result !== undefined) { 75 | struct[name] = result; 76 | } 77 | } 78 | }); 79 | 80 | return struct; 81 | }; 82 | 83 | Translator.prototype.translateList = function ( 84 | list: any[], 85 | shape: any, 86 | ): undefined | any[] { 87 | const self: any = this; 88 | 89 | if (list == null) { 90 | return undefined; 91 | } 92 | 93 | return list.map((value: any): any => { 94 | const result: any = self.translate(value, shape.member); 95 | 96 | if (result === undefined) { 97 | return null; 98 | } else { 99 | return result; 100 | } 101 | }); 102 | 103 | // var out = []; 104 | // // util.arrayEach(list, function(value) { 105 | // list.forEach(function(value) { 106 | // var result = self.translate(value, shape.member); 107 | // if (result === undefined) out.push(null); 108 | // else out.push(result); 109 | // }); 110 | // return out; 111 | }; 112 | 113 | Translator.prototype.translateMap = function ( 114 | map: Doc | undefined | null, 115 | shape: any, 116 | ): undefined | Doc { 117 | const self: any = this; 118 | 119 | if (!map) { 120 | return undefined; 121 | } 122 | 123 | return Object.entries(map).reduce( 124 | (acc: Doc, [key, value]: [string, any]): Doc => { 125 | const result: any = self.translate(value, shape.value); 126 | 127 | if (result === undefined) { 128 | acc[key] = null; 129 | } else { 130 | acc[key] = result; 131 | } 132 | 133 | return acc; 134 | }, 135 | {}, 136 | ); 137 | 138 | // var out = {}; 139 | // // util.each(map, function(key, value) { 140 | // Object.entries(map).forEach(function([key, value]) { 141 | // var result = self.translate(value, shape.value); 142 | // if (result === undefined) out[key] = null; 143 | // else out[key] = result; 144 | // }); 145 | // return out; 146 | }; 147 | 148 | Translator.prototype.translateScalar = function (value: any, shape: any): any { 149 | return shape.toType(value); 150 | }; 151 | -------------------------------------------------------------------------------- /deps.ts: -------------------------------------------------------------------------------- 1 | export { 2 | encode, 3 | decode, 4 | } from "https://denopkg.com/chiefbiiko/std-encoding@v1.1.0/mod.ts"; 5 | export { get } from "https://denopkg.com/chiefbiiko/get-aws-config@v1.0.1/mod.ts"; 6 | export { hmac } from "https://denopkg.com/chiefbiiko/hmac@v1.0.2/mod.ts"; 7 | export { 8 | toUint8Array as base64ToUint8Array, 9 | fromUint8Array as base64FromUint8Array, 10 | } from "https://deno.land/x/base64@v0.2.0/mod.ts"; 11 | export { sha256 } from "https://denopkg.com/chiefbiiko/sha256@v1.0.2/mod.ts"; 12 | -------------------------------------------------------------------------------- /make_the_docs.ts: -------------------------------------------------------------------------------- 1 | import { OPS } from "./mod.ts"; 2 | import { NO_PARAMS_OPS } from "./client/base_op.ts"; 3 | import { Doc } from "./util.ts"; 4 | 5 | const README0: string = ` 6 | # dynamodb 7 | 8 | ![ci](https://github.com/chiefbiiko/dynamodb/workflows/ci/badge.svg?branch=master) 9 | 10 | DynamoDB client. 11 | 12 | ## Usage 13 | 14 | \`\`\` ts 15 | import { createClient } from "https://denopkg.com/chiefbiiko/dynamodb/mod.ts"; 16 | 17 | // if config/credentials not passed they will be read from the env/fs 18 | const dyno = createClient(); 19 | 20 | // the client has all of DynamoDB's operations as camelCased async methods 21 | const result = await dyno.listTables(); 22 | \`\`\` 23 | 24 | The client config can be omitted entirely when calling \`createClient\`. If that is the case the config will be derived from the environment and filesystem, in that order, using [\`get-aws-config\`](https://github.com/chiefbiiko/get-aws-config). 25 | 26 | Prefer using temporary credentials and a session token. 27 | 28 | ## API 29 | 30 | ### Contents 31 | 32 | 33 | 34 | ### Basics 35 | 36 | \`\`\` ts 37 | /** Generic document. */ 38 | export interface Doc { 39 | [key: string]: any; 40 | } 41 | 42 | /** Generic representation of a DynamoDB client. */ 43 | export interface DynamoDBClient { 44 | describeEndpoints: (options?: Doc) => Promise; 45 | describeLimits: (options?: Doc) => Promise; 46 | listTables: (options?: Doc) => Promise; 47 | scan: ( 48 | params: Doc, 49 | options?: Doc 50 | ) => Promise>; 51 | query: ( 52 | params: Doc, 53 | options?: Doc 54 | ) => Promise>; 55 | [key: string]: (params: Doc, options?: Doc) => Promise; 56 | } 57 | 58 | /** Credentials. */ 59 | export interface Credentials { 60 | accessKeyId: string; // AKIAIOSFODNN7EXAMPLE 61 | secretAccessKey: string; // wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 62 | sessionToken?: string; // somesessiontoken 63 | } 64 | 65 | /** Client configuration. */ 66 | export interface ClientConfig { 67 | credentials?: Credentials | (() => Credentials | Promise); 68 | region?: string; // us-west-2 69 | profile?: string; // default 70 | canonicalUri?: string; // fx /path/to/somewhere 71 | port?: number; // 80 72 | } 73 | 74 | /** Op options. */ 75 | export interface OpOptions { 76 | wrapNumbers?: boolean, // wrap numbers to a special number value type? [false] 77 | convertEmptyValues?: boolean, // convert empty strings and binaries? [false] 78 | translateJSON?: boolean, // translate I/O JSON schemas? [true] 79 | iteratePages?: boolean // if a result is paged, async-iterate it? [true] 80 | } 81 | \`\`\` 82 | 83 | ### Factory 84 | 85 | #### createClient 86 | 87 | ##### \`createClient(conf: ClientConfig): DynamoDBClient\` 88 | 89 | Creates a DynamoDB client. 90 | 91 | ### Ops 92 | 93 | The client supports all DynamoDB operations. Check the linked aws docs for info about parameters of a specific operation. 94 | 95 | 96 | 97 | ## FYI 98 | 99 | Don't want to do all development against the real AWS cloud? 100 | 101 | + [DynamoDB Local](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html) 102 | 103 | + [DynamoDB GUI](https://github.com/Arattian/DynamoDb-GUI-Client) 104 | 105 | ## License 106 | 107 | [MIT](./LICENSE) 108 | 109 | `.trim(); 110 | 111 | const { contents, ops }: Doc = Array.from(OPS) 112 | .map( 113 | (op: string, i: number): Doc => { 114 | const params: string = NO_PARAMS_OPS.has(op) ? "" : "params: Doc, "; 115 | 116 | const rtn: string = `Promise<${ 117 | op === "Scan" || op === "Query" 118 | ? "Doc | AsyncIterableIterator" 119 | : "Doc" 120 | }>`; 121 | 122 | const camel: string = `${op[0].toLowerCase()}${op.slice(1)}`; 123 | 124 | const signature: string = 125 | `${camel}(${params}options?: OpOptions): ${rtn}`; 126 | 127 | const tail: string = i === OPS.size - 1 ? "" : "\n\n"; 128 | 129 | return { 130 | contentItem: ` + [${op}](#${op})${tail}`, 131 | opItem: `#### ${op}\n\n##### \`${signature}\`\n\n` + 132 | `[aws ${op} docs](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_${op}.html)${tail}`, 133 | }; 134 | }, 135 | ) 136 | .reduce( 137 | ( 138 | acc: { contents: string[]; ops: string[] }, 139 | { contentItem, opItem }: Doc, 140 | i: number, 141 | ): Doc => { 142 | acc.contents[3 + i] = contentItem; 143 | acc.ops[i] = opItem; 144 | 145 | return acc; 146 | }, 147 | { 148 | contents: [ 149 | "1. [Basics](#Basics)\n\n", 150 | "2. [Factory](#Factory)\n\n", 151 | "3. [Ops](#Ops)\n\n", 152 | ].concat(new Array(OPS.size)), 153 | ops: new Array(OPS.size), 154 | }, 155 | ); 156 | 157 | const readTheDocs: string = README0.replace( 158 | "", 159 | contents.join(""), 160 | ).replace("", ops.join("")); 161 | 162 | Deno.writeFileSync("./README.md", new TextEncoder().encode(readTheDocs)); 163 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | import { baseOp, deriveConfig } from "./client/mod.ts"; 2 | import { Doc, camelCase } from "./util.ts"; 3 | 4 | /** Convenience export. */ 5 | export type { Doc } from "./util.ts"; 6 | 7 | /** Generic representation of a DynamoDB client. */ 8 | export interface DynamoDBClient { 9 | describeEndpoints: (options?: Doc) => Promise; 10 | describeLimits: (options?: Doc) => Promise; 11 | listTables: (options?: Doc) => Promise; 12 | scan: ( 13 | params: Doc, 14 | options?: Doc, 15 | ) => Promise>; 16 | query: ( 17 | params: Doc, 18 | options?: Doc, 19 | ) => Promise>; 20 | [key: string]: (params: Doc, options?: Doc) => Promise; 21 | } 22 | 23 | /** Credentials. */ 24 | export interface Credentials { 25 | accessKeyId: string; // AKIAIOSFODNN7EXAMPLE 26 | secretAccessKey: string; // wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 27 | sessionToken?: string; // somesessiontoken 28 | } 29 | 30 | /** Client configuration. */ 31 | export interface ClientConfig { 32 | credentials?: Credentials | (() => Credentials | Promise); 33 | region?: string; // us-west-2 34 | profile?: string; // default 35 | canonicalUri?: string; // fx /path/to/somewhere 36 | port?: number; // 80 37 | host?: string; // localhost 38 | } 39 | 40 | /** DynamoDB operations. */ 41 | export const OPS: Set = new Set([ 42 | "BatchGetItem", 43 | "BatchWriteItem", 44 | "CreateBackup", 45 | "CreateGlobalTable", 46 | "CreateTable", 47 | "DeleteBackup", 48 | "DeleteItem", 49 | "DeleteTable", 50 | "DescribeBackup", 51 | "DescribeContinuousBackups", 52 | "DescribeEndpoints", 53 | "DescribeGlobalTable", 54 | "DescribeGlobalTableSettings", 55 | "DescribeLimits", 56 | "DescribeTable", 57 | "DescribeTimeToLive", 58 | "GetItem", 59 | "ListBackups", 60 | "ListGlobalTables", 61 | "ListTables", 62 | "ListTagsOfResource", 63 | "PutItem", 64 | "Query", 65 | "RestoreTableFromBackup", 66 | "RestoreTableToPointInTime", 67 | "Scan", 68 | "TagResource", 69 | "TransactGetItems", 70 | "TransactWriteItems", 71 | "UntagResource", 72 | "UpdateContinuousBackups", 73 | "UpdateGlobalTable", 74 | "UpdateGlobalTableSettings", 75 | "UpdateItem", 76 | "UpdateTable", 77 | "UpdateTimeToLive", 78 | ]); 79 | 80 | /** Creates a DynamoDB client. */ 81 | export function createClient(conf?: ClientConfig): DynamoDBClient { 82 | const _conf: Doc = deriveConfig(conf); 83 | 84 | const dyno: DynamoDBClient = {} as DynamoDBClient; 85 | 86 | for (const op of OPS) { 87 | dyno[camelCase(op)] = baseOp.bind(null, _conf, op); 88 | } 89 | 90 | return dyno; 91 | } 92 | -------------------------------------------------------------------------------- /start_db.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -xEeuo pipefail 4 | 5 | URL="https://s3.eu-central-1.amazonaws.com/dynamodb-local-frankfurt/dynamodb_local_latest.tar.gz" 6 | DIR="./dynamodb_local_latest" 7 | 8 | if [[ ! -d $DIR ]]; then 9 | mkdir $DIR 10 | curl --progress-bar $URL | tar --directory=$DIR -zxf - 11 | fi 12 | 13 | java -D"java.library.path=$DIR/DynamoDBLocal_lib" -jar "$DIR/DynamoDBLocal.jar" -sharedDb & 14 | -------------------------------------------------------------------------------- /test/cloud.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assert, 3 | assertEquals, 4 | assertThrowsAsync, 5 | } from "https://deno.land/std@0.34.0/testing/asserts.ts"; 6 | 7 | import { 8 | ClientConfig, 9 | Credentials, 10 | DynamoDBClient, 11 | createClient, 12 | } from "../mod.ts"; 13 | 14 | import { Doc } from "../util.ts"; 15 | 16 | /** 17 | * This test suite requires an existing DynamoDB table as well as the 18 | * corresponding IAM access permissions. Pass a TABLE_NAME env var. 19 | */ 20 | const TABLE_NAME: string = Deno.env.get("TABLE_NAME")!; 21 | 22 | const dyno: DynamoDBClient = createClient(); 23 | 24 | Deno.test({ 25 | name: "schema translation enabled by default", 26 | async fn(): Promise { 27 | const id: string = "abc"; 28 | 29 | let result: Doc = await dyno.listTables(); 30 | 31 | const friends: string[] = ["djb", "devil", "donkey kong"]; 32 | 33 | result = await dyno.putItem({ 34 | TableName: TABLE_NAME, 35 | Item: { id, friends }, 36 | }); 37 | 38 | result = await dyno.getItem({ 39 | TableName: TABLE_NAME, 40 | Key: { id }, 41 | }); 42 | 43 | assertEquals(result.Item.friends, friends); 44 | }, 45 | }); 46 | 47 | Deno.test({ 48 | name: "opt-in raw queries", 49 | async fn(): Promise { 50 | const id: string = "def"; 51 | 52 | let result: Doc = await dyno.putItem( 53 | { 54 | TableName: TABLE_NAME, 55 | Item: { id: { S: id }, role: { S: "admin" } }, 56 | }, 57 | { translateJSON: false }, 58 | ); 59 | 60 | assertEquals(result, {}); 61 | 62 | result = await dyno.getItem( 63 | { 64 | TableName: TABLE_NAME, 65 | Key: { id: { S: id } }, 66 | }, 67 | { translateJSON: false }, 68 | ); 69 | 70 | assertEquals(result.Item.role.S, "admin"); 71 | }, 72 | }); 73 | 74 | Deno.test({ 75 | name: "batch write items", 76 | async fn(): Promise { 77 | const N: number = 25; 78 | 79 | const params: Doc = { 80 | RequestItems: { [TABLE_NAME]: new Array(N) }, 81 | }; 82 | 83 | for (let i: number = 0; i < N; ++i) { 84 | params.RequestItems[TABLE_NAME][i] = { 85 | PutRequest: { 86 | Item: { 87 | id: String(i), 88 | }, 89 | }, 90 | }; 91 | } 92 | 93 | const result: Doc = await dyno.batchWriteItem(params); 94 | 95 | assertEquals(Object.keys(result.UnprocessedItems).length, 0); 96 | }, 97 | }); 98 | 99 | Deno.test({ 100 | name: "storing a binary value", 101 | async fn(): Promise { 102 | const id: string = "ghi"; 103 | 104 | const buf: Uint8Array = new TextEncoder().encode("deadbeefdeadbeef"); 105 | 106 | let result: Doc = await dyno.putItem({ 107 | TableName: TABLE_NAME, 108 | Item: { id, buf }, 109 | }); 110 | 111 | assertEquals(result, {}); 112 | 113 | result = await dyno.getItem({ 114 | TableName: TABLE_NAME, 115 | Key: { id }, 116 | }); 117 | 118 | assertEquals(result.Item.buf, buf); 119 | }, 120 | }); 121 | 122 | Deno.test({ 123 | name: "deleting an item", 124 | async fn(): Promise { 125 | const id: string = "jkl"; 126 | 127 | let result: Doc = await dyno.putItem({ 128 | TableName: TABLE_NAME, 129 | Item: { id, fraud: "money" }, 130 | }); 131 | 132 | assertEquals(result, {}); 133 | 134 | result = await dyno.deleteItem({ 135 | TableName: TABLE_NAME, 136 | Key: { id }, 137 | }); 138 | 139 | assertEquals(result, {}); 140 | }, 141 | }); 142 | 143 | Deno.test({ 144 | name: "missing table throws a readable error", 145 | async fn(): Promise { 146 | await assertThrowsAsync(async (): Promise => { 147 | await dyno.scan({ TableName: "notatable" }); 148 | }, Error); 149 | }, 150 | }); 151 | 152 | // Deno.test({ 153 | // name: "TODO: having temporary credentials refreshed", 154 | // async fn(): Promise { 155 | // // 2 have temp credentials refreshed pass a (n async) credentials func 156 | // // this will refresh creds after recv a 403 by retrying once 157 | // const conf: ClientConfig = { 158 | // async credentials(): Promise { 159 | // // call STS AssumeRole GetSessionToken or similar... 160 | // return { 161 | // accessKeyId: "freshAccessKeyId", 162 | // secretAccessKey: "freshAccessKey", 163 | // sessionToken: "freshSessionToken" 164 | // }; 165 | // } 166 | // }; 167 | // 168 | // const dyno: DynamoDBClient = createClient(); 169 | // 170 | // await dyno.listTables(); 171 | // } 172 | // }); 173 | 174 | Deno.test({ 175 | name: "conditional put item op", 176 | async fn(): Promise { 177 | const id: string = "remington"; 178 | const caliber: number = 223; 179 | 180 | await dyno.putItem({ TableName: TABLE_NAME, Item: { id, caliber } }); 181 | 182 | let failed: boolean = false; 183 | 184 | try { 185 | // NOTE: fails bc the id already exists & we use a cond expr 186 | await dyno.putItem({ 187 | TableName: TABLE_NAME, 188 | Item: { id, caliber: caliber - 1 }, 189 | ConditionExpression: "attribute_not_exists(id)", 190 | }); 191 | } catch (err) { 192 | failed = true; 193 | assertEquals(err.message, "The conditional request failed"); 194 | } finally { 195 | assert(failed); 196 | } 197 | 198 | const result = await dyno.getItem({ TableName: TABLE_NAME, Key: { id } }); 199 | 200 | assertEquals(result.Item.caliber, caliber); // still caliber 223 201 | }, 202 | }); 203 | -------------------------------------------------------------------------------- /test/cloud_setup.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: Setup for testing dynamodb in the AWS cloud 3 | Parameters: 4 | TableName: 5 | Type: String 6 | Description: Name of the DynamoDB testing table 7 | Default: testing_table 8 | Resources: 9 | Table: 10 | Type: AWS::DynamoDB::Table 11 | Properties: 12 | TableName: !Ref TableName 13 | KeySchema: 14 | - KeyType: HASH 15 | AttributeName: id 16 | AttributeDefinitions: 17 | - AttributeName: id 18 | AttributeType: S 19 | ProvisionedThroughput: 20 | ReadCapacityUnits: 10 21 | WriteCapacityUnits: 10 22 | User: 23 | Type: AWS::IAM::User 24 | Properties: 25 | Policies: 26 | - PolicyName: DynamoDBTestingPolicy 27 | PolicyDocument: 28 | Version: 2012-10-17 29 | Statement: 30 | - Sid: AllowDynamoDBFullAccess 31 | Effect: Allow 32 | Action: dynamodb:* 33 | Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/* 34 | AccessKey: 35 | Type: AWS::IAM::AccessKey 36 | Properties: 37 | Status: Active 38 | UserName: !Ref User 39 | Outputs: 40 | TableName: 41 | Description: Name of the DynamoDB testing table 42 | Value: !Ref TableName 43 | UserArn: 44 | Description: IAM user with full access to DynamoDB 45 | Value: !GetAtt User.Arn 46 | UserAccessKeyId: 47 | Description: Access key id of the IAM user 48 | Value: !Ref AccessKey 49 | UserSecretAccessKey: 50 | Description: Secret access key of the IAM user 51 | Value: !GetAtt AccessKey.SecretAccessKey -------------------------------------------------------------------------------- /test/local.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assert, 3 | assertEquals, 4 | assertThrowsAsync, 5 | } from "https://deno.land/std@0.34.0/testing/asserts.ts"; 6 | 7 | import { ClientConfig, DynamoDBClient, createClient } from "../mod.ts"; 8 | 9 | import { deriveConfig } from "../client/derive_config.ts"; 10 | 11 | import { Doc } from "../util.ts"; 12 | 13 | const TABLE_NAME: string = "testing_table"; 14 | 15 | const conf: ClientConfig = { 16 | credentials: { 17 | accessKeyId: "DynamoDBLocal", 18 | secretAccessKey: "DoesNotDoAnyAuth", 19 | sessionToken: "preferTemporaryCredentials", 20 | }, 21 | region: "local", 22 | port: 8000, // DynamoDB Local's default port 23 | }; 24 | 25 | const dyno: DynamoDBClient = createClient(conf); 26 | 27 | let result: Doc = await dyno.listTables(); 28 | 29 | if (!result.TableNames.includes(TABLE_NAME)) { 30 | await dyno.createTable({ 31 | TableName: TABLE_NAME, 32 | KeySchema: [{ KeyType: "HASH", AttributeName: "id" }], 33 | AttributeDefinitions: [{ AttributeName: "id", AttributeType: "S" }], 34 | ProvisionedThroughput: { ReadCapacityUnits: 10, WriteCapacityUnits: 10 }, 35 | }); 36 | } 37 | 38 | Deno.test({ 39 | name: "sets specified dynamodb host", 40 | async fn(): Promise { 41 | const _conf: ClientConfig = { 42 | credentials: { 43 | accessKeyId: "DynamoDBLocal", 44 | secretAccessKey: "DoesNotDoAnyAuth", 45 | sessionToken: "preferTemporaryCredentials", 46 | }, 47 | region: "local", 48 | port: 8000, // DynamoDB Local's default port 49 | host: "host.docker.internal", // Specific DynamoDB host 50 | }; 51 | 52 | let result: Doc = deriveConfig(_conf); 53 | 54 | assertEquals(_conf.host, result.host); 55 | }, 56 | }); 57 | 58 | Deno.test({ 59 | name: "schema translation enabled by default", 60 | async fn(): Promise { 61 | const id: string = "abc"; 62 | 63 | const friends: string[] = ["djb", "devil", "donkey kong"]; 64 | 65 | let result: Doc = await dyno.putItem({ 66 | TableName: TABLE_NAME, 67 | Item: { id, friends }, 68 | }); 69 | 70 | result = await dyno.getItem({ 71 | TableName: TABLE_NAME, 72 | Key: { id }, 73 | }); 74 | 75 | assertEquals(result.Item.friends, friends); 76 | }, 77 | }); 78 | 79 | Deno.test({ 80 | name: "opt-in raw queries", 81 | async fn(): Promise { 82 | const id: string = "def"; 83 | 84 | let result: Doc = await dyno.putItem( 85 | { 86 | TableName: TABLE_NAME, 87 | Item: { id: { S: id }, role: { S: "admin" } }, 88 | }, 89 | { translateJSON: false }, 90 | ); 91 | 92 | assertEquals(result, {}); 93 | 94 | result = await dyno.getItem( 95 | { 96 | TableName: TABLE_NAME, 97 | Key: { id: { S: id } }, 98 | }, 99 | { translateJSON: false }, 100 | ); 101 | 102 | assertEquals(result.Item.role.S, "admin"); 103 | }, 104 | }); 105 | 106 | Deno.test({ 107 | name: "batch write items", 108 | async fn(): Promise { 109 | const N: number = 25; 110 | 111 | const params: Doc = { 112 | RequestItems: { [TABLE_NAME]: new Array(N) }, 113 | }; 114 | 115 | for (let i: number = 0; i < N; ++i) { 116 | params.RequestItems[TABLE_NAME][i] = { 117 | PutRequest: { 118 | Item: { 119 | id: String(i), 120 | }, 121 | }, 122 | }; 123 | } 124 | 125 | const result: Doc = await dyno.batchWriteItem(params); 126 | 127 | assertEquals(Object.keys(result.UnprocessedItems).length, 0); 128 | }, 129 | }); 130 | 131 | Deno.test({ 132 | name: "storing a binary value", 133 | async fn(): Promise { 134 | const id: string = "ghi"; 135 | 136 | const buf: Uint8Array = new TextEncoder().encode("deadbeefdeadbeef"); 137 | 138 | let result: Doc = await dyno.putItem({ 139 | TableName: TABLE_NAME, 140 | Item: { id, buf }, 141 | }); 142 | 143 | assertEquals(result, {}); 144 | 145 | result = await dyno.getItem({ 146 | TableName: TABLE_NAME, 147 | Key: { id }, 148 | }); 149 | 150 | assertEquals(result.Item.buf, buf); 151 | }, 152 | }); 153 | 154 | Deno.test({ 155 | name: "deleting an item", 156 | async fn(): Promise { 157 | const id: string = "jkl"; 158 | 159 | let result: Doc = await dyno.putItem({ 160 | TableName: TABLE_NAME, 161 | Item: { id, fraud: "money" }, 162 | }); 163 | 164 | assertEquals(result, {}); 165 | 166 | result = await dyno.deleteItem({ 167 | TableName: TABLE_NAME, 168 | Key: { id }, 169 | }); 170 | 171 | assertEquals(result, {}); 172 | }, 173 | }); 174 | 175 | Deno.test({ 176 | name: "missing table throws a readable error", 177 | async fn(): Promise { 178 | await assertThrowsAsync( 179 | async (): Promise => { 180 | await dyno.scan({ TableName: "notatable" }); 181 | }, 182 | Error, 183 | "Cannot do operations on a non-existent table", 184 | ); 185 | }, 186 | }); 187 | 188 | Deno.test({ 189 | name: "ops that receive paged results return an async iterator by default", 190 | async fn(): Promise { 191 | const n: number = 25; 192 | const N: number = 20 * n; 193 | 194 | function batch(_: null, i: number): Promise { 195 | const trash: Uint8Array = new Uint8Array(4096); 196 | 197 | const params: Doc = { 198 | RequestItems: { [TABLE_NAME]: new Array(n) }, 199 | }; 200 | 201 | for (let j: number = 0; j < n; ++j) { 202 | params.RequestItems[TABLE_NAME][j] = { 203 | PutRequest: { 204 | Item: { 205 | id: `batch${i} item${j}`, 206 | trash, 207 | }, 208 | }, 209 | }; 210 | } 211 | 212 | return dyno.batchWriteItem(params); 213 | } 214 | 215 | // 20 * 25 items each gt 4096 bytes 216 | const batches: Promise[] = new Array(20).fill(null).map(batch); 217 | 218 | const results: Doc[] = await Promise.all(batches); 219 | 220 | const unprocessed: number = results.reduce( 221 | (acc: number, result: Doc): number => 222 | acc + Object.keys(result.UnprocessedItems).length, 223 | 0, 224 | ); 225 | 226 | assertEquals(unprocessed, 0); 227 | 228 | const ait: any = await dyno.scan({ TableName: TABLE_NAME }); 229 | 230 | let pages: number = 0; 231 | let items: number = 0; 232 | 233 | for await (const page of ait) { 234 | assert(Array.isArray(page.Items)); 235 | assert(page.Items.length > 0); 236 | 237 | ++pages; 238 | items += page.Count; 239 | } 240 | 241 | assert(pages >= 2); 242 | 243 | assert(items > N); 244 | }, 245 | }); 246 | 247 | Deno.test({ 248 | name: "handling pagination manually", 249 | async fn(): Promise { 250 | // only fetching 1 page - not async iterating 251 | const result: Doc = await dyno.scan( 252 | { TableName: TABLE_NAME }, 253 | { iteratePages: false }, 254 | ); 255 | 256 | assert(Array.isArray(result.Items)); 257 | assert(result.Items.length > 0); 258 | assert(!!result.LastEvaluatedKey); 259 | }, 260 | }); 261 | 262 | Deno.test({ 263 | name: "conditional put item op", 264 | async fn(): Promise { 265 | const id: string = "remington"; 266 | const caliber: number = 223; 267 | 268 | await dyno.putItem({ TableName: TABLE_NAME, Item: { id, caliber } }); 269 | 270 | let failed: boolean = false; 271 | 272 | try { 273 | // NOTE: fails bc the id already exists & we use a cond expr 274 | await dyno.putItem({ 275 | TableName: TABLE_NAME, 276 | Item: { id, caliber: caliber - 1 }, 277 | ConditionExpression: "attribute_not_exists(id)", 278 | }); 279 | } catch (err) { 280 | failed = true; 281 | assertEquals(err.message, "The conditional request failed"); 282 | } finally { 283 | assert(failed); 284 | } 285 | 286 | const result = await dyno.getItem({ TableName: TABLE_NAME, Key: { id } }); 287 | 288 | assertEquals(result.Item.caliber, caliber); // still caliber 223 289 | }, 290 | }); 291 | -------------------------------------------------------------------------------- /test/signv4.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.34.0/testing/asserts.ts"; 2 | 3 | import { encode } from "../deps.ts"; 4 | import { awsSignatureV4, kdf } from "../client/aws_signature_v4.ts"; 5 | 6 | Deno.test({ 7 | name: "aws signature v4 flow", 8 | fn(): void { 9 | const expectedSignature: string = 10 | "31fac5ed29db737fbcafac527470ca6d9283283197c5e6e94ea40ddcec14a9c1"; 11 | 12 | const key: Uint8Array = kdf( 13 | "secret", 14 | "20310430", 15 | "region", 16 | "dynamodb", 17 | "utf8", 18 | ) as Uint8Array; 19 | 20 | const msg: Uint8Array = encode( 21 | "AWS4-HMAC-SHA256\n20310430T201613Z\n20310430/region/dynamodb/aws4_request\n4be20e7bf75dc6c7e93873b5f49096771729b8a28f0c62010db431fea79220ef", 22 | "utf8", 23 | ); 24 | 25 | const actualSignature: string = awsSignatureV4(key, msg, "hex") as string; 26 | 27 | assertEquals(actualSignature, expectedSignature); 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /util.ts: -------------------------------------------------------------------------------- 1 | const ANY_BUT_DIGITS: RegExp = /[^\d]/g; 2 | const ANY_BUT_DIGITS_T: RegExp = /[^\dT]/g; 3 | 4 | /** Generic document. */ 5 | export interface Doc { 6 | [key: string]: any; 7 | } 8 | 9 | /** noop. */ 10 | export function noop(..._: any[]): void {} 11 | 12 | /** camelCase */ 13 | export function camelCase(text: string): string { 14 | return `${text[0].toLowerCase()}${text.slice(1)}`; 15 | } 16 | 17 | /** Defines a property. */ 18 | export function property( 19 | obj: any, 20 | name: string, 21 | value: any, 22 | enumerable?: boolean, 23 | isValue?: boolean, 24 | ): void { 25 | const opts: Doc = { 26 | configurable: true, 27 | enumerable: typeof enumerable === "boolean" ? enumerable : true, 28 | }; 29 | 30 | if (typeof value === "function" && !isValue) { 31 | opts.get = value; 32 | } else { 33 | opts.value = value; 34 | opts.writable = true; 35 | } 36 | 37 | Object.defineProperty(obj, name, opts); 38 | } 39 | 40 | /** Defines a memoized property. */ 41 | export function memoizedProperty( 42 | obj: any, 43 | name: string, 44 | get: () => any, 45 | enumerable?: boolean, 46 | ): void { 47 | let cachedValue: any = null; 48 | 49 | // build enumerable attribute for each value with lazy accessor. 50 | property( 51 | obj, 52 | name, 53 | (): void => { 54 | if (cachedValue === null) { 55 | cachedValue = get(); 56 | } 57 | 58 | return cachedValue; 59 | }, 60 | enumerable, 61 | ); 62 | } 63 | 64 | /** aws typeof impl. */ 65 | export function typeOf(data: any): string { 66 | if (data === null && typeof data === "object") { 67 | return "null"; 68 | } else if (data !== undefined && isBinary(data)) { 69 | return "Binary"; 70 | } else if (data !== undefined && data.constructor) { 71 | return data.wrapperName || data.constructor.name; 72 | } else if (data !== undefined && typeof data === "object") { 73 | // this object is the result of Object.create(null), hence the absence of a 74 | // defined constructor 75 | return "Object"; 76 | } else { 77 | return "undefined"; 78 | } 79 | } 80 | 81 | /** Is given value a binary type? */ 82 | function isBinary(data: any): boolean { 83 | const types: string[] = [ 84 | "Buffer", 85 | "File", 86 | "Blob", 87 | "ArrayBuffer", 88 | "DataView", 89 | "Int8Array", 90 | "Uint8Array", 91 | "Uint8ClampedArray", 92 | "Int16Array", 93 | "Uint16Array", 94 | "Int32Array", 95 | "Uint32Array", 96 | "Float32Array", 97 | "Float64Array", 98 | ]; 99 | 100 | // if (util.isNode()) { 101 | // var Stream = util.stream.Stream; 102 | // if (util.Buffer.isBuffer(data) || data instanceof Stream) { 103 | // return true; 104 | // } 105 | // } 106 | 107 | // var isType = (obj, type) => Object.prototype.toString.call(obj) === '[object ' + type + ']'; 108 | 109 | if (data !== undefined && data.constructor) { 110 | // console.error(">>>>>>>>>> isBinary data", data) 111 | // for (let i: number = 0; i < types.length; i++) { 112 | // 113 | // // if (util.isType(data, types[i])) return true; 114 | // if (data.constructor.name === types[i]) { 115 | // // console.error(">>>> isBinary TRUE", data) 116 | // return true; 117 | // } 118 | // // if(isType(data, types[i])) { 119 | // // return true 120 | // // } 121 | // 122 | // } 123 | return types.some( 124 | (type: string): boolean => data.constructor.name === type, 125 | ); 126 | } 127 | 128 | return false; 129 | } 130 | 131 | /** Mapping member to set type. */ 132 | const memberTypeToSetType: Doc = { 133 | String: "String", 134 | Number: "Number", 135 | NumberValue: "Number", 136 | Binary: "Binary", 137 | }; 138 | 139 | /** DynamoDB set type. */ 140 | export class DynamoDBSet { 141 | readonly wrappername: string = "Set"; 142 | readonly values: any[] = []; 143 | readonly type: string = ""; 144 | 145 | /** Creates a dynamodb set. */ 146 | constructor(list: any[] = [], options: Doc = {}) { 147 | Array.prototype.push.apply(this.values, list); 148 | 149 | this.type = memberTypeToSetType[typeOf(this.values[0])]; 150 | 151 | if (!this.type) { 152 | throw new Error( 153 | "DynamoDB sets can only contain string, number, or binary values", 154 | ); 155 | } 156 | 157 | if (options.validate) { 158 | for (const value of this.values) { 159 | if (memberTypeToSetType[typeOf(value)] !== this.type) { 160 | throw new Error(`${this.type} Set contains ${typeOf(value)} value`); 161 | } 162 | } 163 | } 164 | } 165 | 166 | /** Renders the underlying values only when converting to JSON. */ 167 | toJSON(): any[] { 168 | return this.values; 169 | } 170 | } 171 | 172 | /** 173 | * An object recognizable as a numeric value that stores the underlying number 174 | * as a string. 175 | * 176 | * Intended to be a deserialization target for the DynamoDB Doc Client when 177 | * the `wrapNumbers` flag is set. This allows for numeric values that lose 178 | * precision when converted to JavaScript's `number` type. 179 | */ 180 | export class DynamoDBNumberValue { 181 | readonly wrapperName: string = "NumberValue"; 182 | readonly value: string; 183 | 184 | /** Creates a dynamodb number value. */ 185 | constructor(value: number | string) { 186 | this.value = value.toString(); 187 | } 188 | 189 | /** Renders the underlying value as a number when converting to JSON. */ 190 | toJSON(): number { 191 | return this.toNumber(); 192 | } 193 | 194 | /** Converts the underlying value to a JavaScript number. */ 195 | toNumber(): number { 196 | return Number(this.value); 197 | } 198 | 199 | /** Returns a decimal string representing the number value. */ 200 | toString(): string { 201 | return this.value; 202 | } 203 | } 204 | 205 | /** Date format helpers. */ 206 | export const date: Doc = { 207 | /** Date stamp format as expected by awsSignatureV4KDF. */ 208 | DATE_STAMP_REGEX: /^\d{8}$/, 209 | amz(date: Date): string { 210 | return `${ 211 | date 212 | .toISOString() 213 | .slice(0, 19) 214 | .replace(ANY_BUT_DIGITS_T, "") 215 | }Z`; 216 | }, 217 | dateStamp(date: Date): string { 218 | return date 219 | .toISOString() 220 | .slice(0, 10) 221 | .replace(ANY_BUT_DIGITS, ""); 222 | }, 223 | from(date: number | string | Date): Date { 224 | if (typeof date === "number") { 225 | return new Date(date * 1000); // unix timestamp 226 | } else { 227 | return new Date(date as any); 228 | } 229 | }, 230 | iso8601(date: Date = new Date()): string { 231 | return date.toISOString().replace(/\.\d{3}Z$/, "Z"); 232 | }, 233 | rfc822(date: Date = new Date()): string { 234 | return date.toUTCString(); 235 | }, 236 | unixTimestamp(date: Date = new Date()): number { 237 | return date.getTime() / 1000; 238 | }, 239 | /** Valid formats are: iso8601, rfc822, unixTimestamp, dateStamp, amz. */ 240 | format(date: Date, formatter: string = "iso8601"): number | string { 241 | return this[formatter](this.from(date)); 242 | }, 243 | parseTimestamp(value: number | string): Date { 244 | if (typeof value === "number") { 245 | // unix timestamp (number) 246 | return new Date(value * 1000); 247 | } else if (value.match(/^\d+$/)) { 248 | // unix timestamp 249 | return new Date(Number(value) * 1000); 250 | } else if (value.match(/^\d{4}/)) { 251 | // iso8601 252 | return new Date(value); 253 | } else if (value.match(/^\w{3},/)) { 254 | // rfc822 255 | return new Date(value); 256 | } else { 257 | throw new Error(`unhandled timestamp format: ${value}`); 258 | } 259 | }, 260 | }; 261 | --------------------------------------------------------------------------------