Skip to content

Commit 15bc1ae

Browse files
Merge pull request #98 from shiftcode/#96-transact-get-request
#96 transact get request
2 parents d922796 + 3f8572e commit 15bc1ae

File tree

6 files changed

+334
-0
lines changed

6 files changed

+334
-0
lines changed

src/dynamo/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ export * from './dynamo-store'
66
export * from './primary-key.type'
77
export * from './session-validity-ensurer.type'
88
export * from './table-name-resolver.type'
9+
export * from './transactget'
10+
export * from './transactwrite'

src/dynamo/transactget/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './transact-get.request'
2+
export * from './transact-get.request.type'
3+
export * from './transact-get-full.response'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import * as DynamoDB from 'aws-sdk/clients/dynamodb'
2+
3+
export interface TransactGetFullResponse<X> {
4+
Items: X
5+
ConsumedCapacity?: DynamoDB.ConsumedCapacityMultiple
6+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// tslint:disable:no-non-null-assertion
2+
// tslint:disable:no-unnecessary-class
3+
import * as DynamoDB from 'aws-sdk/clients/dynamodb'
4+
import { of } from 'rxjs'
5+
import { SimpleWithCompositePartitionKeyModel, SimpleWithPartitionKeyModel } from '../../../test/models'
6+
import { Attributes } from '../../mapper'
7+
import { getTableName } from '../get-table-name.function'
8+
import { TransactGetRequest } from './transact-get.request'
9+
import { TransactGetRequest2 } from './transact-get.request.type'
10+
11+
describe('TransactGetRequest', () => {
12+
let req: TransactGetRequest
13+
14+
describe('constructor', () => {
15+
beforeEach(() => (req = new TransactGetRequest()))
16+
17+
it('shoud init params', () => {
18+
expect(req.params).toBeDefined()
19+
expect(req.params.TransactItems).toBeDefined()
20+
expect(req.params.TransactItems.length).toBe(0)
21+
})
22+
})
23+
24+
describe('returnConsumedCapacity', () => {
25+
beforeEach(() => (req = new TransactGetRequest()))
26+
27+
it('should set the param', () => {
28+
req.returnConsumedCapacity('INDEXES')
29+
30+
expect(req.params.ReturnConsumedCapacity).toBe('INDEXES')
31+
})
32+
})
33+
34+
describe('forModel', () => {
35+
beforeEach(() => (req = new TransactGetRequest()))
36+
37+
it('should add a single item to params', () => {
38+
req.forModel(SimpleWithPartitionKeyModel, { id: 'myId' })
39+
40+
expect(req.params.TransactItems.length).toBe(1)
41+
expect(req.params.TransactItems[0]).toEqual({
42+
Get: {
43+
TableName: getTableName(SimpleWithPartitionKeyModel),
44+
Key: { id: { S: 'myId' } },
45+
},
46+
})
47+
})
48+
49+
it('should a multiple items to params', () => {
50+
req.forModel(SimpleWithPartitionKeyModel, { id: 'myId' })
51+
const creationDate = new Date()
52+
req.forModel(SimpleWithCompositePartitionKeyModel, { id: 'myId', creationDate })
53+
54+
expect(req.params.TransactItems.length).toBe(2)
55+
expect(req.params.TransactItems[0].Get.TableName).toBe(getTableName(SimpleWithPartitionKeyModel))
56+
57+
expect(req.params.TransactItems[1].Get.TableName).toBe(getTableName(SimpleWithCompositePartitionKeyModel))
58+
expect(req.params.TransactItems[1].Get.Key).toEqual({
59+
id: { S: 'myId' },
60+
creationDate: { S: creationDate.toISOString() },
61+
})
62+
})
63+
64+
it('should throw when non-model class is added', () => {
65+
class FooBar {}
66+
expect(() => req.forModel(FooBar, {})).toThrow()
67+
})
68+
69+
it('should throw when more than 10 items are requested', () => {
70+
for (let i = 0; i < 10; i++) {
71+
req.forModel(SimpleWithPartitionKeyModel, { id: 'myId' })
72+
}
73+
// the 11th time
74+
expect(() => req.forModel(SimpleWithPartitionKeyModel, { id: 'myId' })).toThrow()
75+
})
76+
})
77+
78+
describe('execNoMap, execFullResponse, exec', () => {
79+
let transactGetItemsSpy: jasmine.Spy
80+
let req2: TransactGetRequest2<SimpleWithPartitionKeyModel, SimpleWithCompositePartitionKeyModel>
81+
let creationDate: Date
82+
83+
beforeEach(() => {
84+
const dbItem: Attributes<SimpleWithPartitionKeyModel> = {
85+
id: { S: 'myId' },
86+
age: { N: '20' },
87+
}
88+
creationDate = new Date()
89+
const dbItem2: Attributes<SimpleWithCompositePartitionKeyModel> = {
90+
id: { S: 'myId' },
91+
creationDate: { S: creationDate.toISOString() },
92+
age: { N: '22' },
93+
}
94+
const output: DynamoDB.TransactGetItemsOutput = {
95+
ConsumedCapacity: [],
96+
Responses: [{ Item: dbItem }, { Item: dbItem2 }],
97+
}
98+
transactGetItemsSpy = jasmine.createSpy().and.returnValues(of(output))
99+
req2 = new TransactGetRequest()
100+
.forModel(SimpleWithPartitionKeyModel, { id: 'myId' })
101+
.forModel(SimpleWithCompositePartitionKeyModel, { id: 'myId', creationDate })
102+
Object.assign(req2, { dynamoRx: { transactGetItems: transactGetItemsSpy } })
103+
})
104+
105+
it('exec should return the mapped item', async () => {
106+
const result = await req2.exec().toPromise()
107+
expect(Array.isArray(result)).toBeTruthy()
108+
expect(result.length).toBe(2)
109+
expect(result[0]).toEqual({
110+
id: 'myId',
111+
age: 20,
112+
})
113+
expect(result[1]).toEqual({
114+
id: 'myId',
115+
age: 22,
116+
creationDate,
117+
})
118+
})
119+
120+
it('execFullResponse should return the mapped items', async () => {
121+
const result = await req2.execFullResponse().toPromise()
122+
expect(result).toBeDefined()
123+
expect(result.ConsumedCapacity).toEqual([])
124+
expect(result.Items).toBeDefined()
125+
expect(result.Items[0]).toEqual({
126+
id: 'myId',
127+
age: 20,
128+
})
129+
expect(result.Items[1]).toEqual({
130+
id: 'myId',
131+
age: 22,
132+
creationDate,
133+
})
134+
})
135+
136+
it('execNoMap should return the original response', async () => {
137+
const result = await req2.execNoMap().toPromise()
138+
expect(result.ConsumedCapacity).toEqual([])
139+
expect(result.Responses).toBeDefined()
140+
expect(result.Responses![0]).toBeDefined()
141+
expect(result.Responses![0].Item).toBeDefined()
142+
expect(result.Responses![1]).toBeDefined()
143+
expect(result.Responses![1].Item).toBeDefined()
144+
})
145+
})
146+
})
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import * as DynamoDB from 'aws-sdk/clients/dynamodb'
2+
import { Observable } from 'rxjs'
3+
import { map } from 'rxjs/operators'
4+
import { metadataForClass } from '../../decorator/metadata/metadata-helper'
5+
import { Attributes, createToKeyFn, fromDb } from '../../mapper'
6+
import { ModelConstructor } from '../../model'
7+
import { DynamoRx } from '../dynamo-rx'
8+
import { getTableName } from '../get-table-name.function'
9+
import { TransactGetFullResponse } from './transact-get-full.response'
10+
import { TransactGetRequest1 } from './transact-get.request.type'
11+
12+
const MAX_REQUEST_ITEM_COUNT = 10
13+
14+
export class TransactGetRequest {
15+
readonly params: DynamoDB.TransactGetItemsInput
16+
private readonly dynamoRx: DynamoRx
17+
private readonly tables: Array<ModelConstructor<any>> = []
18+
19+
constructor() {
20+
this.dynamoRx = new DynamoRx()
21+
this.params = {
22+
TransactItems: [],
23+
}
24+
}
25+
26+
27+
forModel<T>(modelClazz: ModelConstructor<T>, key: Partial<T>): TransactGetRequest1<T> {
28+
29+
// check if modelClazz is really an @Model() decorated class
30+
const metadata = metadataForClass(modelClazz)
31+
if (!metadata.modelOptions) {
32+
throw new Error('given ModelConstructor has no @Model decorator')
33+
}
34+
35+
this.tables.push(modelClazz)
36+
37+
// check if table was already used in this request
38+
const tableName = getTableName(metadata)
39+
40+
41+
// check if keys to add do not exceed max count
42+
if (this.params.TransactItems.length + 1 > MAX_REQUEST_ITEM_COUNT) {
43+
throw new Error(`you can request at max ${MAX_REQUEST_ITEM_COUNT} items per request`)
44+
}
45+
46+
this.params.TransactItems.push({
47+
Get: {
48+
TableName: tableName,
49+
Key: createToKeyFn(modelClazz)(key),
50+
},
51+
},
52+
)
53+
return <any>this
54+
}
55+
56+
returnConsumedCapacity(level: DynamoDB.ReturnConsumedCapacity): TransactGetRequest {
57+
this.params.ReturnConsumedCapacity = level
58+
return this
59+
}
60+
61+
execNoMap(): Observable<DynamoDB.TransactGetItemsOutput> {
62+
return this.dynamoRx.transactGetItems(this.params)
63+
}
64+
65+
execFullResponse(): Observable<TransactGetFullResponse<[]>> {
66+
return this.dynamoRx.transactGetItems(this.params).pipe(
67+
map(this.mapResponse),
68+
)
69+
}
70+
71+
exec(): Observable<[]> {
72+
return this.dynamoRx.transactGetItems(this.params).pipe(
73+
map(this.mapResponse),
74+
map(r => r.Items),
75+
)
76+
}
77+
78+
79+
private mapResponse = (response: DynamoDB.TransactGetItemsOutput): TransactGetFullResponse<[]> => {
80+
const Items: any = response.Responses && Object.keys(response.Responses).length
81+
? response.Responses.map((item, ix) => fromDb(<Attributes>item.Item, this.tables[ix]))
82+
: []
83+
return {
84+
ConsumedCapacity: response.ConsumedCapacity,
85+
Items,
86+
}
87+
}
88+
}
89+
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import * as DynamoDB from 'aws-sdk/clients/dynamodb'
2+
import { Observable } from 'rxjs'
3+
import { ModelConstructor } from '../../model'
4+
import { TransactGetFullResponse } from './transact-get-full.response'
5+
6+
export interface TransactGetRequestBase {
7+
readonly params: DynamoDB.TransactGetItemsInput
8+
execNoMap(): Observable<DynamoDB.TransactGetItemsOutput>
9+
}
10+
11+
export interface TransactGetRequest1<A> extends TransactGetRequestBase {
12+
forModel<B>(modelClazz: ModelConstructor<B>, key: Partial<B>): TransactGetRequest2<A, B>
13+
14+
execFullResponse(): Observable<TransactGetFullResponse<[A]>>
15+
16+
exec(): Observable<[A]>
17+
}
18+
19+
export interface TransactGetRequest2<A, B> extends TransactGetRequestBase {
20+
forModel<C>(modelClazz: ModelConstructor<C>, key: Partial<C>): TransactGetRequest3<A, B, C>
21+
22+
execFullResponse(): Observable<TransactGetFullResponse<[A, B]>>
23+
24+
exec(): Observable<[A, B]>
25+
}
26+
27+
export interface TransactGetRequest3<A, B, C> extends TransactGetRequestBase {
28+
forModel<D>(modelClazz: ModelConstructor<D>, key: Partial<D>): TransactGetRequest4<A, B, C, D>
29+
30+
execFullResponse(): Observable<TransactGetFullResponse<[A, B, C]>>
31+
32+
exec(): Observable<[A, B, C]>
33+
}
34+
35+
export interface TransactGetRequest4<A, B, C, D> extends TransactGetRequestBase {
36+
forModel<E>(modelClazz: ModelConstructor<E>, key: Partial<E>): TransactGetRequest5<A, B, C, D, E>
37+
38+
execFullResponse(): Observable<TransactGetFullResponse<[A, B, C, D]>>
39+
40+
exec(): Observable<[A, B, C, D]>
41+
}
42+
43+
export interface TransactGetRequest5<A, B, C, D, E> extends TransactGetRequestBase {
44+
forModel<F>(modelClazz: ModelConstructor<F>, key: Partial<F>): TransactGetRequest6<A, B, C, D, E, F>
45+
46+
execFullResponse(): Observable<TransactGetFullResponse<[A, B, C, D, E]>>
47+
48+
exec(): Observable<[A, B, C, D, E]>
49+
}
50+
51+
export interface TransactGetRequest6<A, B, C, D, E, F> extends TransactGetRequestBase {
52+
forModel<G>(modelClazz: ModelConstructor<G>, key: Partial<G>): TransactGetRequest7<A, B, C, D, E, F, G>
53+
54+
execFullResponse(): Observable<TransactGetFullResponse<[A, B, C, D, E, F]>>
55+
56+
exec(): Observable<[A, B, C, D, E, F]>
57+
}
58+
59+
export interface TransactGetRequest7<A, B, C, D, E, F, G> extends TransactGetRequestBase {
60+
forModel<H>(modelClazz: ModelConstructor<H>, key: Partial<H>): TransactGetRequest8<A, B, C, D, E, F, G, H>
61+
62+
execFullResponse(): Observable<TransactGetFullResponse<[A, B, C, D, E, F, G]>>
63+
64+
exec(): Observable<[A, B, C, D, E, F, G]>
65+
}
66+
67+
export interface TransactGetRequest8<A, B, C, D, E, F, G, H> extends TransactGetRequestBase {
68+
forModel<I>(modelClazz: ModelConstructor<I>, key: Partial<I>): TransactGetRequest9<A, B, C, D, E, F, G, H, I>
69+
70+
execFullResponse(): Observable<TransactGetFullResponse<[A, B, C, D, E, F, G, H]>>
71+
72+
exec(): Observable<[A, B, C, D, E, F, G, H]>
73+
}
74+
75+
export interface TransactGetRequest9<A, B, C, D, E, F, G, H, I> extends TransactGetRequestBase {
76+
forModel<J>(modelClazz: ModelConstructor<J>, key: Partial<J>): TransactGetRequest10<A, B, C, D, E, F, G, H, I, J>
77+
78+
execFullResponse(): Observable<TransactGetFullResponse<[A, B, C, D, E, F, G, H, I]>>
79+
80+
exec(): Observable<[A, B, C, D, E, F, G, H, I]>
81+
}
82+
83+
export interface TransactGetRequest10<A, B, C, D, E, F, G, H, I, J> extends TransactGetRequestBase {
84+
85+
execFullResponse(): Observable<TransactGetFullResponse<[A, B, C, D, E, F, G, H, I, J]>>
86+
87+
exec(): Observable<[A, B, C, D, E, F, G, H, I, J]>
88+
}

0 commit comments

Comments
 (0)