Skip to content

Next #326

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 3, 2020
7 changes: 5 additions & 2 deletions .releaserc.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# run semantic-release on master branch
branch: master
branches:
- name: master
- name: next
channel: next
prerelease: next
9 changes: 0 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -59,7 +59,6 @@
"@commitlint/prompt-cli": "^8.1.0",
"@types/jest": "^25.2.1",
"@types/node": "8.10.40",
"@types/uuid": "^3.4.5",
"aws-sdk": "^2.401.0",
"colors": "^1.3.3",
"coveralls": "^3.0.6",
@@ -79,13 +78,11 @@
"tsutils": "^3.17.1",
"typedoc": "0.14.0",
"typedoc-plugin-external-module-name": "^2.1.0",
"typescript": ">=2.9.1",
"uuid": "^3.3.2"
"typescript": ">=2.9.1"
},
"peerDependencies": {
"aws-sdk": "^2.401.0",
"reflect-metadata": "^0.1.12",
"tslib": "^1.10.0",
"uuid": "^3.3.2"
"tslib": "^1.10.0"
}
}
2 changes: 0 additions & 2 deletions src/decorator/decorators.spec.ts
Original file line number Diff line number Diff line change
@@ -117,7 +117,6 @@ describe('Decorators should add correct metadata', () => {
expect(prop!.nameDb).toBe('id')
expect(prop!.key).toBeDefined()
expect(prop!.key!.type).toBe('HASH')
expect(prop!.key!.uuid).toBeFalsy()
expect(prop!.transient).toBeFalsy()
expect(prop!.typeInfo).toBeDefined()
expect(prop!.typeInfo!.type).toBe(String)
@@ -130,7 +129,6 @@ describe('Decorators should add correct metadata', () => {
expect(prop!.nameDb).toBe('creationDate')
expect(prop!.key).toBeDefined()
expect(prop!.key!.type).toBe('RANGE')
expect(prop!.key!.uuid).toBeFalsy()
expect(prop!.transient).toBeFalsy()
expect(prop!.typeInfo).toBeDefined()
})
12 changes: 0 additions & 12 deletions src/decorator/impl/key/partition-key-uuid.decorator.ts

This file was deleted.

9 changes: 6 additions & 3 deletions src/decorator/impl/property/property-data.model.ts
Original file line number Diff line number Diff line change
@@ -6,8 +6,11 @@ import { MapperForType } from '../../../mapper/for-type/base.mapper'
/**
* Option interface for @Property decorator
*/
export interface PropertyData {
// the name of property how it is named in dynamoDB
export interface PropertyData<T> {
/**
* the name of property how it is named in dynamoDB
*/
name: string
mapper: MapperForType<any, any>
mapper: MapperForType<T, any>
defaultValueProvider: () => T
}
3 changes: 2 additions & 1 deletion src/decorator/impl/property/property.decorator.ts
Original file line number Diff line number Diff line change
@@ -5,12 +5,13 @@ import { PropertyMetadata } from '../../metadata/property-metadata.model'
import { initOrUpdateProperty } from './init-or-update-property.function'
import { PropertyData } from './property-data.model'

export function Property(opts: Partial<PropertyData> = {}): PropertyDecorator {
export function Property<T>(opts: Partial<PropertyData<T>> = {}): PropertyDecorator {
return (target: object, propertyKey: string | symbol) => {
if (typeof propertyKey === 'string') {
const propertyOptions: Partial<PropertyMetadata<any>> = {
name: propertyKey,
nameDb: opts.name || propertyKey,
defaultValueProvider: opts.defaultValueProvider,
}

if ('mapper' in opts && !!opts.mapper) {
1 change: 0 additions & 1 deletion src/decorator/impl/public-api.ts
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@ export * from './index/lsi-sort-key.decorator'
export * from './index/index-type.enum'
// key
export * from './key/partition-key.decorator'
export * from './key/partition-key-uuid.decorator'
export * from './key/sort-key.decorator'

// model
19 changes: 10 additions & 9 deletions src/decorator/metadata/metadata.spec.ts
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import {
INDEX_ACTIVE_CREATED_AT,
INDEX_COUNT,
ModelWithABunchOfIndexes,
ModelWithAutogeneratedId,
ModelWithDefaultValue,
ModelWithGSI,
ModelWithLSI,
ModelWithoutPartitionKeyModel,
@@ -22,7 +22,7 @@ describe('metadata', () => {
let metaDataLsi: Metadata<ModelWithLSI>
let metaDataGsi: Metadata<ModelWithGSI>
let metaDataIndexes: Metadata<ModelWithABunchOfIndexes>
let metaDataUuid: Metadata<ModelWithAutogeneratedId>
let metaDataDefaultValue: Metadata<ModelWithDefaultValue>
let metaDataComplex: Metadata<ComplexModel>

beforeEach(() => {
@@ -32,7 +32,7 @@ describe('metadata', () => {
metaDataLsi = new Metadata(ModelWithLSI)
metaDataGsi = new Metadata(ModelWithGSI)
metaDataIndexes = new Metadata(ModelWithABunchOfIndexes)
metaDataUuid = new Metadata(ModelWithAutogeneratedId)
metaDataDefaultValue = new Metadata(ModelWithDefaultValue)
metaDataComplex = new Metadata(ComplexModel)
})

@@ -61,12 +61,13 @@ describe('metadata', () => {
expect(nestedObjDateMeta).toBeUndefined()
})

it('getKeysWithUUID', () => {
const uuid = metaDataUuid.getKeysWithUUID()
expect(uuid.length).toBe(1)
expect(uuid[0].key).toBeDefined()
expect(uuid[0].key!.uuid).toBeTruthy()
expect(uuid[0].name).toBe('id')
it('getPropertiesWithDefaultValueProvider', () => {
const props = metaDataDefaultValue.getPropertiesWithDefaultValueProvider()
expect(props.length).toBe(1)
expect(props[0].key).toBeDefined()
expect(props[0].name).toBe('id')
expect(props[0].defaultValueProvider).toBeDefined()
expect(props[0].defaultValueProvider!()).toBeDefined()
})

it('getPartitionKey', () => {
6 changes: 3 additions & 3 deletions src/decorator/metadata/metadata.ts
Original file line number Diff line number Diff line change
@@ -70,10 +70,10 @@ export class Metadata<T> {

/**
*
* @returns {Array<PropertyMetadata<any>>} Returns all the properties property the @PartitionKeyUUID decorator is present, returns an empty array by default
* @returns {Array<PropertyMetadata<any>>} Returns all the properties a defaultValueProvider, returns an empty array by default
*/
getKeysWithUUID(): Array<PropertyMetadata<any>> {
return filterBy(this.modelOptions, (p) => !!(p.key && p.key.uuid), [])
getPropertiesWithDefaultValueProvider(): Array<PropertyMetadata<any>> {
return filterBy(this.modelOptions, p => !!p.defaultValueProvider, [])
}

/**
3 changes: 2 additions & 1 deletion src/decorator/metadata/property-metadata.model.ts
Original file line number Diff line number Diff line change
@@ -13,7 +13,6 @@ export interface TypeInfo {

export interface Key {
type: DynamoDB.KeyType
uuid?: boolean
}

export interface PropertyMetadata<T, R extends Attribute = Attribute> {
@@ -49,6 +48,8 @@ export interface PropertyMetadata<T, R extends Attribute = Attribute> {

// index?: IModelAttributeIndex
transient?: boolean

defaultValueProvider?: () => any
}

/**
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ import {
* @Model()
* class Person{
*
* @PartitionKeyUUID()
* @PartitionKey()
* id: string
* age: number
* }
2 changes: 1 addition & 1 deletion src/dynamo/expression/logical-operator/update.function.ts
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ import {
* @Model()
* class Person {
*
* @PartitionKeyUUID()
* @PartitionKey()
* id: string
* age: number
* }
6 changes: 5 additions & 1 deletion src/mapper/for-type/string.mapper.spec.ts
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ describe('string mapper', () => {

it('should work (empty string)', () => {
const attributeValue = StringMapper.toDb('')
expect(attributeValue).toBe(null)
expect(attributeValue).toStrictEqual({ S: '' })
})

it('should work (null)', () => {
@@ -28,6 +28,10 @@ describe('string mapper', () => {
const stringValue = StringMapper.fromDb({ S: 'myStringValue' })
expect(stringValue).toBe('myStringValue')
})
it('should allow empty string values', () => {
const stringValue = StringMapper.fromDb({ S: '' })
expect(stringValue).toBe('')
})
it('should throw if not a string attribute', () => {
expect(() => StringMapper.fromDb(<any>{ N: '8' })).toThrow()
})
6 changes: 3 additions & 3 deletions src/mapper/for-type/string.mapper.ts
Original file line number Diff line number Diff line change
@@ -5,16 +5,16 @@ import { StringAttribute } from '../type/attribute.type'
import { MapperForType } from './base.mapper'

function stringFromDb(attributeValue: StringAttribute): string {
if (attributeValue.S) {
if (attributeValue.S || attributeValue.S === '') {
return attributeValue.S
} else {
throw new Error(`there is no S(tring) value defined on given attribute value: ${JSON.stringify(attributeValue)}`)
}
}

function stringToDb(modelValue: string): StringAttribute | null {
// an empty string is not a valid value for string attribute
if (modelValue === '' || modelValue === null || modelValue === undefined) {
// an empty string is valid for a string attribute
if (modelValue === null || modelValue === undefined) {
return null
} else {
return { S: modelValue }
31 changes: 20 additions & 11 deletions src/mapper/mapper.spec.ts
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ import {
Employee,
Gift,
Id,
ModelWithAutogeneratedId,
ModelWithDefaultValue,
ModelWithCustomMapperModel,
ModelWithDateAsHashKey,
ModelWithDateAsIndexHashKey,
@@ -32,6 +32,8 @@ import {
SimpleWithRenamedPartitionKeyModel,
StringType,
Type,
ModelWithCustomMapperAndDefaultValue,
MyProp,
} from '../../test/models'
import { IdMapper } from '../../test/models/model-with-custom-mapper.model'
import { ModelWithEmptyValues } from '../../test/models/model-with-empty-values'
@@ -67,7 +69,7 @@ describe('Mapper', () => {

it('string (empty)', () => {
const attrValue = <StringAttribute>toDbOne('')!
expect(attrValue).toBeNull()
expect(attrValue.S).toStrictEqual('')
})

it('number', () => {
@@ -684,15 +686,21 @@ describe('Mapper', () => {
})
})

describe('model with autogenerated id', () => {
it('should create an uuid', () => {
const toDbVal = toDb(new ModelWithAutogeneratedId(), ModelWithAutogeneratedId)
describe('model with autogenerated value for id', () => {
it('should create an id', () => {
const toDbVal = toDb(new ModelWithDefaultValue(), ModelWithDefaultValue)
expect(toDbVal.id).toBeDefined()
expect(keyOf(toDbVal.id)).toBe('S')
// https://stackoverflow.com/questions/7905929/how-to-test-valid-uuid-guid
expect((<StringAttribute>toDbVal.id).S).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
)
expect((<StringAttribute>toDbVal.id).S).toMatch(/^generated-id-\d{1,3}$/)
})
})

describe('model with default value provider AND custom mapper', () => {
it('should create correct value', () => {
const toDbVal = toDb(new ModelWithCustomMapperAndDefaultValue(), ModelWithCustomMapperAndDefaultValue)
expect(toDbVal.myProp).toBeDefined()
expect(keyOf(toDbVal.myProp)).toBe('S')
expect((<StringAttribute>toDbVal.myProp).S).toBe(MyProp.default().toString())
})
})

@@ -801,7 +809,7 @@ describe('Mapper', () => {
// OK
id: 'myId',

// x -> empty strings are not valid
// x -> empty strings are valid
name: '',

// x -> empty set is not valid
@@ -824,7 +832,8 @@ describe('Mapper', () => {
expect(toDbValue.id).toBeDefined()
expect(keyOf(toDbValue.id)).toBe('S')

expect(toDbValue.name).toBeUndefined()
expect(toDbValue.name).toBeDefined()
expect(keyOf(toDbValue.name)).toBe('S')

expect(toDbValue.roles).toBeUndefined()

11 changes: 6 additions & 5 deletions src/mapper/mapper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* @module mapper
*/
import { v4 as uuidv4 } from 'uuid'
import { hasSortKey, Metadata } from '../decorator/metadata/metadata'
import { metadataForModel } from '../decorator/metadata/metadata-for-model.function'
import { hasType, Key, PropertyMetadata } from '../decorator/metadata/property-metadata.model'
@@ -42,12 +41,14 @@ export function toDb<T>(item: T, modelConstructor?: ModelConstructor<T>): Attrib
const metadata: Metadata<T> = metadataForModel(modelConstructor)

/*
* initialize possible properties with auto generated uuid
* initialize possible properties with default value providers
*/
if (metadata) {
metadata.getKeysWithUUID().forEach((propertyMetadata) => {
if (!Reflect.get(<any>item, propertyMetadata.name)) {
Reflect.set(<any>item, propertyMetadata.name, uuidv4())
metadata.getPropertiesWithDefaultValueProvider().forEach(propertyMetadata => {
const currentVal = Reflect.get(<any>item, propertyMetadata.name)
if (currentVal === undefined || currentVal === null) {
// tslint:disable-next-line:no-non-null-assertion
Reflect.set(<any>item, propertyMetadata.name, propertyMetadata.defaultValueProvider!())
}
})
}
3 changes: 2 additions & 1 deletion test/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export * from './complex.model'
export * from './custom-table-name.model'
export * from './employee.model'
export * from './model-with-autogenerated-id.model'
export * from './model-with-default-value.model'
export * from './model-with-custom-mapper.model'
export * from './model-with-custom-mapper-for-sort-key.model'
export * from './model-with-custom-mapper-and-default-value.model'
export * from './model-with-date.model'
export * from './model-with-enum.model'
export * from './model-with-indexes.model'
7 changes: 0 additions & 7 deletions test/models/model-with-autogenerated-id.model.ts

This file was deleted.

Loading