diff --git a/package-lock.json b/package-lock.json index 3602018bf..0528670f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5360,9 +5360,9 @@ } }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -11012,9 +11012,9 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" diff --git a/src/__tests__/entities/test-entity.ts b/src/__tests__/entities/test-entity.ts index cd3ffbb23..2e53cfc14 100644 --- a/src/__tests__/entities/test-entity.ts +++ b/src/__tests__/entities/test-entity.ts @@ -27,6 +27,10 @@ export = { test_string_set_type_coerce: { type: 'set', setType: 'string', coerce: true }, test_number_set_type_coerce: { type: 'set', setType: 'number', coerce: true }, test_binary: { type: 'binary' }, - simple_string: 'string' + simple_string: 'string', + inverse_transformed_simple_string: { + type: "string", + inverseTransform: (input: string) => input.toUpperCase(), + }, } } diff --git a/src/__tests__/entity-creation.unit.test.ts b/src/__tests__/entity-creation.unit.test.ts index b4f339472..1663d0753 100644 --- a/src/__tests__/entity-creation.unit.test.ts +++ b/src/__tests__/entity-creation.unit.test.ts @@ -314,6 +314,49 @@ describe('Entity creation', ()=> { expect(result).toThrow(`Please provide a valid entity definition`) }) + it("creates an attribute with an inverseTransformation function", async () => { + // Create basic table + const TestTable = new Table({ + name: "test-table", + partitionKey: "pk", + sortKey: "sk", + DocumentClient, + }); + + // Create basic entity + const TestEntity = new Entity({ + name: "TestEnt", + attributes: { + pk: { + partitionKey: true, + inverseTransform: (val) => val.toUpperCase(), + default: "pkDef", + }, + test: { + inverseTransform: (val, data) => { + return val.toUpperCase(); + }, + default: () => "defaultVal", + }, + sk: { type: "string", sortKey: true }, + testx: ["sk", 0], + testy: [ + "sk", + 1, + { + default: () => "testDefaultX", + inverseTransform: (val) => { + return "__" + val.toUpperCase(); + }, + }, + ], + }, + table: TestTable, + timestamps: false, + }) + }) + + // it('creates entity w/ table', async () => { diff --git a/src/__tests__/entity.parse.unit.test.ts b/src/__tests__/entity.parse.unit.test.ts index ad58d5998..21f397fe9 100644 --- a/src/__tests__/entity.parse.unit.test.ts +++ b/src/__tests__/entity.parse.unit.test.ts @@ -22,11 +22,12 @@ const TestTable = new Table({ describe('parse',()=>{ it('parses single item', ()=>{ - let item = TestEntity.parse({ pk: 'test@test.com', sk: 'email', test_string: 'test', _et: 'TestEntity' }) + let item = TestEntity.parse({ pk: 'test@test.com', sk: 'email', test_string: 'test', inverse_transformed_simple_string: "transformed", _et: 'TestEntity' }) expect(item).toEqual({ email: 'test@test.com', test_type: 'email', test_string: 'test', + inverse_transformed_simple_string: "TRANSFORMED", entity: 'TestEntity' }) }) @@ -41,19 +42,21 @@ describe('parse',()=>{ it('parses multiple items', ()=>{ let items = TestEntity.parse([ - { pk: 'test@test.com', sk: 'email', test_string: 'test' }, - { pk: 'test2@test.com', sk: 'email2', test_string: 'test2' } + { pk: 'test@test.com', sk: 'email', test_string: 'test', inverse_transformed_simple_string: "transformed", }, + { pk: 'test2@test.com', sk: 'email2', test_string: 'test2', inverse_transformed_simple_string: "transformed", } ]) expect(items).toEqual([ { email: 'test@test.com', test_type: 'email', - test_string: 'test' + test_string: 'test', + inverse_transformed_simple_string: "TRANSFORMED", }, { email: 'test2@test.com', test_type: 'email2', - test_string: 'test2' + test_string: 'test2', + inverse_transformed_simple_string: "TRANSFORMED", } ]) }) @@ -76,11 +79,11 @@ describe('parse',()=>{ }) it('parses composite field', ()=>{ - let item = SimpleEntity.parse({ pk: 'test@test.com', sk: 'active#email', test_composite: 'test' }) + let item = SimpleEntity.parse({ pk: 'test@test.com', sk: 'active#email', test_composite: 'test' }) expect(item).toEqual({ pk: 'test@test.com', test_composite: 'test', - test_composite2: 'email' + test_composite2: 'email', }) }) diff --git a/src/__tests__/parseEntity.unit.test.ts b/src/__tests__/parseEntity.unit.test.ts index 210f781d9..716390cd5 100644 --- a/src/__tests__/parseEntity.unit.test.ts +++ b/src/__tests__/parseEntity.unit.test.ts @@ -13,6 +13,7 @@ const entity: EntityConstructor = { modified: '_modified', modifiedAlias: 'modifiedAlias', typeAlias: 'typeAlias', + typeHidden: true, attributes: { pk: { partitionKey: true }, sk: { sortKey: true }, @@ -38,6 +39,7 @@ describe('parseEntity', () => { expect(ent.autoExecute).toBe(true) expect(ent.autoParse).toBe(true) expect(ent._etAlias).toBe('typeAlias') + expect(ent.typeHidden).toBe(true) }) it('fails on extra config option', async () => { diff --git a/src/classes/Entity.ts b/src/classes/Entity.ts index 18dc8c6a0..b1e0f57f7 100644 --- a/src/classes/Entity.ts +++ b/src/classes/Entity.ts @@ -41,6 +41,7 @@ export interface EntityConstructor { autoExecute?: boolean autoParse?: boolean table?: Table + typeHidden?: boolean; } export interface EntityAttributeConfig { @@ -48,6 +49,7 @@ export interface EntityAttributeConfig { default?: any | ((data: object) => any) dependsOn?: string | string[] transform?: (value: any, data: {}) => any + inverseTransform?: (value: any, data: {}) => any coerce?: boolean save?: boolean onUpdate?: boolean @@ -139,6 +141,7 @@ class Entity< public defaults: any public linked: any public required: any + public typeHidden!: boolean // Declare constructor (entity config) constructor(entity: EntityConstructor) { @@ -172,7 +175,7 @@ class Entity< // If an entity tracking field is enabled, add the attributes, alias and the default if (table.Table.entityField) { - this.schema.attributes[table.Table.entityField] = { type: 'string', alias: this._etAlias, default: this.name } as EntityAttributeConfig + this.schema.attributes[table.Table.entityField] = { type: 'string', hidden: this.typeHidden, alias: this._etAlias, default: this.name } as EntityAttributeConfig this.defaults[table.Table.entityField] = this.name this.schema.attributes[this._etAlias] = { type: 'string', map: table.Table.entityField, default: this.name } as EntityAttributeConfig this.defaults[this._etAlias] = this.name diff --git a/src/classes/Table.ts b/src/classes/Table.ts index d3fd958b7..0ce928a50 100644 --- a/src/classes/Table.ts +++ b/src/classes/Table.ts @@ -191,7 +191,7 @@ class Table { // Validate and sets the document client (extend with options.convertEmptyValues because it's not typed) set DocumentClient(docClient: (DocumentClient & { options?: { convertEmptyValues: boolean}}) | undefined) { // If a valid document client - if (docClient && docClient.get && docClient.put && docClient.delete && docClient.update) { + if (docClient) { // Automatically set convertEmptyValues to true, unless false if (docClient.options!.convertEmptyValues !== false) docClient.options!.convertEmptyValues = true diff --git a/src/lib/formatItem.ts b/src/lib/formatItem.ts index 5803337d0..2359e0c02 100644 --- a/src/lib/formatItem.ts +++ b/src/lib/formatItem.ts @@ -37,15 +37,32 @@ export default (DocumentClient: DocumentClient) => (attributes: { [key:string] : if ((attributes[field] && attributes[field].hidden) || (include.length > 0 && !include.includes(field))) return acc // Extract values from sets - if (attributes[field] && attributes[field].type === 'set' && Array.isArray(item[field].values)) { item[field] = item[field].values } - return Object.assign(acc,{ - [(attributes[field] && attributes[field].alias) || field]: ( - attributes[field] && (attributes[field].prefix || attributes[field].suffix) + if (attributes[field] && attributes[field].type === 'set' && Array.isArray(item[field].values)) { item[field] = item[field].values } + + const fieldValue = + attributes[field] && + (attributes[field].prefix || attributes[field].suffix) ? item[field] - .replace(new RegExp(`^${escapeRegExp(attributes[field].prefix!)}`),'') - .replace(new RegExp(`${escapeRegExp(attributes[field].suffix!)}$`),'') - : item[field] - ) + .replace( + new RegExp(`^${escapeRegExp(attributes[field].prefix!)}`), + "" + ) + .replace( + new RegExp(`${escapeRegExp(attributes[field].suffix!)}$`), + "" + ) + : item[field]; + + const transformedValue = + attributes[field] && attributes[field].inverseTransform + ? ( + attributes[field] as Required + ).inverseTransform(fieldValue, item) + : fieldValue + + return Object.assign(acc, { + [(attributes[field] && attributes[field].alias) || field]: + transformedValue, }) },{}) } diff --git a/src/lib/parseEntity.ts b/src/lib/parseEntity.ts index f91b71e9b..b26eaa974 100644 --- a/src/lib/parseEntity.ts +++ b/src/lib/parseEntity.ts @@ -41,6 +41,7 @@ export function parseEntity(entity: EntityConstructor) { modified, modifiedAlias, typeAlias, + typeHidden, attributes, autoExecute, autoParse, @@ -87,6 +88,9 @@ export function parseEntity(entity: EntityConstructor) { && typeAlias.trim().length > 0 ? typeAlias.trim() : 'entity' + // Define 'typeHidden' + typeHidden = typeof typeHidden === "boolean" ? typeHidden : false; + // Sanity check the attributes attributes = typeof attributes === 'object' && !Array.isArray(attributes) ? attributes : error(`Please provide a valid 'attributes' object`) @@ -115,6 +119,7 @@ export function parseEntity(entity: EntityConstructor) { linked: track.linked, autoExecute, autoParse, + typeHidden, _etAlias: typeAlias }, table ? { table } : {} diff --git a/src/lib/parseMapping.ts b/src/lib/parseMapping.ts index 14510e56c..7bf59c7ed 100644 --- a/src/lib/parseMapping.ts +++ b/src/lib/parseMapping.ts @@ -25,6 +25,10 @@ export default (field: string, config: EntityAttributeConfig, track: TrackingInf case 'transform': if (typeof config[prop] !== 'function') error(`'${prop}' must be a function`) break + case "inverseTransform": + if (typeof config[prop] !== "function") + error(`'${prop}' must be a function`); + break; case 'coerce': case 'onUpdate': case 'hidden':