Skip to content

Commit 21b7a98

Browse files
feat(typings): better typing
- update jest config to match new config schema - add new type MetadataWithSortKey to be used with the guard function hasSortKey on metadata - make Attributes generic - update typings for request-expression-builder - add type Omit for convenience
1 parent 977eb38 commit 21b7a98

28 files changed

+309
-222
lines changed

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module.exports = {
22
testEnvironment: "node",
33
globals: {
44
"ts-jest": {
5-
tsConfigFile: "./tsconfig.jest.json"
5+
tsConfig: "./tsconfig.jest.json"
66
}
77
},
88
transform: {

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/decorator/metadata/metadata.ts

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
import { ModelConstructor } from '../../model/model-constructor'
2+
import { Omit } from '../../model/omit.type'
23
import { SecondaryIndex } from '../impl/index/secondary-index'
34
import { KEY_MODEL } from '../impl/model/model.decorator'
45
import { ModelMetadata } from './model-metadata.model'
56
import { PropertyMetadata } from './property-metadata.model'
67

8+
export type MetadataWithSortKey<T> = Omit<Metadata<T>, 'getSortKey'> & { getSortKey: (indexName?: string) => keyof T }
9+
10+
/**
11+
* Checks if given metadata returns a sort key when calling metadata.getSortKey
12+
*/
13+
export function hasSortKey<T>(metadata: Metadata<T>): metadata is MetadataWithSortKey<T> {
14+
return metadata.getSortKey() !== null
15+
}
16+
717
export class Metadata<T> {
818
readonly modelOptions: ModelMetadata<T>
919

@@ -28,7 +38,7 @@ export class Metadata<T> {
2838
* @returns {Array<PropertyMetadata<any>>} Returns all the properties property the @PartitionKeyUUID decorator is present, returns an empty array by default
2939
*/
3040
getKeysWithUUID(): Array<PropertyMetadata<any>> {
31-
return this.filterBy(p => !!(p.key && p.key.uuid), [])
41+
return filterBy(this.modelOptions, p => !!(p.key && p.key.uuid), [])
3242
}
3343

3444
/**
@@ -46,7 +56,7 @@ export class Metadata<T> {
4656
throw new Error(`there is no index defined for name ${indexName}`)
4757
}
4858
} else {
49-
const property = this.filterByFirst(p => !!(p.key && p.key.type === 'HASH'))
59+
const property = filterByFirst(this.modelOptions, p => !!(p.key && p.key.type === 'HASH'))
5060

5161
if (property) {
5262
return property.name
@@ -74,7 +84,7 @@ export class Metadata<T> {
7484
throw new Error(`there is no index defined for name ${indexName}`)
7585
}
7686
} else {
77-
const property = this.filterByFirst(p => !!(p.key && p.key.type === 'RANGE'))
87+
const property = filterByFirst(this.modelOptions, p => !!(p.key && p.key.type === 'RANGE'))
7888
return property ? property.name : null
7989
}
8090
}
@@ -103,23 +113,27 @@ export class Metadata<T> {
103113

104114
return null
105115
}
116+
}
106117

107-
private filterBy<R>(
108-
predicate: (property: PropertyMetadata<any>) => boolean,
109-
defaultValue: R,
110-
): Array<PropertyMetadata<any>> | R {
111-
if (this.modelOptions && this.modelOptions.properties) {
112-
const properties = this.modelOptions.properties.filter(predicate)
113-
if (properties && properties.length) {
114-
return properties
115-
}
118+
function filterBy<T, R>(
119+
modelOptions: ModelMetadata<T>,
120+
predicate: (property: PropertyMetadata<any>) => boolean,
121+
defaultValue: R,
122+
): Array<PropertyMetadata<any>> | R {
123+
if (modelOptions && modelOptions.properties) {
124+
const properties = modelOptions.properties.filter(predicate)
125+
if (properties && properties.length) {
126+
return properties
116127
}
117-
118-
return defaultValue
119128
}
120129

121-
private filterByFirst(predicate: (property: PropertyMetadata<T>) => boolean): PropertyMetadata<T> | null {
122-
const properties = this.filterBy(predicate, null)
123-
return properties && properties.length ? properties[0] : null
124-
}
130+
return defaultValue
131+
}
132+
133+
function filterByFirst<T>(
134+
modelOptions: ModelMetadata<T>,
135+
predicate: (property: PropertyMetadata<T>) => boolean,
136+
): PropertyMetadata<T> | null {
137+
const properties = filterBy(modelOptions, predicate, null)
138+
return properties && properties.length ? properties[0] : null
125139
}

src/dynamo/batchget/batch-get.request.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { BatchGetItemInput } from 'aws-sdk/clients/dynamodb'
22
import { isObject, isString } from 'lodash'
33
import { Observable } from 'rxjs'
44
import { map } from 'rxjs/operators'
5+
import { hasSortKey } from '../../decorator/metadata'
56
import { metadataForClass } from '../../decorator/metadata/metadata-helper'
67
import { fromDb, toDbOne } from '../../mapper'
78
import { Attributes } from '../../mapper/type/attribute.type'
@@ -49,16 +50,16 @@ export class BatchGetRequest {
4950

5051
// loop over all the keys
5152
keys.forEach(key => {
52-
const idOb: Attributes = {}
53+
const idOb: Attributes<T> = <any>{}
5354

5455
if (isString(key)) {
5556
// got a simple primary key
5657
const value = toDbOne(key)
5758
if (value === null) {
5859
throw Error('please provide an actual value for partition key')
5960
}
60-
// FIXME: should work without cast - because keyof T must be a string or symbol (error exists since update to 2.9.x -> check in a later version, there are some open issues)
61-
idOb[<string>metadata.getPartitionKey()] = value
61+
62+
idOb[metadata.getPartitionKey()] = value
6263
} else if (isObject(key) && key.partitionKey !== undefined && key.partitionKey !== null) {
6364
// got a composite primary key
6465

@@ -67,15 +68,17 @@ export class BatchGetRequest {
6768
if (mappedPartitionKey === null) {
6869
throw Error('please provide an actual value for partition key')
6970
}
70-
idOb[<string>metadata.getPartitionKey()] = mappedPartitionKey
71+
idOb[metadata.getPartitionKey()] = mappedPartitionKey
7172

7273
// sort key
7374
const mappedSortKey = toDbOne(key.sortKey)
7475
if (mappedSortKey === null) {
7576
throw Error('please provide an actual value for partition key')
7677
}
7778

78-
idOb[<string>metadata.getSortKey()] = mappedSortKey
79+
if (hasSortKey(metadata)) {
80+
idOb[metadata.getSortKey()] = mappedSortKey
81+
}
7982
} else {
8083
throw new Error('a key must either be a string or a PrimaryKey')
8184
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export function buildFilterExpression(
8888
// check if provided values are valid for given operator
8989
validateValues(operator, values)
9090

91-
// load property metadat if model metadata was provided
91+
// load property metadata if model metadata was provided
9292
let propertyMetadata: PropertyMetadata<any> | undefined
9393
if (metadata) {
9494
propertyMetadata = metadata.forProperty(attributePath)
@@ -150,7 +150,7 @@ function buildInConditionExpression(
150150
existingValueNames: string[] | undefined,
151151
propertyMetadata: PropertyMetadata<any> | undefined,
152152
): Expression {
153-
const attributeValues: Attributes = (<any[]>values[0])
153+
const attributeValues: Attributes<any> = (<any[]>values[0])
154154
.map(value => toDbOne(value, propertyMetadata))
155155
.reduce(
156156
(result, mappedValue: Attribute | null, index: number) => {
@@ -159,7 +159,7 @@ function buildInConditionExpression(
159159
}
160160
return result
161161
},
162-
<Attributes>{},
162+
<Attributes<any>>{},
163163
)
164164

165165
const inStatement = (<any[]>values[0]).map((value: any, index: number) => `${valuePlaceholder}_${index}`).join(', ')
@@ -180,7 +180,7 @@ function buildBetweenConditionExpression(
180180
existingValueNames: string[] | undefined,
181181
propertyMetadata: PropertyMetadata<any> | undefined,
182182
): Expression {
183-
const attributes: Attributes = {}
183+
const attributes: Attributes<any> = {}
184184
const mappedValue1 = toDbOne(values[0], propertyMetadata)
185185
const mappedValue2 = toDbOne(values[1], propertyMetadata)
186186

@@ -224,7 +224,7 @@ function buildDefaultConditionExpression(
224224
statement = [namePlaceholder, operator, valuePlaceholder].join(' ')
225225
}
226226

227-
const attributes: Attributes = {}
227+
const attributes: Attributes<any> = {}
228228
if (hasValue) {
229229
let attribute: Attribute | null
230230
switch (operator) {

src/dynamo/expression/functions/resolve-attribute-value-name-conflicts.function.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function resolveAttributeValueNameConflicts(
1313
expression: Expression,
1414
params: QueryInput | ScanInput | UpdateItemInput,
1515
): Expression {
16-
let attributeValues: Attributes = {}
16+
let attributeValues: Attributes<any> = {}
1717
let statement: string = expression.statement
1818

1919
if (params.ExpressionAttributeValues) {

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

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,44 +25,55 @@ import { buildUpdateExpression } from './update-expression-builder'
2525
/**
2626
* Adds a condition to the given query.
2727
*/
28-
export function addCondition<T extends BaseRequest<any, any>>(
28+
export function addCondition<R extends BaseRequest<any, any>>(
2929
expressionType: ExpressionType,
3030
attributePath: string,
31-
request: T,
31+
request: R,
3232
metadata?: Metadata<any>,
33-
): RequestConditionFunction<T> {
33+
): RequestConditionFunction<R> {
3434
const f = (operator: ConditionOperator) => {
35-
return (...values: any[]): T => {
35+
return (...values: any[]): R => {
3636
return doAddCondition(expressionType, attributePath, request, metadata, operator, ...values)
3737
}
3838
}
3939

40-
return createConditionFunctions<RequestConditionFunction<T>>(f)
40+
return createConditionFunctions<RequestConditionFunction<R>>(f)
4141
}
4242

43-
export function addSortKeyCondition<T extends BaseRequest<any, any>>(
44-
keyName: string,
45-
request: T,
46-
metadata?: Metadata<any>,
47-
): RequestSortKeyConditionFunction<T> {
43+
export function addSortKeyCondition<R extends BaseRequest<any, any>>(
44+
keyName: keyof any,
45+
request: R,
46+
): RequestSortKeyConditionFunction<R>
47+
48+
export function addSortKeyCondition<T, R extends BaseRequest<T, any>>(
49+
keyName: keyof T,
50+
request: R,
51+
metadata: Metadata<T>,
52+
): RequestSortKeyConditionFunction<R>
53+
54+
export function addSortKeyCondition<T, R extends BaseRequest<T, any>>(
55+
keyName: keyof T,
56+
request: R,
57+
metadata?: Metadata<T>,
58+
): RequestSortKeyConditionFunction<R> {
4859
const f = (operator: ConditionOperator) => {
49-
return (...values: any[]): T => {
50-
return doAddCondition('KeyConditionExpression', keyName, request, metadata, operator, ...values)
60+
return (...values: any[]): R => {
61+
return doAddCondition('KeyConditionExpression', <string>keyName, request, metadata, operator, ...values)
5162
}
5263
}
5364

5465
// only a subset of available operators are supported for sort keys
5566
return createConditionFunctions(f, '=', '<=', '<', '>', '>=', 'begins_with', 'BETWEEN')
5667
}
5768

58-
export function doAddCondition<T extends BaseRequest<any, any>>(
69+
export function doAddCondition<T, R extends BaseRequest<T, any>>(
5970
expressionType: ExpressionType,
6071
attributePath: string,
61-
request: T,
62-
metadata: Metadata<any> | undefined,
72+
request: R,
73+
metadata: Metadata<T> | undefined,
6374
operator: ConditionOperator,
6475
...values: any[]
65-
): T {
76+
): R {
6677
const copy = [...values]
6778
const existingValueKeys = request.params.ExpressionAttributeValues
6879
? Object.keys(request.params.ExpressionAttributeValues)
@@ -73,13 +84,30 @@ export function doAddCondition<T extends BaseRequest<any, any>>(
7384
return request
7485
}
7586

76-
export function addPartitionKeyCondition<T extends BaseRequest<any, any>>(
77-
keyName: string,
87+
export function addPartitionKeyCondition<R extends BaseRequest<any, any>>(
88+
keyName: keyof any,
7889
keyValue: any,
79-
request: T,
80-
metadata?: Metadata<any>,
81-
): T {
82-
return addSortKeyCondition(keyName, request, metadata).equals(keyValue)
90+
request: R,
91+
): R
92+
93+
export function addPartitionKeyCondition<T, R extends BaseRequest<T, any>>(
94+
keyName: keyof T,
95+
keyValue: any,
96+
request: R,
97+
metadata: Metadata<T>,
98+
): R
99+
100+
export function addPartitionKeyCondition<T, R extends BaseRequest<T, any>>(
101+
keyName: keyof T,
102+
keyValue: any,
103+
request: R,
104+
metadata?: Metadata<T>,
105+
): R {
106+
if (metadata) {
107+
return addSortKeyCondition(keyName, request, metadata).equals(keyValue)
108+
} else {
109+
return addSortKeyCondition(keyName, request).equals(keyValue)
110+
}
83111
}
84112

85113
export function updateDefinitionFunction(attributePath: string): UpdateExpressionDefinitionChain

src/dynamo/expression/type/expression.type.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ import { Attributes } from '../../../mapper/type/attribute.type'
22

33
export interface Expression {
44
attributeNames: { [key: string]: string }
5-
attributeValues: Attributes
5+
attributeValues: Attributes<any>
66
statement: string
77
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ function buildDefaultExpression(
133133

134134
const hasValue = !isNoValueAction(operator.action)
135135

136-
const attributes: Attributes = {}
136+
const attributes: Attributes<any> = {}
137137
if (hasValue) {
138138
const attribute: Attribute | null = toDbOne(values[0], propertyMetadata)
139139

src/dynamo/request/base.request.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export abstract class BaseRequest<
2020
readonly params: I
2121
readonly modelClazz: ModelConstructor<T>
2222

23-
private metadata: Metadata<T>
23+
readonly metadata: Metadata<T>
2424

2525
constructor(dynamoRx: DynamoRx, modelClazz: ModelConstructor<T>, tableName: string) {
2626
this.dynamoRx = dynamoRx
@@ -33,14 +33,7 @@ export abstract class BaseRequest<
3333
this.params = <I>{
3434
TableName: tableName,
3535
}
36-
}
37-
38-
get metaData(): Metadata<T> {
39-
if (!this.metadata) {
40-
this.metadata = metadataForClass(this.modelClazz)
41-
}
42-
43-
return this.metadata
36+
this.metadata = metadataForClass(this.modelClazz)
4437
}
4538

4639
abstract execFullResponse(): Observable<any>

0 commit comments

Comments
 (0)