Skip to content

Commit

Permalink
Merge pull request #268 from shiftcode/#91-default-value-provider
Browse files Browse the repository at this point in the history
#91 default value provider
  • Loading branch information
michaelwittwer authored Sep 3, 2020
2 parents 7420346 + f553e20 commit 9637207
Show file tree
Hide file tree
Showing 19 changed files with 104 additions and 71 deletions.
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: "#91-default-value-provider"
channel: pr91
prerelease: pr91
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
24 changes: 16 additions & 8 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 @@ -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
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.

40 changes: 40 additions & 0 deletions test/models/model-with-custom-mapper-and-default-value.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// tslint:disable:max-classes-per-file
import { Model } from '../../src/decorator/impl/model/model.decorator'
import { Property } from '../../src/decorator/impl/property/property.decorator'
import { MapperForType } from '../../src/mapper/for-type/base.mapper'
import { StringAttribute } from '../../src/mapper/type/attribute.type'

export class MyProp {
static default() {
return new MyProp('default', 'none')
}

static parse(v: string) {
const p = v.split('-')
return new MyProp(p[0], p[1])
}

static toString(v: MyProp) {
return `${v.type}-${v.name}`
}

constructor(public type: string, public name: string) {}

toString() {
return MyProp.toString(this)
}
}

const myPropMapper: MapperForType<MyProp, StringAttribute> = {
fromDb: a => MyProp.parse(a.S),
toDb: v => ({ S: MyProp.toString(v) }),
}

@Model()
export class ModelWithCustomMapperAndDefaultValue {
@Property({
mapper: myPropMapper,
defaultValueProvider: () => MyProp.default(),
})
myProp: MyProp
}
8 changes: 8 additions & 0 deletions test/models/model-with-default-value.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Model, PartitionKey, Property } from '../../src/dynamo-easy'

@Model()
export class ModelWithDefaultValue {
@PartitionKey()
@Property({ defaultValueProvider: () => `generated-id-${Math.floor(Math.random() * 1000)}` })
id: string
}

0 comments on commit 9637207

Please sign in to comment.