diff --git a/package-lock.json b/package-lock.json index bd600d8..94a49c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -931,6 +931,12 @@ "picomatch": "^2.0.4" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1002,19 +1008,26 @@ "integrity": "sha1-0WkB0QzOxZUWwZe5zNiTBom4E7Q=" }, "aws-sdk": { - "version": "2.568.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.568.0.tgz", - "integrity": "sha512-jPvhiJV2iLyWbJJDM01gvUCzeChWUeRMkIr6dsHu+leH2QnzvGNunTwMGculKE1jouXatajZEoA9bdqfosranw==", - "requires": { - "buffer": "^4.9.1", - "events": "^1.1.1", - "ieee754": "^1.1.13", - "jmespath": "^0.15.0", - "querystring": "^0.2.0", - "sax": "^1.2.1", - "url": "^0.10.3", - "uuid": "^3.3.2", - "xml2js": "^0.4.19" + "version": "2.794.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.794.0.tgz", + "integrity": "sha512-Qqz8v0WfeGveaZTPo9+52nNUep/CTuo18OcdCwF4WrnNBv7bAxExUOwN9XkqhoxLjBDk/LuMmHGhOXRljFQgRw==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } } }, "aws-xray-sdk-core": { @@ -1067,9 +1080,9 @@ "dev": true }, "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "before-after-hook": { "version": "2.1.0", @@ -1141,6 +1154,12 @@ "isarray": "^1.0.0" } }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -1666,6 +1685,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -1763,6 +1783,7 @@ "version": "1.16.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", + "dev": true, "requires": { "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", @@ -1780,6 +1801,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -1957,7 +1979,8 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "get-caller-file": { "version": "2.0.5", @@ -2119,6 +2142,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -2132,7 +2156,8 @@ "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true }, "he": { "version": "1.2.0", @@ -2429,12 +2454,14 @@ "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true }, "is-date-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true }, "is-directory": { "version": "0.3.1", @@ -2494,6 +2521,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, "requires": { "has": "^1.0.1" } @@ -2508,6 +2536,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, "requires": { "has-symbols": "^1.0.0" } @@ -2780,6 +2809,12 @@ "integrity": "sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==", "dev": true }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "map-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", @@ -7115,12 +7150,14 @@ "object-inspect": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==" + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object.assign": { "version": "4.1.0", @@ -7138,6 +7175,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, "requires": { "define-properties": "^1.1.2", "es-abstract": "^1.5.1" @@ -7633,9 +7671,9 @@ "dev": true }, "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" }, "semantic-release": { "version": "15.14.0", @@ -7888,6 +7926,16 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "spawn-error-forwarder": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", @@ -7979,6 +8027,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, "requires": { "define-properties": "^1.1.3", "function-bind": "^1.1.1" @@ -7988,6 +8037,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, "requires": { "define-properties": "^1.1.3", "function-bind": "^1.1.1" @@ -8142,6 +8192,27 @@ "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", "dev": true }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -8260,15 +8331,6 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" - } - }, "uuid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", @@ -8484,19 +8546,18 @@ "dev": true }, "xml2js": { - "version": "0.4.22", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.22.tgz", - "integrity": "sha512-MWTbxAQqclRSTnehWWe5nMKzI3VmJ8ltiJEco8akcC6j3miOhjjfzKum5sId+CWhfxdOs/1xauYr8/ZDBtQiRw==", + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", "requires": { "sax": ">=0.6.0", - "util.promisify": "~1.0.0", - "xmlbuilder": "~11.0.0" + "xmlbuilder": "~9.0.1" } }, "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" }, "xtend": { "version": "4.0.2", @@ -8820,6 +8881,12 @@ } } } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } } } diff --git a/package.json b/package.json index 3910a4b..83a25c0 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,7 @@ "prebuild": "check-engine && rm -rf dst", "build": "tsc", "postbuild": "cd src && find . -name '*.json' -type f -exec cp {} ../dst/{} \\; && cd ..", - "pretest": "npm run build -- -p ./tsconfig.test.json", - "test": "env AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=mock AWS_SECRET_ACCESS_KEY=mock mocha -t 20000 dst/**/__test__/**/*.js", + "test": "env AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=mock AWS_SECRET_ACCESS_KEY=moc mocha -r ./node_modules/ts-node/register --exit -t 20000 src/**/__test__/**/*.ts", "test:local": "env DYNAMO_TYPES_ENDPOINT=http://127.0.0.1:8000 npm test", "prepublishOnly": "npm run build", "lint": "tslint -c tslint.json 'src/**/*.ts'" @@ -42,6 +41,7 @@ "faker": "4.1.0", "husky": "4.3.0", "mocha": "7.2.0", + "ts-node": "^9.0.0", "tslint": "5.20.1", "typescript": "3.9.7" }, diff --git a/src/__test__/table_spec.ts b/src/__test__/table_spec.ts index e6ea68e..c0715eb 100644 --- a/src/__test__/table_spec.ts +++ b/src/__test__/table_spec.ts @@ -5,6 +5,7 @@ import { Query, Table, } from "../index"; +import { NumberSet, StringSet } from "../metadata/attribute"; import { toJS } from "./helper"; describe("Table", () => { @@ -25,6 +26,12 @@ describe("Table", () => { @Decorator.Attribute() public title: string; + @Decorator.Attribute() + public followers: StringSet; + + @Decorator.Attribute() + public tags: NumberSet; + @Decorator.Attribute({ timeToLive: true }) public expiresAt: number; } @@ -48,6 +55,8 @@ describe("Table", () => { const card = new Card(); card.id = 10; card.title = "100"; + card.followers = new StringSet(["U1", "U2", "U3"]); + card.tags = new NumberSet([1, 2, 3]); await card.save(); @@ -55,6 +64,10 @@ describe("Table", () => { expect(reloadedCard).to.be.instanceof(Card); expect(reloadedCard!.id).to.eq(10); expect(reloadedCard!.title).to.eq("100"); + expect(reloadedCard!.followers).to.be.instanceOf(StringSet); + expect(reloadedCard!.followers.toArray()).to.be.deep.eq(["U1", "U2", "U3"]); + expect(reloadedCard!.tags).to.be.instanceOf(NumberSet); + expect(reloadedCard!.tags.toArray()).to.be.deep.eq([1, 2, 3]); }); it("should works with TTL", async () => { diff --git a/src/__test__/transaction_spec.ts b/src/__test__/transaction_spec.ts new file mode 100644 index 0000000..20c7c79 --- /dev/null +++ b/src/__test__/transaction_spec.ts @@ -0,0 +1,110 @@ +import { expect } from "chai"; + +import { + Decorator, + Query, + Table, + TransactionWrite, +} from "../index"; +import { NumberSet, StringSet } from "../metadata/attribute"; +import { toJS } from "./helper"; + +describe("Table", () => { + @Decorator.Table({ name: `prod-Card${Math.random()}` }) + class Card extends Table { + @Decorator.FullPrimaryKey("id", "title") + public static readonly primaryKey: Query.FullPrimaryKey; + + @Decorator.HashGlobalSecondaryIndex("title") + public static readonly titleIndex: Query.HashGlobalSecondaryIndex; + + @Decorator.Writer() + public static readonly writer: Query.Writer; + + @Decorator.Attribute() + public id: number; + + @Decorator.Attribute() + public title: string; + + @Decorator.Attribute() + public followers: StringSet; + + @Decorator.Attribute() + public tags: NumberSet; + + @Decorator.Attribute({ timeToLive: true }) + public expiresAt: number; + + @Decorator.Attribute() + public content: string; + + @Decorator.Attribute() + public author: string; + } + + beforeEach(async () => { + await Card.createTable(); + }); + afterEach(async () => { + await Card.dropTable(); + }); + + it("should do transaction write", async () => { + const transaction = new TransactionWrite(); + + const card1 = new Card(); + card1.id = 1; + card1.title = "100"; + card1.followers = new StringSet(["U1", "U2", "U3"]); + card1.tags = new NumberSet([1, 2, 3]); + await card1.save(); + + const card2 = new Card(); + card2.id = 2; + card2.title = "100"; + card2.followers = new StringSet(["U1", "U2", "U3"]); + card2.tags = new NumberSet([1, 2, 3]); + await card2.save(); + + const card3 = new Card(); + card3.id = 3; + card3.title = "100"; + card3.followers = new StringSet(["U1", "U2", "U3"]); + card3.tags = new NumberSet([1, 2, 3]); + await card3.save(); + + const card4 = new Card(); + card4.id = 4; + card4.title = "100"; + card4.followers = new StringSet(["U1", "U2", "U3"]); + card4.tags = new NumberSet([1, 2, 3]); + await card4.save(); + + card1.content = "content"; + card1.transactionSave(transaction); + + Card.primaryKey.transactionUpdate(transaction, card2.id, card2.title, { + author: ["PUT", "author"] + }); + + Card.primaryKey.transactionDelete(transaction, card3.id, card3.title); + + card4.transactionDelete(transaction); + await transaction.commit(); + + const card1Reloaded = await Card.primaryKey.get(1, "100"); + const card2Reloaded = await Card.primaryKey.get(2, "100"); + const card3Reloaded = await Card.primaryKey.get(3, "100"); + const card4Reloaded = await Card.primaryKey.get(4, "100"); + + expect(card1Reloaded!.content).to.be.eq("content"); + expect(card2Reloaded!.author).to.be.eq("author"); + // tslint:disable-next-line: no-unused-expression + expect(card3Reloaded).to.be.null; + // tslint:disable-next-line: no-unused-expression + expect(card4Reloaded).to.be.null; + + }); + +}); diff --git a/src/codec/__test__/attribute_value_spec.ts b/src/codec/__test__/attribute_value_spec.ts index 0aea274..d8751ca 100644 --- a/src/codec/__test__/attribute_value_spec.ts +++ b/src/codec/__test__/attribute_value_spec.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; import { Attribute } from "../../metadata"; +import { StringSet } from "../../metadata/attribute"; import * as AttributeValue from "../attribute_value"; describe("AttributeValue.parse", () => { @@ -58,4 +59,34 @@ describe("AttributeValue.parse", () => { }, type: Attribute.Type.Map}); }); + it("should parse string set", () => { + const beforeParse = { + SS: [ + "test", + "string", + "set", + ], + }; + + const afterParse = AttributeValue.parse(beforeParse); + + expect(afterParse.type).to.eq(Attribute.Type.StringSet); + expect(afterParse.value).to.be.deep.eq(["test", "string", "set"]); + }); + + it("should parse number set", () => { + const beforeParse = { + NS: [ + "1", + "0.5", + "0.9", + ], + }; + + const afterParse = AttributeValue.parse(beforeParse); + + expect(afterParse.type).to.eq(Attribute.Type.NumberSet); + expect(afterParse.value).to.be.deep.eq(["1", "0.5", "0.9"]); + }); + }); diff --git a/src/codec/attribute_value.ts b/src/codec/attribute_value.ts index 638b486..80ac96f 100644 --- a/src/codec/attribute_value.ts +++ b/src/codec/attribute_value.ts @@ -23,6 +23,10 @@ export function parse(v: DynamoDB.AttributeValue) { return { value: Number(v.N), type: Attribute.Type.Number }; } else if (v.S !== undefined) { return { value: v.S, type: Attribute.Type.String }; + } else if (v.SS !== undefined) { + return { value: v.SS, type: Attribute.Type.StringSet }; + } else if (v.NS !== undefined) { + return { value: v.NS, type: Attribute.Type.NumberSet }; } else if (v.NULL !== undefined) { return { value: null, type: Attribute.Type.Null }; } else { diff --git a/src/codec/deserialize.ts b/src/codec/deserialize.ts index 033d716..eba243b 100644 --- a/src/codec/deserialize.ts +++ b/src/codec/deserialize.ts @@ -16,6 +16,7 @@ export function deserialize( tableClass.metadata.attributes.forEach((attributeMetadata) => { const attributeValue = dynamoAttributes[attributeMetadata.name]; + if (!dynamoAttributes.hasOwnProperty(attributeMetadata.name)) { // attribute is defined but not provided by DynamoDB // raise error but maybe later? diff --git a/src/codec/serialize.ts b/src/codec/serialize.ts index b6f124c..8c2177e 100644 --- a/src/codec/serialize.ts +++ b/src/codec/serialize.ts @@ -11,7 +11,7 @@ export function serialize(tableClass: ITable, record: T): { const res: { [key: string]: any } = {}; tableClass.metadata.attributes.forEach((attributeMetadata) => { - const attr = record.getAttribute(attributeMetadata.name); + const attr = record.getAttribute(attributeMetadata.name, tableClass.metadata); if (attr !== undefined) { res[attributeMetadata.name] = attr; } diff --git a/src/decorator/attribute.ts b/src/decorator/attribute.ts index 3f4527f..f7c5a97 100644 --- a/src/decorator/attribute.ts +++ b/src/decorator/attribute.ts @@ -1,6 +1,7 @@ import "reflect-metadata"; import { Attribute as AttributeMetadata } from "../metadata"; +import { NumberSet, StringSet } from "../metadata/attribute"; import { ITable, Table } from "../table"; // Table Decorator @@ -31,6 +32,10 @@ function _nativeTypeToAttributeMetadataType(nativeType: any) { return AttributeMetadata.Type.Array; } else if (nativeType === Object) { return AttributeMetadata.Type.Map; + } else if (nativeType === StringSet) { + return AttributeMetadata.Type.StringSet; + } else if (nativeType === NumberSet) { + return AttributeMetadata.Type.NumberSet; } else { throw new Error(`Unsupported type ${nativeType}`); } diff --git a/src/index.ts b/src/index.ts index e8208d2..44e966c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import * as Decorator from "./decorator"; import * as Metadata from "./metadata"; import * as Query from "./query"; import { Table } from "./table"; +import { TransactionWrite } from "./transaction"; export { Query, @@ -16,4 +17,6 @@ export { Metadata, Connection, Table, + TransactionWrite, + }; diff --git a/src/metadata/attribute.ts b/src/metadata/attribute.ts index 18ffc32..25d5469 100644 --- a/src/metadata/attribute.ts +++ b/src/metadata/attribute.ts @@ -9,6 +9,32 @@ export const enum Type { // StringArray = "SS", Array = "L", Map = "M", + StringSet = "SS", + NumberSet = "NS", +} + +export class StringSet extends Set { + public toJSON() { + return { + [Type.StringSet]: Array.from(this) + }; + } + + public toArray() { + return Array.from(this); + } +} +// tslint:disable-next-line: max-classes-per-file +export class NumberSet extends Set { + public toJSON() { + return { + [Type.NumberSet]: Array.from(this).map((value) => value.toString()) + }; + } + + public toArray() { + return Array.from(this); + } } export interface Metadata { diff --git a/src/query/__test__/full_primary_key_spec.ts b/src/query/__test__/full_primary_key_spec.ts index 1cf7ab7..e7d3299 100644 --- a/src/query/__test__/full_primary_key_spec.ts +++ b/src/query/__test__/full_primary_key_spec.ts @@ -1,3 +1,4 @@ +import { DynamoDB } from "aws-sdk"; import { expect } from "chai"; import { toJS } from "../../__test__/helper"; @@ -14,6 +15,7 @@ import { Table as TableDecorator, } from "../../decorator"; +import { NumberSet, StringSet } from "../../metadata/attribute"; import * as Query from "../index"; describe("FullPrimaryKey", () => { @@ -31,6 +33,12 @@ describe("FullPrimaryKey", () => { @AttributeDecorator() public count: number; + + @AttributeDecorator() + public followers: StringSet; + + @AttributeDecorator() + public tags: NumberSet; } let primaryKey: FullPrimaryKey; @@ -253,6 +261,29 @@ describe("FullPrimaryKey", () => { card = await primaryKey.get(10, "abc"); expect(card!.count).to.eq(3); + + await primaryKey.update(10, "abc", { followers: ["PUT", new StringSet(["U123", "U456"])] }); + + card = await primaryKey.get(10, "abc"); + expect(card!.followers.toArray()).to.deep.eq(["U123", "U456"]); + + await primaryKey.update(10, "abc", { followers: ["DELETE", new StringSet(["U123"])]}); + card = await primaryKey.get(10, "abc"); + expect(card!.followers.toArray()).to.deep.eq(["U456"]); + + await primaryKey.update(10, "abc", { tags: ["PUT", new NumberSet([1, 2, 3])]}); + card = await primaryKey.get(10, "abc"); + expect(card!.tags.toArray()).to.deep.eq([1, 2, 3]); + + await primaryKey.update(10, "abc", { tags: ["DELETE", new NumberSet([2])]}); + card = await primaryKey.get(10, "abc"); + expect(card!.tags.toArray()).to.deep.eq([1, 3]); + + await primaryKey.update(10, "abc", { followers: ["REMOVE"]}); + + card = await primaryKey.get(10, "abc"); + // tslint:disable-next-line: no-unused-expression + expect(card!.followers).to.be.undefined; }); context("when condition check was failed", () => { diff --git a/src/query/expressions/transformers/update.ts b/src/query/expressions/transformers/update.ts index f469d82..cf47647 100644 --- a/src/query/expressions/transformers/update.ts +++ b/src/query/expressions/transformers/update.ts @@ -1,6 +1,8 @@ import * as _ from "lodash"; +import Config from "../../../config"; import * as Metadata from "../../../metadata"; +import { NumberSet, StringSet } from "../../../metadata/attribute"; import { UpdateAction, UpdateChanges } from "../update"; const UPDATE_NAME_REF_PREFIX = "#uk"; @@ -9,7 +11,9 @@ const UPDATE_VALUE_REF_PREFIX = ":uv"; const ACTION_TOKEN_MAP = new Map([ ["PUT", "SET"], ["ADD", "ADD"], + ["APPEND", "SET"], ["DELETE", "DELETE"], + ["REMOVE", "REMOVE"], ]); export function buildUpdate( @@ -18,6 +22,7 @@ export function buildUpdate( ) { const keyRef = new Map(); const valueRef = new Map(); + const client = metadata.connection.documentClient; const keyNameCache = new Map(metadata.attributes.map((attr) => [attr.propertyName, attr.name])); @@ -27,11 +32,19 @@ export function buildUpdate( .groupBy((change) => change.action) .map((groupedChanges, action: UpdateAction) => { const actions = groupedChanges.map((change) => { - const keyPath = `${UPDATE_NAME_REF_PREFIX}${keyRef.size}`; - keyRef.set(keyPath, change.name!); + const keyPath = `${UPDATE_NAME_REF_PREFIX}${keyRef.size}`; const valuePath = `${UPDATE_VALUE_REF_PREFIX}${valueRef.size}`; - valueRef.set(valuePath, change.value); + + if (action !== "REMOVE") { + keyRef.set(keyPath, change.name!); + + if (change.value instanceof StringSet || change.value instanceof NumberSet) { + valueRef.set(valuePath, client.createSet(change.value.toArray())); + } else { + valueRef.set(valuePath, change.value); + } + } switch (action) { case "PUT": @@ -39,6 +52,12 @@ export function buildUpdate( case "ADD": case "DELETE": return `${keyPath} ${valuePath}`; + case "APPEND": { + valueRef.set(":empty_list", []); + return `${keyPath} = list_append(if_not_exists(${keyPath}, :empty_list), ${valuePath})`; + } + case "REMOVE": + return `${change.name}`; } }); @@ -46,19 +65,25 @@ export function buildUpdate( }) .join(" "); - return { - UpdateExpression: expr, - ExpressionAttributeNames: keyRef.size > 0 ? - Array.from(keyRef.entries()).reduce((hash, [key, val]) => ({ - ...hash, - [key]: val, - }), {}) : - undefined, - ExpressionAttributeValues: valueRef.size > 0 ? + const attributeNames = keyRef.size > 0 ? + Array.from(keyRef.entries()).reduce((hash, [key, val]) => ({ + ...hash, + [key]: val, + }), {}) : + undefined; + + const attributeValues = valueRef.size > 0 ? Array.from(valueRef.entries()).reduce((hash, [key, val]) => ({ ...hash, [key]: val, }), {}) : - undefined, + undefined; + + const operation = { + UpdateExpression: expr, + ExpressionAttributeNames: attributeNames ? _.omitBy(attributeNames, _.isUndefined) : attributeNames, + ExpressionAttributeValues: attributeValues ? _.omitBy(attributeValues, _.isUndefined) : attributeValues, }; + + return _.omitBy(operation, _.isUndefined); } diff --git a/src/query/expressions/update/index.ts b/src/query/expressions/update/index.ts index c2c907b..cf352e0 100644 --- a/src/query/expressions/update/index.ts +++ b/src/query/expressions/update/index.ts @@ -1,10 +1,20 @@ +import { NumberSet, StringSet } from "../../../metadata/attribute"; + // TODO: Implement own Action classes +export type UpdateAction = UpdateActionNormal | UpdateActionList | UpdateActionRemove; -export type UpdateAction = "ADD" | "PUT" | "DELETE"; +export type UpdateActionNormal = "PUT" | "DELETE" | "ADD"; +export type UpdateActionList = "APPEND"; +export type UpdateActionRemove = "REMOVE"; export type UpdateChanges = { [P in keyof T]?: [ - UpdateAction, + UpdateActionNormal, T[P] + ] | [ + UpdateActionList, + T[P] extends Array ? T[P] : never + ] | [ + UpdateActionRemove ] }; diff --git a/src/query/full_primary_key.ts b/src/query/full_primary_key.ts index 7cb2920..2f4f13e 100644 --- a/src/query/full_primary_key.ts +++ b/src/query/full_primary_key.ts @@ -2,10 +2,12 @@ import { DynamoDB } from "aws-sdk"; import { ITable, Table } from "../table"; +import * as _ from "lodash"; import * as Codec from "../codec"; import * as Metadata from "../metadata"; import * as Query from "./query"; +import { TransactionWrite } from ".."; import { batchGetFull, batchGetTrim } from "./batch_get"; import { batchWrite } from "./batch_write"; import { Conditions } from "./expressions/conditions"; @@ -23,14 +25,14 @@ export class FullPrimaryKey { readonly metadata: Metadata.Indexes.FullPrimaryKeyMetadata, ) {} - public async delete( + public buildDeleteOperation( hashKey: HashKeyType, sortKey: RangeKeyType, options: Partial<{ condition: Conditions | Array>; }> = {}, ) { - const res = await this.tableClass.metadata.connection.documentClient.delete({ + return { TableName: this.tableClass.metadata.name, // ReturnValues: "ALL_OLD", Key: { @@ -38,7 +40,33 @@ export class FullPrimaryKey { [this.metadata.range.name]: sortKey, }, ...buildCondition(this.tableClass.metadata, options.condition), - }).promise(); + }; + } + + public async delete( + hashKey: HashKeyType, + sortKey: RangeKeyType, + options: Partial<{ + condition: Conditions | Array>; + }> = {}, + ) { + await this.tableClass.metadata.connection.documentClient.delete( + this.buildDeleteOperation(hashKey, sortKey, options) + ).promise(); + } + + public transactionDelete( + transaction: TransactionWrite, + hashKey: HashKeyType, + sortKey: RangeKeyType, + options: Partial<{ + condition: Conditions | Array>; + }> = {}, + ) { + const operation = this.buildDeleteOperation(hashKey, sortKey, options); + transaction.delete(operation); + + return transaction; } /** @@ -199,28 +227,58 @@ export class FullPrimaryKey { }; } - public async update( + public buildUpdateOperation( hashKey: HashKeyType, sortKey: RangeKeyType, changes: Partial>, options: Partial<{ condition: Conditions | Array>; }> = {}, - ): Promise { + ) { const update = buildUpdate(this.tableClass.metadata, changes); const condition = buildCondition(this.tableClass.metadata, options.condition); - const dynamoRecord = - await this.tableClass.metadata.connection.documentClient.update({ - TableName: this.tableClass.metadata.name, - Key: { - [this.metadata.hash.name]: hashKey, - [this.metadata.range.name]: sortKey, - }, - UpdateExpression: update.UpdateExpression, - ConditionExpression: condition.ConditionExpression, - ExpressionAttributeNames: { ...update.ExpressionAttributeNames, ...condition.ExpressionAttributeNames }, - ExpressionAttributeValues: { ...update.ExpressionAttributeValues, ...condition.ExpressionAttributeValues }, - }).promise(); + const attributeNames = { ...update.ExpressionAttributeNames, ...condition.ExpressionAttributeNames }; + const attributeValues = { ...update.ExpressionAttributeValues, ...condition.ExpressionAttributeValues }; + + return { + TableName: this.tableClass.metadata.name, + Key: { + [this.metadata.hash.name]: hashKey, + [this.metadata.range.name]: sortKey, + }, + UpdateExpression: update.UpdateExpression, + ConditionExpression: condition.ConditionExpression, + ExpressionAttributeNames: _.isEmpty(attributeNames) ? undefined : attributeNames, + ExpressionAttributeValues: _.isEmpty(attributeValues) ? undefined : attributeValues, + }; + } + + public async update( + hashKey: HashKeyType, + sortKey: RangeKeyType, + changes: Partial>, + options: Partial<{ + condition: Conditions | Array>; + }> = {}, + ): Promise { + + await this.tableClass.metadata.connection.documentClient.update( + this.buildUpdateOperation(hashKey, sortKey, changes, options) + ).promise(); + } + + public transactionUpdate( + transaction: TransactionWrite, + hashKey: HashKeyType, + sortKey: RangeKeyType, + changes: Partial>, + options: Partial<{ + condition: Conditions | Array>; + }> = {}, + ): TransactionWrite { + const operation = this.buildUpdateOperation(hashKey, sortKey, changes, options); + transaction.update(operation); + return transaction; } } diff --git a/src/query/hash_primary_key.ts b/src/query/hash_primary_key.ts index 0dedef7..40f6b31 100644 --- a/src/query/hash_primary_key.ts +++ b/src/query/hash_primary_key.ts @@ -1,5 +1,7 @@ import { DynamoDB } from "aws-sdk"; +import * as _ from "lodash"; +import { TransactionWrite } from ".."; import * as Codec from "../codec"; import * as Metadata from "../metadata"; import { ITable, Table } from "../table"; @@ -17,19 +19,43 @@ export class HashPrimaryKey { readonly metadata: Metadata.Indexes.HashPrimaryKeyMetadata, ) {} - public async delete( + public buildDeleteOperation( hashKey: HashKeyType, options: Partial<{ condition: Conditions | Array>; }> = {}, ) { - const res = await this.tableClass.metadata.connection.documentClient.delete({ + return { TableName: this.tableClass.metadata.name, Key: { [this.metadata.hash.name]: hashKey, }, ...buildCondition(this.tableClass.metadata, options.condition), - }).promise(); + }; + } + + public async delete( + hashKey: HashKeyType, + options: Partial<{ + condition: Conditions | Array>; + }> = {}, + ) { + await this.tableClass.metadata.connection.documentClient.delete( + this.buildDeleteOperation(hashKey, options) + ).promise(); + } + + public transactionDelete( + transaction: TransactionWrite, + hashKey: HashKeyType, + options: Partial<{ + condition: Conditions | Array>; + }> = {}, + ) { + const operation = this.buildDeleteOperation(hashKey, options); + transaction.delete(operation); + + return transaction; } public async get(hashKey: HashKeyType, options: { consistent: boolean } = { consistent: false }): Promise { @@ -128,28 +154,56 @@ export class HashPrimaryKey { ); } - // Let'just don't use Scan if it's possible - // async scan() - public async update( + public buildUpdateOperation( hashKey: HashKeyType, changes: Partial>, options: Partial<{ condition: Conditions | Array>; }> = {}, - ): Promise { + ) { const update = buildUpdate(this.tableClass.metadata, changes); const condition = buildCondition(this.tableClass.metadata, options.condition); - const dynamoRecord = - await this.tableClass.metadata.connection.documentClient.update({ - TableName: this.tableClass.metadata.name, - Key: { - [this.metadata.hash.name]: hashKey, - }, - UpdateExpression: update.UpdateExpression, - ConditionExpression: condition.ConditionExpression, - ExpressionAttributeNames: { ...update.ExpressionAttributeNames, ...condition.ExpressionAttributeNames }, - ExpressionAttributeValues: { ...update.ExpressionAttributeValues, ...condition.ExpressionAttributeValues }, - }).promise(); + const attributeNames = { ...update.ExpressionAttributeNames, ...condition.ExpressionAttributeNames }; + const attributeValues = { ...update.ExpressionAttributeValues, ...condition.ExpressionAttributeValues }; + + return { + TableName: this.tableClass.metadata.name, + Key: { + [this.metadata.hash.name]: hashKey, + }, + UpdateExpression: update.UpdateExpression, + ConditionExpression: condition.ConditionExpression, + ExpressionAttributeNames: _.isEmpty(attributeNames) ? undefined : attributeNames, + ExpressionAttributeValues: _.isEmpty(attributeValues) ? undefined : attributeValues, + }; } + + public async update( + hashKey: HashKeyType, + changes: Partial>, + options: Partial<{ + condition: Conditions | Array>; + }> = {}, + ): Promise { + + await this.tableClass.metadata.connection.documentClient.update( + this.buildUpdateOperation(hashKey, changes, options) + ).promise(); + } + + public async transactionUpdate( + transaction: TransactionWrite, + hashKey: HashKeyType, + changes: Partial>, + options: Partial<{ + condition: Conditions | Array>; + }> = {}, + ) { + const operation = this.buildUpdateOperation(hashKey, changes, options); + + transaction.update(operation); + return transaction; + } + } diff --git a/src/query/writer.ts b/src/query/writer.ts index c0cd4af..9603412 100644 --- a/src/query/writer.ts +++ b/src/query/writer.ts @@ -14,6 +14,19 @@ export class Writer { constructor(private tableClass: ITable) { } + public buildPutOperation( + record: T, + options: Partial<{ + condition: Conditions | Array>; + }> = {}, + ) { + return { + TableName: this.tableClass.metadata.name, + Item: Codec.serialize(this.tableClass, record), + ...buildCondition(this.tableClass.metadata, options.condition), + }; + } + public async put( record: T, options: Partial<{ @@ -21,11 +34,8 @@ export class Writer { }> = {}, ) { try { - const res = await this.tableClass.metadata.connection.documentClient.put({ - TableName: this.tableClass.metadata.name, - Item: Codec.serialize(this.tableClass, record), - ...buildCondition(this.tableClass.metadata, options.condition), - }).promise(); + const operation = this.buildPutOperation(record, options); + const res = await this.tableClass.metadata.connection.documentClient.put(operation).promise(); record.setAttributes(res.Attributes || {}); return record; @@ -50,17 +60,28 @@ export class Writer { ); } - public async delete( + public buildDeleteOperation( record: T, options: Partial<{ condition: Conditions | Array>; }> = {}, ) { - await this.tableClass.metadata.connection.documentClient.delete({ + return { TableName: this.tableClass.metadata.name, Key: KeyFromRecord(record, this.tableClass.metadata.primaryKey), ...buildCondition(this.tableClass.metadata, options.condition), - }).promise(); + }; + } + + public async delete( + record: T, + options: Partial<{ + condition: Conditions | Array>; + }> = {}, + ) { + await this.tableClass.metadata.connection.documentClient.delete( + this.buildDeleteOperation(record, options) + ).promise(); } } diff --git a/src/table.ts b/src/table.ts index b01bb5d..ab3bf1c 100644 --- a/src/table.ts +++ b/src/table.ts @@ -1,6 +1,7 @@ import * as _ from "lodash"; - +import { TransactionWrite } from "."; import * as Metadata from "./metadata"; +import { NumberSet, StringSet, Type } from "./metadata/attribute"; import * as Query from "./query"; import { Conditions } from "./query/expressions/conditions"; @@ -29,13 +30,36 @@ export class Table { private __writer: Query.Writer; // tslint:disable-line - public getAttribute(name: string) { - return this.__attributes[name]; + public getAttribute(name: string, metadata?: Metadata.Table.Metadata) { + const value = this.__attributes[name]; + + if (metadata && (value instanceof NumberSet || value instanceof StringSet)) { + return metadata.connection.documentClient.createSet(value.toArray()); + } + + return value; } // Those are pretty much "Private". don't use it if its possible public setAttribute(name: string, value: any) { // Do validation with Attribute metadata maybe + + if (typeof(value) === "object") { + const wrapperName = _.get(value, "wrapperName"); + const type = _.get(value, "type"); + + if (wrapperName === "Set") { + switch (type) { + case "String": + this.__attributes[name] = new StringSet(_.get(value, "values")); + return; + case "Number": + this.__attributes[name] = new NumberSet(_.get(value, "values")); + return; + } + } + } + this.__attributes[name] = value; } @@ -58,6 +82,19 @@ export class Table { ) { return await this.writer.put(this, options); } + public transactionSave( + this: T, + transaction: TransactionWrite, + options?: Partial<{ + condition?: Conditions | Array>; + }>, + + ) { + const operation = this.writer.buildPutOperation(this, options); + transaction.put(operation); + return transaction; + } + public async delete( this: T, options?: Partial<{ @@ -66,6 +103,20 @@ export class Table { ) { return await this.writer.delete(this, options); } + + public async transactionDelete( + this: T, + transaction: TransactionWrite, + options?: Partial<{ + condition?: Conditions | Array>; + }>, + ) { + const operation = this.writer.buildDeleteOperation(this, options); + transaction.delete(operation); + + return transaction; + } + public serialize() { // TODO some serialization logic return this.__attributes; diff --git a/src/transaction.ts b/src/transaction.ts new file mode 100644 index 0000000..e0370d2 --- /dev/null +++ b/src/transaction.ts @@ -0,0 +1,76 @@ +import { DocumentClient } from "aws-sdk/clients/dynamodb"; +import { Config, Table } from "."; +import { Connection } from "./connections"; +import { Conditions } from "./query"; + +export class TransactionWrite { + private __connection: Connection; + private __typedOperation: Array<{ + type: "put", + operation: DocumentClient.Put + } | { + type: "update", + operation: DocumentClient.Update + } | { + type: "delete", + operation: DocumentClient.Delete + }>; + + constructor( + connection?: Connection, + ) { + this.__connection = connection ?? Config.default.defaultConnection; + this.__typedOperation = []; + } + + public put(operation: DocumentClient.Put) { + this.__typedOperation.push({ + type: "put", + operation + }); + } + + public update(operation: DocumentClient.Update) { + this.__typedOperation.push({ + type: "update", + operation + }); + } + + public delete(operation: DocumentClient.Delete) { + this.__typedOperation.push({ + type: "delete", + operation + }); + } + + public async commit() { + + const items = this.__typedOperation.map((item) => { + if (item.type === "put") { + return { + Put: item.operation + }; + } else if (item.type === "update") { + return { + Update: item.operation + }; + } else { + return { + Delete: item.operation + }; + } + }); + + try { + await this.__connection.documentClient.transactWrite({ + TransactItems: items + }).promise(); + } catch (e) { + // tslint:disable-next-line: no-console + console.log(e); + throw e; + } + + } +}