From 0a9b5145621fda59a288711fd9e2dc82c78229db Mon Sep 17 00:00:00 2001 From: Simon Mumenthaler Date: Sat, 11 Jan 2020 15:57:38 +0100 Subject: [PATCH 1/3] feat(decorator): defaultValueProvider for @Property() With a defaultValueProvider you're able to have properties like createdAt without the necessity to set the date by hand. With this we also dropped the UUID as peerDependency. BREAKING CHANGE: `@PartitionKeyUUID()` does no longer exist. Use `@Property({ defaultValueProvider: uuidGeneratorFunction })` instead --- package.json | 7 +--- src/decorator/decorators.spec.ts | 2 - .../impl/key/partition-key-uuid.decorator.ts | 12 ------ .../impl/property/property-data.model.ts | 9 +++-- .../impl/property/property.decorator.ts | 3 +- src/decorator/impl/public-api.ts | 1 - src/decorator/metadata/metadata.spec.ts | 19 ++++----- src/decorator/metadata/metadata.ts | 6 +-- .../metadata/property-metadata.model.ts | 3 +- .../logical-operator/attribute.function.ts | 2 +- .../logical-operator/update.function.ts | 2 +- src/mapper/mapper.spec.ts | 24 +++++++---- src/mapper/mapper.ts | 11 ++--- test/models/index.ts | 3 +- .../model-with-autogenerated-id.model.ts | 7 ---- ...h-custom-mapper-and-default-value.model.ts | 40 +++++++++++++++++++ test/models/model-with-default-value.model.ts | 8 ++++ 17 files changed, 99 insertions(+), 60 deletions(-) delete mode 100644 src/decorator/impl/key/partition-key-uuid.decorator.ts delete mode 100644 test/models/model-with-autogenerated-id.model.ts create mode 100644 test/models/model-with-custom-mapper-and-default-value.model.ts create mode 100644 test/models/model-with-default-value.model.ts diff --git a/package.json b/package.json index 7209a6f5a..31b618420 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "@types/jest": "^24.0.18", "@types/lodash": "^4.14.137", "@types/node": "8.10.40", - "@types/uuid": "^3.4.5", "aws-sdk": "^2.401.0", "colors": "^1.3.3", "coveralls": "^3.0.6", @@ -82,14 +81,12 @@ "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", "lodash": "^4.17.11", "reflect-metadata": "^0.1.12", - "tslib": "^1.10.0", - "uuid": "^3.3.2" + "tslib": "^1.10.0" } } diff --git a/src/decorator/decorators.spec.ts b/src/decorator/decorators.spec.ts index 782b3bc7f..03e453a9d 100644 --- a/src/decorator/decorators.spec.ts +++ b/src/decorator/decorators.spec.ts @@ -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() }) diff --git a/src/decorator/impl/key/partition-key-uuid.decorator.ts b/src/decorator/impl/key/partition-key-uuid.decorator.ts deleted file mode 100644 index 9a140c710..000000000 --- a/src/decorator/impl/key/partition-key-uuid.decorator.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @module decorators - */ -import { initOrUpdateProperty } from '../property/init-or-update-property.function' - -export function PartitionKeyUUID(): PropertyDecorator { - return (target: any, propertyKey: string | symbol) => { - if (typeof propertyKey === 'string') { - initOrUpdateProperty({ key: { type: 'HASH', uuid: true } }, target, propertyKey) - } - } -} diff --git a/src/decorator/impl/property/property-data.model.ts b/src/decorator/impl/property/property-data.model.ts index 8c24e4f80..70495be0c 100644 --- a/src/decorator/impl/property/property-data.model.ts +++ b/src/decorator/impl/property/property-data.model.ts @@ -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 { + /** + * the name of property how it is named in dynamoDB + */ name: string - mapper: MapperForType + mapper: MapperForType + defaultValueProvider: () => T } diff --git a/src/decorator/impl/property/property.decorator.ts b/src/decorator/impl/property/property.decorator.ts index d45ce509a..c57f921c4 100644 --- a/src/decorator/impl/property/property.decorator.ts +++ b/src/decorator/impl/property/property.decorator.ts @@ -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 = {}): PropertyDecorator { +export function Property(opts: Partial> = {}): PropertyDecorator { return (target: object, propertyKey: string | symbol) => { if (typeof propertyKey === 'string') { const propertyOptions: Partial> = { name: propertyKey, nameDb: opts.name || propertyKey, + defaultValueProvider: opts.defaultValueProvider, } if ('mapper' in opts && !!opts.mapper) { diff --git a/src/decorator/impl/public-api.ts b/src/decorator/impl/public-api.ts index 796968577..03ec508b5 100644 --- a/src/decorator/impl/public-api.ts +++ b/src/decorator/impl/public-api.ts @@ -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 diff --git a/src/decorator/metadata/metadata.spec.ts b/src/decorator/metadata/metadata.spec.ts index df7127a14..f7b32c7c8 100644 --- a/src/decorator/metadata/metadata.spec.ts +++ b/src/decorator/metadata/metadata.spec.ts @@ -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 let metaDataGsi: Metadata let metaDataIndexes: Metadata - let metaDataUuid: Metadata + let metaDataDefaultValue: Metadata let metaDataComplex: Metadata 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', () => { diff --git a/src/decorator/metadata/metadata.ts b/src/decorator/metadata/metadata.ts index 452dd223b..464d48553 100644 --- a/src/decorator/metadata/metadata.ts +++ b/src/decorator/metadata/metadata.ts @@ -70,10 +70,10 @@ export class Metadata { /** * - * @returns {Array>} Returns all the properties property the @PartitionKeyUUID decorator is present, returns an empty array by default + * @returns {Array>} Returns all the properties a defaultValueProvider, returns an empty array by default */ - getKeysWithUUID(): Array> { - return filterBy(this.modelOptions, p => !!(p.key && p.key.uuid), []) + getPropertiesWithDefaultValueProvider(): Array> { + return filterBy(this.modelOptions, p => !!p.defaultValueProvider, []) } /** diff --git a/src/decorator/metadata/property-metadata.model.ts b/src/decorator/metadata/property-metadata.model.ts index b04143ef2..dd6e0af57 100644 --- a/src/decorator/metadata/property-metadata.model.ts +++ b/src/decorator/metadata/property-metadata.model.ts @@ -13,7 +13,6 @@ export interface TypeInfo { export interface Key { type: DynamoDB.KeyType - uuid?: boolean } export interface PropertyMetadata { @@ -49,6 +48,8 @@ export interface PropertyMetadata { // index?: IModelAttributeIndex transient?: boolean + + defaultValueProvider?: () => any } /** diff --git a/src/dynamo/expression/logical-operator/attribute.function.ts b/src/dynamo/expression/logical-operator/attribute.function.ts index 5d3924d26..63e7ab934 100644 --- a/src/dynamo/expression/logical-operator/attribute.function.ts +++ b/src/dynamo/expression/logical-operator/attribute.function.ts @@ -16,7 +16,7 @@ import { * @Model() * class Person{ * - * @PartitionKeyUUID() + * @PartitionKey() * id: string * age: number * } diff --git a/src/dynamo/expression/logical-operator/update.function.ts b/src/dynamo/expression/logical-operator/update.function.ts index 16ce4d566..da53636c1 100644 --- a/src/dynamo/expression/logical-operator/update.function.ts +++ b/src/dynamo/expression/logical-operator/update.function.ts @@ -15,7 +15,7 @@ import { * @Model() * class Person { * - * @PartitionKeyUUID() + * @PartitionKey() * id: string * age: number * } diff --git a/src/mapper/mapper.spec.ts b/src/mapper/mapper.spec.ts index 53e4557e8..30d21ac74 100644 --- a/src/mapper/mapper.spec.ts +++ b/src/mapper/mapper.spec.ts @@ -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' @@ -667,15 +669,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((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((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((toDbVal.myProp).S).toBe(MyProp.default().toString()) }) }) diff --git a/src/mapper/mapper.ts b/src/mapper/mapper.ts index a81d4b54e..f63b7bca7 100644 --- a/src/mapper/mapper.ts +++ b/src/mapper/mapper.ts @@ -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(item: T, modelConstructor?: ModelConstructor): Attrib const metadata: Metadata = 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(item, propertyMetadata.name)) { - Reflect.set(item, propertyMetadata.name, uuidv4()) + metadata.getPropertiesWithDefaultValueProvider().forEach(propertyMetadata => { + const currentVal = Reflect.get(item, propertyMetadata.name) + if (currentVal === undefined || currentVal === null) { + // tslint:disable-next-line:no-non-null-assertion + Reflect.set(item, propertyMetadata.name, propertyMetadata.defaultValueProvider!()) } }) } diff --git a/test/models/index.ts b/test/models/index.ts index 3f17cb0a7..471416845 100644 --- a/test/models/index.ts +++ b/test/models/index.ts @@ -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' diff --git a/test/models/model-with-autogenerated-id.model.ts b/test/models/model-with-autogenerated-id.model.ts deleted file mode 100644 index 115a56ac6..000000000 --- a/test/models/model-with-autogenerated-id.model.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Model, PartitionKeyUUID } from '../../src/dynamo-easy' - -@Model() -export class ModelWithAutogeneratedId { - @PartitionKeyUUID() - id: string -} diff --git a/test/models/model-with-custom-mapper-and-default-value.model.ts b/test/models/model-with-custom-mapper-and-default-value.model.ts new file mode 100644 index 000000000..f43e81978 --- /dev/null +++ b/test/models/model-with-custom-mapper-and-default-value.model.ts @@ -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 = { + fromDb: a => MyProp.parse(a.S), + toDb: v => ({ S: MyProp.toString(v) }), +} + +@Model() +export class ModelWithCustomMapperAndDefaultValue { + @Property({ + mapper: myPropMapper, + defaultValueProvider: () => MyProp.default(), + }) + myProp: MyProp +} diff --git a/test/models/model-with-default-value.model.ts b/test/models/model-with-default-value.model.ts new file mode 100644 index 000000000..5e69e39bb --- /dev/null +++ b/test/models/model-with-default-value.model.ts @@ -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 +} From 097b4cce2a44a551e4155ed1299871e75c2e7175 Mon Sep 17 00:00:00 2001 From: Simon Mumenthaler Date: Sat, 11 Jan 2020 16:34:35 +0100 Subject: [PATCH 2/3] build(package.lock): remove @types/uuid --- package-lock.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index d85ff0b9b..a7b18383c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1762,15 +1762,6 @@ "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", "dev": true }, - "@types/uuid": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz", - "integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/yargs": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.2.tgz", From f553e20cab22a4debc49dc511e07cb5b2705a183 Mon Sep 17 00:00:00 2001 From: Simon Mumenthaler Date: Fri, 12 Jun 2020 22:52:35 +0200 Subject: [PATCH 3/3] chore(release): set releaserc config [skip release] --- .releaserc.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.releaserc.yml b/.releaserc.yml index 3a89bc993..c3c3013f9 100644 --- a/.releaserc.yml +++ b/.releaserc.yml @@ -1,2 +1,5 @@ -# run semantic-release on master branch -branch: master +branches: + - name: master + - name: "#91-default-value-provider" + channel: pr91 + prerelease: pr91