Skip to content

Commit 78c6980

Browse files
feat(typing): more advanced typing for return types
more advanced typing for cases which need to manipulate the return type of exec() (or similar), before the update update().returnValues('ALL_NEW').exec() returned `T` but when another command was added to the chain the type went back to `void` when using `this` as return type the information will be prevented.
1 parent 43840c4 commit 78c6980

File tree

15 files changed

+110
-116
lines changed

15 files changed

+110
-116
lines changed

src/dynamo/expression/request-expression-builder.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,18 +151,18 @@ export function doAddCondition<T, R extends ConditionalParamsHost>(
151151
/**
152152
* @hidden
153153
*/
154-
export function addPartitionKeyCondition<R extends StandardRequest<any, any, any>>(
154+
export function addPartitionKeyCondition<R extends StandardRequest<any, any, any, any>>(
155155
keyName: keyof any,
156156
keyValue: any,
157157
request: R,
158158
): R
159-
export function addPartitionKeyCondition<T, R extends StandardRequest<T, any, any>>(
159+
export function addPartitionKeyCondition<T, R extends StandardRequest<T, any, any, any>>(
160160
keyName: keyof T,
161161
keyValue: any,
162162
request: R,
163163
metadata: Metadata<T>,
164164
): R
165-
export function addPartitionKeyCondition<T, R extends StandardRequest<T, any, any>>(
165+
export function addPartitionKeyCondition<T, R extends StandardRequest<T, any, any, any>>(
166166
keyName: keyof T,
167167
keyValue: any,
168168
request: R,

src/dynamo/request/base.request.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { getTableName } from '../get-table-name.function'
1515
*/
1616
export abstract class BaseRequest<
1717
T,
18+
T2,
1819
I extends
1920
| DynamoDB.DeleteItemInput
2021
| DynamoDB.GetItemInput
@@ -26,7 +27,7 @@ export abstract class BaseRequest<
2627
| DynamoDB.BatchWriteItemInput
2728
| DynamoDB.TransactGetItemsInput
2829
| DynamoDB.TransactWriteItemsInput,
29-
R extends BaseRequest<T, I, any>
30+
R extends BaseRequest<T, T2, I, any>
3031
> {
3132
readonly dynamoDBWrapper: DynamoDbWrapper
3233

@@ -86,5 +87,5 @@ export abstract class BaseRequest<
8687
/**
8788
* execute request and return the parsed item(s) or void if none were requested.
8889
*/
89-
abstract exec(): Promise<T | T[] | void | null>
90+
abstract exec(): Promise<T2 | T2[] | void | null>
9091
}

src/dynamo/request/batchgetsingletable/batch-get-single-table.request.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ import { BatchGetSingleTableResponse } from './batch-get-single-table.response'
1818
/**
1919
* Request class for BatchGetItem operation which supports a single model class only.
2020
*/
21-
export class BatchGetSingleTableRequest<T> extends BaseRequest<
21+
export class BatchGetSingleTableRequest<T, T2 = T> extends BaseRequest<
2222
T,
23+
T2,
2324
DynamoDB.BatchGetItemInput,
24-
BatchGetSingleTableRequest<T>
25+
BatchGetSingleTableRequest<T, T2>
2526
> {
2627
private readonly logger: Logger
2728

@@ -43,7 +44,7 @@ export class BatchGetSingleTableRequest<T> extends BaseRequest<
4344
/**
4445
* Determines the read consistency model: If set to true, then the operation uses strongly consistent reads; otherwise, the operation uses eventually consistent reads.
4546
*/
46-
consistentRead(value: boolean = true): BatchGetSingleTableRequest<T> {
47+
consistentRead(value: boolean = true): this {
4748
this.params.RequestItems[this.tableName].ConsistentRead = value
4849
return this
4950
}
@@ -52,7 +53,7 @@ export class BatchGetSingleTableRequest<T> extends BaseRequest<
5253
* Specifies the list of model attributes to be returned from the table instead of returning the entire document
5354
* @param attributesToGet List of model attributes to be returned
5455
*/
55-
projectionExpression(...attributesToGet: Array<keyof T | string>): BatchGetSingleTableRequest<T> {
56+
projectionExpression(...attributesToGet: Array<keyof T | string>): BatchGetSingleTableRequest<T, Partial<T>> {
5657
addProjectionExpressionParam(attributesToGet, this.params.RequestItems[this.tableName], this.metadata)
5758
return this
5859
}
@@ -77,7 +78,7 @@ export class BatchGetSingleTableRequest<T> extends BaseRequest<
7778
execFullResponse(
7879
backoffTimer = randomExponentialBackoffTimer,
7980
throttleTimeSlot = BATCH_GET_DEFAULT_TIME_SLOT,
80-
): Promise<BatchGetSingleTableResponse<T>> {
81+
): Promise<BatchGetSingleTableResponse<T2>> {
8182
return this.fetch(backoffTimer, throttleTimeSlot)
8283
.then(this.mapResponse)
8384
.then(promiseTap(response => this.logger.debug('mapped items', response.Items)))
@@ -88,18 +89,18 @@ export class BatchGetSingleTableRequest<T> extends BaseRequest<
8889
* @param backoffTimer when unprocessed keys are returned the next value of backoffTimer is used to determine how many time slots to wait before doing the next request
8990
* @param throttleTimeSlot the duration of a time slot in ms
9091
*/
91-
exec(backoffTimer = randomExponentialBackoffTimer, throttleTimeSlot = BATCH_GET_DEFAULT_TIME_SLOT): Promise<T[]> {
92+
exec(backoffTimer = randomExponentialBackoffTimer, throttleTimeSlot = BATCH_GET_DEFAULT_TIME_SLOT): Promise<T2[]> {
9293
return this.fetch(backoffTimer, throttleTimeSlot)
9394
.then(this.mapResponse)
9495
.then(r => r.Items)
9596
.then(promiseTap(items => this.logger.debug('mapped items', items)))
9697
}
9798

9899
private mapResponse = (response: DynamoDB.BatchGetItemOutput) => {
99-
let items: T[] = []
100+
let items: T2[] = []
100101
if (response.Responses && Object.keys(response.Responses).length && response.Responses[this.tableName]) {
101-
const mapped: T[] = response.Responses[this.tableName].map(attributeMap =>
102-
fromDb(<Attributes<T>>attributeMap, this.modelClazz),
102+
const mapped: T2[] = response.Responses[this.tableName].map(attributeMap =>
103+
fromDb(<Attributes<T2>>attributeMap, <any>this.modelClazz),
103104
)
104105
items = mapped
105106
}

src/dynamo/request/batchwritesingletable/batch-write-single-table.request.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ import { BaseRequest } from '../base.request'
1414
/**
1515
* Request class for BatchWriteItem operation which supports a single model class only.
1616
*/
17-
export class BatchWriteSingleTableRequest<T> extends BaseRequest<
17+
export class BatchWriteSingleTableRequest<T, T2 = T> extends BaseRequest<
1818
T,
19+
T2,
1920
DynamoDB.BatchWriteItemInput,
20-
BatchWriteSingleTableRequest<T>
21+
BatchWriteSingleTableRequest<T, T2>
2122
> {
2223
private readonly logger: Logger
2324
private toKey = createToKeyFn(this.modelClazz)
@@ -37,15 +38,15 @@ export class BatchWriteSingleTableRequest<T> extends BaseRequest<
3738
this.params.ReturnItemCollectionMetrics = value
3839
}
3940

40-
delete(items: Array<Partial<T>>): BatchWriteSingleTableRequest<T> {
41+
delete(items: Array<Partial<T>>): this {
4142
if (this.params.RequestItems[this.tableName].length + items.length > BATCH_WRITE_MAX_REQUEST_ITEM_COUNT) {
4243
throw new Error(`batch write takes at max ${BATCH_WRITE_MAX_REQUEST_ITEM_COUNT} items`)
4344
}
4445
this.params.RequestItems[this.tableName].push(...items.map(this.createDeleteRequest))
4546
return this
4647
}
4748

48-
put(items: T[]): BatchWriteSingleTableRequest<T> {
49+
put(items: T[]): this {
4950
if (this.params.RequestItems[this.tableName].length + items.length > BATCH_WRITE_MAX_REQUEST_ITEM_COUNT) {
5051
throw new Error(`batch write takes at max ${BATCH_WRITE_MAX_REQUEST_ITEM_COUNT} items`)
5152
}

src/dynamo/request/delete/delete.request.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,18 @@ import * as DynamoDB from 'aws-sdk/clients/dynamodb'
55
import { createLogger, Logger } from '../../../logger/logger'
66
import { createKeyAttributes } from '../../../mapper/mapper'
77
import { ModelConstructor } from '../../../model/model-constructor'
8-
import { Omit } from '../../../model/omit.type'
98
import { DynamoDbWrapper } from '../../dynamo-db-wrapper'
109
import { WriteRequest } from '../write.request'
11-
import { DeleteResponse } from './delete.response'
12-
13-
type DeleteRequestReturnT<T> = Omit<Omit<DeleteRequest<T>, 'exec'>, 'execFullResponse'> & {
14-
exec(): Promise<T>
15-
execFullResponse(): Promise<DeleteResponse<T>>
16-
}
1710

1811
/**
1912
* Request class for the DeleteItem operation.
2013
*/
21-
export class DeleteRequest<T> extends WriteRequest<
14+
export class DeleteRequest<T, T2 = T> extends WriteRequest<
2215
T,
16+
T2,
2317
DynamoDB.DeleteItemInput,
2418
DynamoDB.DeleteItemOutput,
25-
DeleteRequest<T>
19+
DeleteRequest<T, T2>
2620
> {
2721
protected readonly logger: Logger
2822

@@ -32,11 +26,11 @@ export class DeleteRequest<T> extends WriteRequest<
3226
this.params.Key = createKeyAttributes(this.metadata, partitionKey, sortKey)
3327
}
3428

35-
returnValues(returnValues: 'ALL_OLD'): DeleteRequestReturnT<T>
36-
returnValues(returnValues: 'NONE'): DeleteRequest<T>
37-
returnValues(returnValues: 'ALL_OLD' | 'NONE'): DeleteRequest<T> | DeleteRequestReturnT<T> {
29+
returnValues(returnValues: 'ALL_OLD'): DeleteRequest<T, T>
30+
returnValues(returnValues: 'NONE'): DeleteRequest<T, void>
31+
returnValues(returnValues: 'ALL_OLD' | 'NONE'): DeleteRequest<T, T | void> {
3832
this.params.ReturnValues = returnValues
39-
return this
33+
return <any>this
4034
}
4135

4236
protected doRequest(params: DynamoDB.DeleteItemInput): Promise<DynamoDB.DeleteItemOutput> {

src/dynamo/request/get/get.request.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ describe('GetRequest', () => {
3434
})
3535

3636
it('projection expression', () => {
37+
// const item = await request.consistentRead().exec()
38+
// const item = await request.consistentRead().projectionExpression('age').exec()
39+
// const item2 = await request.projectionExpression('age').exec()
40+
// const item3 = await request.projectionExpression('age').consistentRead().exec()
3741
request.projectionExpression('age')
38-
3942
const params = request.params
4043
expect(params.ProjectionExpression).toBe('#age')
4144
expect(params.ExpressionAttributeNames).toEqual({ '#age': 'age' })

src/dynamo/request/get/get.request.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { GetResponse } from './get.response'
1515
/**
1616
* Request class for the GetItem operation.
1717
*/
18-
export class GetRequest<T> extends StandardRequest<T, DynamoDB.GetItemInput, GetRequest<T>> {
18+
export class GetRequest<T, T2 = T> extends StandardRequest<T, T2, DynamoDB.GetItemInput, GetRequest<T, T2>> {
1919
private readonly logger: Logger
2020

2121
constructor(dynamoDBWrapper: DynamoDbWrapper, modelClazz: ModelConstructor<T>, partitionKey: any, sortKey?: any) {
@@ -27,7 +27,7 @@ export class GetRequest<T> extends StandardRequest<T, DynamoDB.GetItemInput, Get
2727
/**
2828
* Determines the read consistency model: If set to true, then the operation uses strongly consistent reads; otherwise, the operation uses eventually consistent reads.
2929
*/
30-
consistentRead(consistentRead: boolean = true): GetRequest<T> {
30+
consistentRead(consistentRead: boolean = true): this {
3131
this.params.ConsistentRead = consistentRead
3232
return this
3333
}
@@ -36,21 +36,21 @@ export class GetRequest<T> extends StandardRequest<T, DynamoDB.GetItemInput, Get
3636
* Specifies the list of model attributes to be returned from the table instead of returning the entire document
3737
* @param attributesToGet List of model attributes to be returned
3838
*/
39-
projectionExpression(...attributesToGet: Array<keyof T | string>): GetRequest<T> {
39+
projectionExpression(...attributesToGet: Array<keyof T | string>): GetRequest<T, Partial<T>> {
4040
addProjectionExpressionParam(attributesToGet, this.params, this.metadata)
4141
return this
4242
}
4343

44-
execFullResponse(): Promise<GetResponse<T>> {
44+
execFullResponse(): Promise<GetResponse<T2>> {
4545
this.logger.debug('request', this.params)
4646
return this.dynamoDBWrapper
4747
.getItem(this.params)
4848
.then(promiseTap(response => this.logger.debug('response', response)))
4949
.then(getItemResponse => {
50-
const response: GetResponse<T> = <any>{ ...getItemResponse }
50+
const response: GetResponse<T2> = <any>{ ...getItemResponse }
5151

5252
if (getItemResponse.Item) {
53-
response.Item = fromDb(<Attributes<T>>getItemResponse.Item, this.modelClazz)
53+
response.Item = fromDb(<Attributes<T2>>getItemResponse.Item, <any>this.modelClazz)
5454
} else {
5555
response.Item = null
5656
}
@@ -63,14 +63,14 @@ export class GetRequest<T> extends StandardRequest<T, DynamoDB.GetItemInput, Get
6363
/**
6464
* execute request and return the parsed item
6565
*/
66-
exec(): Promise<T | null> {
66+
exec(): Promise<T2 | null> {
6767
this.logger.debug('request', this.params)
6868
return this.dynamoDBWrapper
6969
.getItem(this.params)
7070
.then(promiseTap(response => this.logger.debug('response', response)))
7171
.then(response => {
7272
if (response.Item) {
73-
return fromDb(<Attributes<T>>response.Item, this.modelClazz)
73+
return fromDb(<Attributes<T2>>response.Item, <any>this.modelClazz)
7474
} else {
7575
return null
7676
}

src/dynamo/request/put/put.request.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,20 @@ import * as DynamoDB from 'aws-sdk/clients/dynamodb'
55
import { createLogger, Logger } from '../../../logger/logger'
66
import { toDb } from '../../../mapper/mapper'
77
import { ModelConstructor } from '../../../model/model-constructor'
8-
import { Omit } from '../../../model/omit.type'
98
import { DynamoDbWrapper } from '../../dynamo-db-wrapper'
109
import { createIfNotExistsCondition } from '../../expression/create-if-not-exists-condition.function'
1110
import { WriteRequest } from '../write.request'
12-
import { PutResponse } from './put.response'
13-
14-
type PutRequestReturnT<T> = Omit<Omit<PutRequest<T>, 'exec'>, 'execFullResponse'> & {
15-
exec(): Promise<T>
16-
execFullResponse(): Promise<PutResponse<T>>
17-
}
1811

1912
/**
2013
* Request class for the PutItem operation.
2114
*/
22-
export class PutRequest<T> extends WriteRequest<T, DynamoDB.PutItemInput, DynamoDB.PutItemOutput, PutRequest<T>> {
15+
export class PutRequest<T, T2 = void> extends WriteRequest<
16+
T,
17+
T2,
18+
DynamoDB.PutItemInput,
19+
DynamoDB.PutItemOutput,
20+
PutRequest<T, T2>
21+
> {
2322
protected readonly logger: Logger
2423

2524
constructor(dynamoDBWrapper: DynamoDbWrapper, modelClazz: ModelConstructor<T>, item: T) {
@@ -31,18 +30,19 @@ export class PutRequest<T> extends WriteRequest<T, DynamoDB.PutItemInput, Dynamo
3130
/**
3231
* Adds a condition expression to the request, which makes sure the item will only be saved if the id does not exist
3332
*/
34-
ifNotExists(predicate: boolean = true): PutRequest<T> {
33+
ifNotExists(predicate: boolean = true): this {
3534
if (predicate) {
3635
this.onlyIf(...createIfNotExistsCondition(this.metadata))
3736
}
37+
3838
return this
3939
}
4040

41-
returnValues(returnValues: 'ALL_OLD'): PutRequestReturnT<T>
42-
returnValues(returnValues: 'NONE'): PutRequest<T>
43-
returnValues(returnValues: 'ALL_OLD' | 'NONE'): PutRequest<T> | PutRequestReturnT<T> {
41+
returnValues(returnValues: 'ALL_OLD'): PutRequest<T, T>
42+
returnValues(returnValues: 'NONE'): PutRequest<T, void>
43+
returnValues(returnValues: 'ALL_OLD' | 'NONE'): PutRequest<T, T | void> {
4444
this.params.ReturnValues = returnValues
45-
return this
45+
return <any>this
4646
}
4747

4848
protected doRequest(params: DynamoDB.PutItemInput): Promise<DynamoDB.PutItemOutput> {

src/dynamo/request/query/query.request.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import { QueryResponse } from './query.response'
1313
/**
1414
* Request class for the Query operation.
1515
*/
16-
export class QueryRequest<T> extends ReadManyRequest<
16+
export class QueryRequest<T, T2 = T> extends ReadManyRequest<
1717
T,
18+
T2,
1819
DynamoDB.QueryInput,
1920
DynamoDB.QueryOutput,
20-
QueryResponse<T>,
21-
QueryRequest<T>
21+
QueryResponse<T2>,
22+
QueryRequest<T2>
2223
> {
2324
protected readonly logger: Logger
2425

@@ -27,10 +28,7 @@ export class QueryRequest<T> extends ReadManyRequest<
2728
this.logger = createLogger('dynamo.request.QueryRequest', modelClazz)
2829
}
2930

30-
/**
31-
*
32-
*/
33-
wherePartitionKey(partitionKeyValue: any): QueryRequest<T> {
31+
wherePartitionKey(partitionKeyValue: any): this {
3432
let partitionKey: keyof T
3533
if (this.secondaryIndex) {
3634
if (!this.secondaryIndex.partitionKey) {
@@ -47,7 +45,7 @@ export class QueryRequest<T> extends ReadManyRequest<
4745
/**
4846
* used to define some condition for the sort key, use the secondary index to query based on a custom index
4947
*/
50-
whereSortKey(): SortKeyConditionFunction<QueryRequest<T>> {
48+
whereSortKey(): SortKeyConditionFunction<QueryRequest<T, T2>> {
5149
let sortKey: keyof T | null
5250
if (this.secondaryIndex) {
5351
if (!this.secondaryIndex.sortKey) {
@@ -65,12 +63,12 @@ export class QueryRequest<T> extends ReadManyRequest<
6563
return addSortKeyCondition(sortKey, this, this.metadata)
6664
}
6765

68-
ascending(): QueryRequest<T> {
66+
ascending(): this {
6967
this.params.ScanIndexForward = true
7068
return this
7169
}
7270

73-
descending(): QueryRequest<T> {
71+
descending(): this {
7472
this.params.ScanIndexForward = false
7573
return this
7674
}

0 commit comments

Comments
 (0)