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
Merged

Next #326

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Expand Up @@ -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",
Expand All @@ -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
Expand Up @@ -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)
Expand All @@ -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()
})
Expand Down
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
Expand Up @@ -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
Expand Up @@ -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) {
Expand Down
1 change: 0 additions & 1 deletion src/decorator/impl/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 10 additions & 9 deletions src/decorator/metadata/metadata.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
INDEX_ACTIVE_CREATED_AT,
INDEX_COUNT,
ModelWithABunchOfIndexes,
ModelWithAutogeneratedId,
ModelWithDefaultValue,
ModelWithGSI,
ModelWithLSI,
ModelWithoutPartitionKeyModel,
Expand All @@ -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(() => {
Expand All @@ -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)
})

Expand Down Expand Up @@ -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', () => {
Expand Down
6 changes: 3 additions & 3 deletions src/decorator/metadata/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, [])
}

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

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

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

// index?: IModelAttributeIndex
transient?: boolean

defaultValueProvider?: () => any
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
* @Model()
* class Person{
*
* @PartitionKeyUUID()
* @PartitionKey()
* id: string
* age: number
* }
Expand Down
2 changes: 1 addition & 1 deletion src/dynamo/expression/logical-operator/update.function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
* @Model()
* class Person {
*
* @PartitionKeyUUID()
* @PartitionKey()
* id: string
* age: number
* }
Expand Down
6 changes: 5 additions & 1 deletion src/mapper/for-type/string.mapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)', () => {
Expand All @@ -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()
})
Expand Down
6 changes: 3 additions & 3 deletions src/mapper/for-type/string.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
31 changes: 20 additions & 11 deletions src/mapper/mapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
Employee,
Gift,
Id,
ModelWithAutogeneratedId,
ModelWithDefaultValue,
ModelWithCustomMapperModel,
ModelWithDateAsHashKey,
ModelWithDateAsIndexHashKey,
Expand All @@ -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'
Expand Down Expand Up @@ -67,7 +69,7 @@ describe('Mapper', () => {

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

it('number', () => {
Expand Down Expand Up @@ -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())
})
})

Expand Down Expand Up @@ -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
Expand All @@ -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()

Expand Down
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'
Expand Down Expand Up @@ -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!())
}
})
}
Expand Down
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'
Expand Down
7 changes: 0 additions & 7 deletions test/models/model-with-autogenerated-id.model.ts

This file was deleted.

Loading