Skip to content

Commit

Permalink
Merge pull request #59 from shiftcode/key-members-validation
Browse files Browse the repository at this point in the history
Key members validation
  • Loading branch information
michaelwittwer authored Oct 26, 2018
2 parents 9785fe7 + ae5f642 commit b348b3b
Show file tree
Hide file tree
Showing 55 changed files with 683 additions and 448 deletions.
5 changes: 4 additions & 1 deletion src/decorator/impl/mapper/custom-mapper.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { MapperForType } from '../../../mapper/for-type/base.mapper'
import { Attribute } from '../../../mapper/type/attribute.type'
import { ModelConstructor } from '../../../model/model-constructor'
import { initOrUpdateProperty } from '../property/property.decorator'

export function CustomMapper(mapperClazz: ModelConstructor<MapperForType<any>>): PropertyDecorator {
export function CustomMapper<T extends Attribute>(
mapperClazz: ModelConstructor<MapperForType<any, T>>
): PropertyDecorator {
return (target: any, propertyKey: string | symbol) => {
if (typeof propertyKey === 'string') {
initOrUpdateProperty({ mapper: mapperClazz }, target, propertyKey)
Expand Down
14 changes: 9 additions & 5 deletions src/decorator/impl/property/property.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { KeyType } from 'aws-sdk/clients/dynamodb'
import { DynamoEasyConfig } from '../../../config/dynamo-easy-config'
import { AttributeModelType } from '../../../mapper/type/attribute-model.type'
import { AttributeValueType } from '../../../mapper/type/attribute-value-type.type'
import { Attribute } from '../../../mapper/type/attribute.type'
import { MomentType } from '../../../mapper/type/moment.type'
import { Util } from '../../../mapper/util'
import { PropertyMetadata, TypeInfo } from '../../metadata/property-metadata.model'
Expand Down Expand Up @@ -74,7 +75,7 @@ function initOrUpdateLSI(indexes: string[], indexData: IndexData): Partial<Prope
}

export function initOrUpdateProperty(
propertyMetadata: Partial<PropertyMetadata<any>> = {},
propertyMetadata: Partial<PropertyMetadata<any, Attribute>> = {},
target: any,
propertyKey: string
): void {
Expand All @@ -86,7 +87,10 @@ export function initOrUpdateProperty(
if (existingProperty) {
// merge property options
// console.log('merge into existing property', existingProperty, propertyMetadata);
Object.assign<PropertyMetadata<any>, Partial<PropertyMetadata<any>>>(existingProperty, propertyMetadata)
Object.assign<PropertyMetadata<any, Attribute>, Partial<PropertyMetadata<any, Attribute>>>(
existingProperty,
propertyMetadata
)
} else {
// add new options
const newProperty: PropertyMetadata<any> = createNewProperty(propertyMetadata, target, propertyKey)
Expand All @@ -98,11 +102,11 @@ export function initOrUpdateProperty(
}

function createNewProperty(
propertyOptions: Partial<PropertyMetadata<any>> = {},
propertyOptions: Partial<PropertyMetadata<any, Attribute>> = {},
target: any,
propertyKey: string
): PropertyMetadata<any> {
let propertyType: AttributeModelType = getMetadataType(target, propertyKey)
let propertyType: AttributeValueType = getMetadataType(target, propertyKey)
let customType = isCustomType(propertyType)

const typeByConvention = Util.typeByConvention(propertyKey)
Expand Down
7 changes: 4 additions & 3 deletions src/decorator/metadata/property-metadata.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { KeyType } from 'aws-sdk/clients/dynamodb'
import { MapperForType } from '../../mapper/for-type/base.mapper'
import { Attribute } from '../../mapper/type/attribute.type'
import { ModelConstructor } from '../../model/model-constructor'

export interface TypeInfo {
Expand All @@ -14,7 +15,7 @@ export interface Key {
uuid?: boolean
}

export interface PropertyMetadata<T> {
export interface PropertyMetadata<T, R extends Attribute = Attribute> {
// this property desribes a key attribute (either partition or sort) for the table
key?: Key

Expand All @@ -35,7 +36,7 @@ export interface PropertyMetadata<T> {
*/
isSortedCollection?: boolean

mapper?: ModelConstructor<MapperForType<any>>
mapper?: ModelConstructor<MapperForType<any, R>>

// maps the index name to the key type to describe for which GSI this property describes a key attribute
keyForGSI?: { [key: string]: KeyType }
Expand All @@ -47,6 +48,6 @@ export interface PropertyMetadata<T> {
transient?: boolean
}

export function hasGenericType(propertyMetadata?: PropertyMetadata<any>): boolean {
export function hasGenericType(propertyMetadata?: PropertyMetadata<any, any>): boolean {
return !!(propertyMetadata && propertyMetadata.typeInfo && propertyMetadata.typeInfo.genericType)
}
13 changes: 7 additions & 6 deletions src/dynamo/batchget/batch-get.request.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AttributeMap, BatchGetItemInput } from 'aws-sdk/clients/dynamodb'
import { BatchGetItemInput } from 'aws-sdk/clients/dynamodb'
import { isObject, isString } from 'lodash'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { MetadataHelper } from '../../decorator/metadata/metadata-helper'
import { Mapper } from '../../mapper/mapper'
import { Attributes } from '../../mapper/type/attribute.type'
import { ModelConstructor } from '../../model/model-constructor'
import { DEFAULT_SESSION_VALIDITY_ENSURER } from '../default-session-validity-ensurer.const'
import { DEFAULT_TABLE_NAME_RESOLVER } from '../default-table-name-resolver.const'
Expand Down Expand Up @@ -44,11 +45,11 @@ export class BatchGetRequest {
this.tables.set(tableName, modelClazz)

const metadata = MetadataHelper.get(modelClazz)
const attributeMaps: AttributeMap[] = []
const attributeMaps: Attributes[] = []

// loop over all the keys
keys.forEach(key => {
const idOb: AttributeMap = {}
const idOb: Attributes = {}

if (isString(key)) {
// got a simple primary key
Expand Down Expand Up @@ -100,8 +101,8 @@ export class BatchGetRequest {

if (response.Responses && Object.keys(response.Responses).length) {
Object.keys(response.Responses).forEach(tableName => {
const mapped = response.Responses![tableName].map(attributeMap =>
Mapper.fromDb(attributeMap, this.tables.get(tableName))
const mapped = response.Responses![tableName].map(attributes =>
Mapper.fromDb(<Attributes>attributes, this.tables.get(tableName))
)
r.Responses![tableName] = mapped
})
Expand All @@ -119,7 +120,7 @@ export class BatchGetRequest {
if (response.Responses && Object.keys(response.Responses).length) {
Object.keys(response.Responses).forEach(tableName => {
const mapped = response.Responses![tableName].map(attributeMap =>
Mapper.fromDb(attributeMap, this.tables.get(tableName))
Mapper.fromDb(<Attributes>attributeMap, this.tables.get(tableName))
)
r[tableName] = mapped
})
Expand Down
44 changes: 21 additions & 23 deletions src/dynamo/expression/condition-expression-builder.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AttributeMap, AttributeValue } from 'aws-sdk/clients/dynamodb'
import { curryRight, forEach, isPlainObject } from 'lodash'
import { Metadata } from '../../decorator/metadata/metadata'
import { PropertyMetadata } from '../../decorator/metadata/property-metadata.model'
import { Mapper } from '../../mapper/mapper'
import { Attribute, Attributes } from '../../mapper/type/attribute.type'
import { Util } from '../../mapper/util'
import { resolveAttributeNames } from './functions/attribute-names.function'
import { isFunctionOperator } from './functions/is-function-operator.function'
Expand Down Expand Up @@ -151,17 +151,15 @@ export class ConditionExpressionBuilder {
existingValueNames: string[] | undefined,
propertyMetadata: PropertyMetadata<any> | undefined
): Expression {
const attributeValues: AttributeMap = (<any[]>values[0])
.map(value => Mapper.toDbOne(value, propertyMetadata))
.reduce(
(result, mappedValue: AttributeValue | null, index: number) => {
if (mappedValue !== null) {
result[`${valuePlaceholder}_${index}`] = mappedValue
}
return result
},
<AttributeMap>{}
)
const attributeValues: Attributes = (<any[]>values[0]).map(value => Mapper.toDbOne(value, propertyMetadata)).reduce(
(result, mappedValue: Attribute | null, index: number) => {
if (mappedValue !== null) {
result[`${valuePlaceholder}_${index}`] = mappedValue
}
return result
},
<Attributes>{}
)

const inStatement = (<any[]>values[0]).map((value: any, index: number) => `${valuePlaceholder}_${index}`).join(', ')

Expand All @@ -181,7 +179,7 @@ export class ConditionExpressionBuilder {
existingValueNames: string[] | undefined,
propertyMetadata: PropertyMetadata<any> | undefined
): Expression {
const attributeValues: AttributeMap = {}
const attributes: Attributes = {}
const mappedValue1 = Mapper.toDbOne(values[0], propertyMetadata)
const mappedValue2 = Mapper.toDbOne(values[1], propertyMetadata)

Expand All @@ -192,13 +190,13 @@ export class ConditionExpressionBuilder {
const value2Placeholder = uniqAttributeValueName(attributePath, [valuePlaceholder].concat(existingValueNames || []))

const statement = `${namePlaceholder} BETWEEN ${valuePlaceholder} AND ${value2Placeholder}`
attributeValues[valuePlaceholder] = mappedValue1
attributeValues[value2Placeholder] = mappedValue2
attributes[valuePlaceholder] = mappedValue1
attributes[value2Placeholder] = mappedValue2

return {
statement,
attributeNames,
attributeValues,
attributeValues: attributes,
}
}

Expand All @@ -225,28 +223,28 @@ export class ConditionExpressionBuilder {
statement = [namePlaceholder, operator, valuePlaceholder].join(' ')
}

const attributeValues: AttributeMap = {}
const attributes: Attributes = {}
if (hasValue) {
let value: AttributeValue | null
let attribute: Attribute | null
switch (operator) {
case 'contains':
// TODO think about validation
// ConditionExpressionBuilder.validateValueForContains(values[0], propertyMetadata)
value = Mapper.toDbOne(values[0], propertyMetadata)
attribute = Mapper.toDbOne(values[0], propertyMetadata)
break
default:
value = Mapper.toDbOne(values[0], propertyMetadata)
attribute = Mapper.toDbOne(values[0], propertyMetadata)
}

if (value) {
attributeValues[valuePlaceholder] = value
if (attribute) {
attributes[valuePlaceholder] = attribute
}
}

return {
statement,
attributeNames,
attributeValues,
attributeValues: attributes,
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/dynamo/expression/type/condition-expression-chain.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AttributeType } from '../../../mapper/type/attribute.type'
import { AttributeType } from '../../../mapper/type/attribute-type.type'
import { Expression } from './expression.type'

export interface ConditionExpressionChain {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AttributeType } from '../../../mapper/type/attribute.type'
import { AttributeType } from '../../../mapper/type/attribute-type.type'
import { ConditionExpressionDefinitionFunction } from './condition-expression-definition-function'

export interface ConditionExpressionDefinitionChain {
Expand Down
4 changes: 2 additions & 2 deletions src/dynamo/expression/type/expression.type.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AttributeMap } from 'aws-sdk/clients/dynamodb'
import { Attributes } from '../../../mapper/type/attribute.type'

export interface Expression {
attributeNames: { [key: string]: string }
attributeValues: AttributeMap
attributeValues: Attributes
statement: string
}
2 changes: 1 addition & 1 deletion src/dynamo/expression/type/request-condition-function.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AttributeType } from '../../../mapper/type/attribute.type'
import { AttributeType } from '../../../mapper/type/attribute-type.type'
import { BaseRequest } from '../../request/base.request'

export interface RequestConditionFunction<T extends BaseRequest<any, any>> {
Expand Down
12 changes: 6 additions & 6 deletions src/dynamo/expression/update-expression-builder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AttributeMap, AttributeValue } from 'aws-sdk/clients/dynamodb'
import { Metadata } from '../../decorator/metadata/metadata'
import { PropertyMetadata } from '../../decorator/metadata/property-metadata.model'
import { Mapper } from '../../mapper/mapper'
import { Attribute, Attributes } from '../../mapper/type/attribute.type'
import { Util } from '../../mapper/util'
import { ConditionExpressionBuilder } from './condition-expression-builder'
import { resolveAttributeNames } from './functions/attribute-names.function'
Expand Down Expand Up @@ -137,20 +137,20 @@ export class UpdateExpressionBuilder {

const hasValue = !UpdateExpressionBuilder.isNoValueAction(operator.action)

const attributeValues: AttributeMap = {}
const attributes: Attributes = {}
if (hasValue) {
const value: AttributeValue | null = Mapper.toDbOne(values[0], propertyMetadata)
const attribute: Attribute | null = Mapper.toDbOne(values[0], propertyMetadata)

if (value) {
attributeValues[valuePlaceholder] = value
if (attribute) {
attributes[valuePlaceholder] = attribute
}
}

return {
type: operator.actionKeyword,
statement,
attributeNames,
attributeValues,
attributeValues: attributes,
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { AttributeMap, BatchGetItemInput } from 'aws-sdk/clients/dynamodb'
import { BatchGetItemInput } from 'aws-sdk/clients/dynamodb'
import { isObject } from 'lodash'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { Metadata } from '../../../decorator/metadata/metadata'
import { MetadataHelper } from '../../../decorator/metadata/metadata-helper'
import { Mapper } from '../../../mapper/mapper'
import { Attributes } from '../../../mapper/type/attribute.type'
import { ModelConstructor } from '../../../model/model-constructor'
import { DynamoRx } from '../../dynamo-rx'
import { BatchGetSingleTableResponse } from './batch-get-single-table.response'
Expand Down Expand Up @@ -49,7 +50,7 @@ export class BatchGetSingleTableRequest<T> {
let items: T[]
if (response.Responses && Object.keys(response.Responses).length && response.Responses[this.tableName]) {
const mapped: T[] = response.Responses![this.tableName].map(attributeMap =>
Mapper.fromDb(attributeMap, this.modelClazz)
Mapper.fromDb(<Attributes>attributeMap, this.modelClazz)
)
items = mapped
} else {
Expand All @@ -69,7 +70,9 @@ export class BatchGetSingleTableRequest<T> {
return this.dynamoRx.batchGetItems(this.params).pipe(
map(response => {
if (response.Responses && Object.keys(response.Responses).length && response.Responses[this.tableName]) {
return response.Responses![this.tableName].map(attributeMap => Mapper.fromDb(attributeMap, this.modelClazz))
return response.Responses![this.tableName].map(attributeMap =>
Mapper.fromDb(<Attributes>attributeMap, this.modelClazz)
)
} else {
return []
}
Expand All @@ -78,10 +81,10 @@ export class BatchGetSingleTableRequest<T> {
}

private addKeyParams(keys: any[]) {
const attributeMaps: AttributeMap[] = []
const attributeMaps: Attributes[] = []

keys.forEach(key => {
const idOb: AttributeMap = {}
const idOb: Attributes = {}
if (isObject(key)) {
// TODO add some more checks
// got a composite primary key
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import {
AttributeMap,
BatchWriteItemInput,
BatchWriteItemOutput,
WriteRequest,
WriteRequests,
} from 'aws-sdk/clients/dynamodb'
import { BatchWriteItemInput, BatchWriteItemOutput, WriteRequest, WriteRequests } from 'aws-sdk/clients/dynamodb'
import { Observable, of } from 'rxjs'
import { delay, map, mergeMap, tap } from 'rxjs/operators'
import { DynamoRx } from '../../../dynamo/dynamo-rx'
import { randomExponentialBackoffTimer } from '../../../helper'
import { Mapper } from '../../../mapper'
import { Attributes } from '../../../mapper/type/attribute.type'
import { ModelConstructor } from '../../../model/model-constructor'
import { BatchWriteSingleTableResponse } from './batch-write-single-table.response'

const MAX_BATCH_WRITE_ITEMS = 25

export class BatchWriteSingleTableRequest<T> {
private get toKey(): (item: T) => AttributeMap {
private get toKey(): (item: T) => Attributes {
if (!this._keyFn) {
this._keyFn = Mapper.createToKeyFn(this.modelClazz)
}
Expand Down
4 changes: 2 additions & 2 deletions src/dynamo/request/delete/delete.request.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
AttributeMap,
DeleteItemInput,
DeleteItemOutput,
ReturnConsumedCapacity,
Expand All @@ -8,6 +7,7 @@ import {
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { Mapper } from '../../../mapper/mapper'
import { Attributes } from '../../../mapper/type/attribute.type'
import { ModelConstructor } from '../../../model/model-constructor'
import { DynamoRx } from '../../dynamo-rx'
import { and } from '../../expression/logical-operator/and.function'
Expand All @@ -33,7 +33,7 @@ export class DeleteRequest<T> extends BaseRequest<T, DeleteItemInput> {
throw new Error(`please provide the sort key for attribute ${this.metaData.getSortKey()}`)
}

const keyAttributeMap: AttributeMap = {}
const keyAttributeMap: Attributes = {}

// partition key
const partitionKeyValue = Mapper.toDbOne(partitionKey, this.metaData.forProperty(this.metaData.getPartitionKey()))
Expand Down
Loading

0 comments on commit b348b3b

Please sign in to comment.