Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,20 @@ export function uniqueAttributeValueName(key: string, existingValueNames?: strin

return potentialName
}

export function uniqueAttributeValueNameRecord(key: string, existingValueNames?: Record<string, string>): string {
key = key.replace(/\./g, '__').replace(BRACED_INDEX_REGEX, attributeNameReplacer)
let potentialName = `:${key}`
let idx = 1

if (existingValueNames && existingValueNames.length) {
const recordKeys = Object.keys(existingValueNames)
const recordValues = Object.values(existingValueNames)
while (recordKeys.includes(potentialName) || recordValues.includes(potentialName)) {
idx++
potentialName = `:${key}_${idx}`
}
}

return potentialName
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ export function prepareAndAddUpdateExpressions(
const sortedByActionKeyWord: Map<UpdateActionKeyword, UpdateExpression[]> = updateDefFns
.map((updateDefFn) => {
// TODO post-v3:: investigate on how to remove any
// tslint:disable-next-line:no-unnecessary-type-assertion
return updateDefFn(params.ExpressionAttributeNames as any, metadata)
return updateDefFn(params.ExpressionAttributeNames, metadata)
})
.reduce((result, expr) => {
const actionKeyword = expr.type
Expand Down
10 changes: 5 additions & 5 deletions src/dynamo/expression/request-expression-builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('updateDefinitionFunction', () => {
})

it('set property', () => {
const expr = updateDefinitionFunction('nestedObj').set({ id: 'ok' })([], metadata)
const expr = updateDefinitionFunction('nestedObj').set({ id: 'ok' })({}, metadata)

expect(expr.statement).toBe('#nestedObj = :nestedObj')
expect(expr.attributeNames).toEqual({ '#nestedObj': 'my_nested_object' })
Expand All @@ -66,7 +66,7 @@ describe('updateDefinitionFunction', () => {
})

it('set nested property', () => {
const expr = updateDefinitionFunction('nestedObj.id').set('ok')([], metadata)
const expr = updateDefinitionFunction('nestedObj.id').set('ok')({}, metadata)

expect(expr.statement).toBe('#nestedObj.#id = :nestedObj__id')
expect(expr.attributeNames).toEqual({ '#nestedObj': 'my_nested_object', '#id': 'id' })
Expand All @@ -75,7 +75,7 @@ describe('updateDefinitionFunction', () => {
})

it('set list item at position', () => {
const expr = updateDefinitionFunction('sortedComplexSet[0]').set({ id: 'ok' })([], metadata)
const expr = updateDefinitionFunction('sortedComplexSet[0]').set({ id: 'ok' })({}, metadata)

expect(expr.statement).toBe('#sortedComplexSet[0] = :sortedComplexSet_at_0')
expect(expr.attributeNames).toEqual({ '#sortedComplexSet': 'sortedComplexSet' })
Expand All @@ -84,7 +84,7 @@ describe('updateDefinitionFunction', () => {
})

it('set nested property of list item at position', () => {
const expr = updateDefinitionFunction('sortedComplexSet[1].id').set('ok')([], metadata)
const expr = updateDefinitionFunction('sortedComplexSet[1].id').set('ok')({}, metadata)

expect(expr.statement).toBe('#sortedComplexSet[1].#id = :sortedComplexSet_at_1__id')
expect(expr.attributeNames).toEqual({ '#sortedComplexSet': 'sortedComplexSet', '#id': 'id' })
Expand All @@ -93,7 +93,7 @@ describe('updateDefinitionFunction', () => {
})

it('set nested property of list item with decorators', () => {
const expr = updateDefinitionFunction('sortedComplexSet[1].date').set(aDate)([], metadata)
const expr = updateDefinitionFunction('sortedComplexSet[1].date').set(aDate)({}, metadata)

expect(expr.statement).toBe('#sortedComplexSet[1].#date = :sortedComplexSet_at_1__date')
expect(expr.attributeNames).toEqual({ '#sortedComplexSet': 'sortedComplexSet', '#date': 'my_date' })
Expand Down
4 changes: 2 additions & 2 deletions src/dynamo/expression/request-expression-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function addUpdate<R extends UpdateParamsHost, T, K extends keyof T>(
string,
UpdateActionDef,
any[],
string[] | undefined,
Record<string, string> | undefined,
Metadata<any> | undefined,
UpdateExpression
>(buildUpdateExpression)
Expand Down Expand Up @@ -193,7 +193,7 @@ export function updateDefinitionFunction<T>(attributePath: keyof T): UpdateExpre
string,
UpdateActionDef,
any[],
string[] | undefined,
Record<string, string> | undefined,
Metadata<any> | undefined,
UpdateExpression
>(buildUpdateExpression)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ import { UpdateExpression } from './update-expression.type'
* @hidden
*/
export type UpdateExpressionDefinitionFunction = (
expressionAttributeValues: string[] | undefined,
expressionAttributeValues: Record<string, string> | undefined,
metadata: Metadata<any> | undefined,
) => UpdateExpression
36 changes: 18 additions & 18 deletions src/dynamo/expression/update-expression-builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ describe('buildUpdateExpression', () => {

it('should throw when operation.action is unknown', () => {
const unknownOp = new UpdateActionDef('SET', <any>'subtract')
expect(() => buildUpdateExpression('age', unknownOp, [3], [], metaDataS)).toThrow()
expect(() => buildUpdateExpression('age', unknownOp, [3], {}, metaDataS)).toThrow()
})

describe('incrementBy', () => {
const op = new UpdateActionDef('SET', 'incrementBy')

it('should build expression', () => {
const exp = buildUpdateExpression('age', op, [23], [], metaDataS)
const exp = buildUpdateExpression('age', op, [23], {}, metaDataS)
expect(exp).toEqual({
attributeNames: { '#age': 'age' },
attributeValues: { ':age': { N: '23' } },
Expand All @@ -27,7 +27,7 @@ describe('buildUpdateExpression', () => {
})

it('should build expression for number at document path ', () => {
const exp = buildUpdateExpression('numberValues[0]', op, [23], [], metaDataU)
const exp = buildUpdateExpression('numberValues[0]', op, [23], {}, metaDataU)
expect(exp).toEqual({
attributeNames: { '#numberValues': 'numberValues' },
attributeValues: { ':numberValues_at_0': { N: '23' } },
Expand All @@ -37,14 +37,14 @@ describe('buildUpdateExpression', () => {
})

it('should throw when not number', () => {
expect(() => buildUpdateExpression('age', op, ['notANumber'], [], metaDataS)).toThrow()
expect(() => buildUpdateExpression('age', op, ['notANumber'], {}, metaDataS)).toThrow()
})
})

describe('decrementBy', () => {
const op = new UpdateActionDef('SET', 'decrementBy')
it('should build expression', () => {
const exp = buildUpdateExpression('age', op, [23], [], metaDataS)
const exp = buildUpdateExpression('age', op, [23], {}, metaDataS)
expect(exp).toEqual({
attributeNames: { '#age': 'age' },
attributeValues: { ':age': { N: '23' } },
Expand All @@ -54,7 +54,7 @@ describe('buildUpdateExpression', () => {
})

it('should build expression for number at document path ', () => {
const exp = buildUpdateExpression('numberValues[0]', op, [23], [], metaDataU)
const exp = buildUpdateExpression('numberValues[0]', op, [23], {}, metaDataU)
expect(exp).toEqual({
attributeNames: { '#numberValues': 'numberValues' },
attributeValues: { ':numberValues_at_0': { N: '23' } },
Expand All @@ -64,15 +64,15 @@ describe('buildUpdateExpression', () => {
})

it('should throw when not number', () => {
expect(() => buildUpdateExpression('age', op, ['notANumber'], [], metaDataS)).toThrow()
expect(() => buildUpdateExpression('age', op, ['notANumber'], {}, metaDataS)).toThrow()
})
})

describe('set', () => {
const op = new UpdateActionDef('SET', 'set')

it('should build set expression for number[]', () => {
const exp = buildUpdateExpression('numberValues', op, [[23]], [], metaDataU)
it('should build set expression for number{}', () => {
const exp = buildUpdateExpression('numberValues', op, [[23]], {}, metaDataU)
expect(exp).toEqual({
attributeNames: { '#numberValues': 'numberValues' },
attributeValues: { ':numberValues': { L: [{ N: '23' }] } },
Expand All @@ -83,7 +83,7 @@ describe('buildUpdateExpression', () => {

describe('should build set expression for empty collection', () => {
it('array', () => {
const exp = buildUpdateExpression('addresses', op, [[]], [], metaDataU)
const exp = buildUpdateExpression('addresses', op, [[]], {}, metaDataU)
expect(exp).toEqual({
attributeNames: { '#addresses': 'addresses' },
attributeValues: { ':addresses': { L: [] } },
Expand All @@ -94,7 +94,7 @@ describe('buildUpdateExpression', () => {
})

it('should build set expression for number at document path', () => {
const exp = buildUpdateExpression('numberValues[0]', op, [23], [], metaDataU)
const exp = buildUpdateExpression('numberValues[0]', op, [23], {}, metaDataU)
expect(exp).toEqual({
attributeNames: { '#numberValues': 'numberValues' },
attributeValues: { ':numberValues_at_0': { N: '23' } },
Expand Down Expand Up @@ -123,7 +123,7 @@ describe('buildUpdateExpression', () => {
const op = new UpdateActionDef('ADD', 'add')

it('should build add expression for numbers', () => {
const exp = buildUpdateExpression('age', op, [23], [], metaDataS)
const exp = buildUpdateExpression('age', op, [23], {}, metaDataS)
expect(exp).toEqual({
attributeNames: { '#age': 'age' },
attributeValues: { ':age': { N: '23' } },
Expand All @@ -134,7 +134,7 @@ describe('buildUpdateExpression', () => {

it('should work with customMapper (1)', () => {
const metaDataC = metadataForModel(SpecialCasesModel)
const exp = buildUpdateExpression('myChars', op, ['abc'], [], metaDataC)
const exp = buildUpdateExpression('myChars', op, ['abc'], {}, metaDataC)
expect(exp).toEqual({
attributeNames: { '#myChars': 'myChars' },
attributeValues: { ':myChars': { SS: ['a', 'b', 'c'] } },
Expand All @@ -144,19 +144,19 @@ describe('buildUpdateExpression', () => {
})

it('should throw when not number or a set value', () => {
expect(() => buildUpdateExpression('age', op, ['notANumber'], [], metaDataS)).toThrow()
expect(() => buildUpdateExpression('age', op, ['notANumber'], {}, metaDataS)).toThrow()
})

it('should throw when no value for attributeValue was given', () => {
expect(() => buildUpdateExpression('age', op, [], [], metaDataS)).toThrow()
expect(() => buildUpdateExpression('age', op, [], {}, metaDataS)).toThrow()
})
})

describe('removeFromSet', () => {
const op = new UpdateActionDef('DELETE', 'removeFromSet')

it('should build the expression', () => {
const exp = buildUpdateExpression('topics', op, [new Set(['val1', 'val2'])], [], metaDataU)
const exp = buildUpdateExpression('topics', op, [new Set(['val1', 'val2'])], {}, metaDataU)
expect(exp).toEqual({
attributeNames: { '#topics': 'topics' },
attributeValues: { ':topics': { SS: ['val1', 'val2'] } },
Expand All @@ -166,11 +166,11 @@ describe('buildUpdateExpression', () => {
})

it('should throw when not a set value', () => {
expect(() => buildUpdateExpression('topics', op, ['notASet'], [], metaDataU)).toThrow()
expect(() => buildUpdateExpression('topics', op, ['notASet'], {}, metaDataU)).toThrow()
})

it('should throw when no value was given', () => {
expect(() => buildUpdateExpression('topics', op, [], [], metaDataU)).toThrow()
expect(() => buildUpdateExpression('topics', op, [], {}, metaDataU)).toThrow()
})
})
})
10 changes: 5 additions & 5 deletions src/dynamo/expression/update-expression-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Attribute, Attributes } from '../../mapper/type/attribute.type'
import { getPropertyPath, isSet } from '../../mapper/util'
import { deepFilter } from './condition-expression-builder'
import { resolveAttributeNames } from './functions/attribute-names.function'
import { uniqueAttributeValueName } from './functions/unique-attribute-value-name.function'
import { uniqueAttributeValueNameRecord } from './functions/unique-attribute-value-name.function'
import { UpdateActionDef } from './type/update-action-def'
import { UpdateAction } from './type/update-action.type'
import { UpdateExpression } from './type/update-expression.type'
Expand All @@ -24,7 +24,7 @@ import { UpdateExpression } from './type/update-expression.type'
* @param {string} attributePath
* @param {ConditionOperator} operation
* @param {any[]} values Depending on the operation the amount of values differs
* @param {string[]} existingValueNames If provided the existing names are used to make sure we have a unique name for the current attributePath
* @param {Record<string, string>} existingValueNames If provided the existing names are used to make sure we have a unique name for the current attributePath
* @param {Metadata<any>} metadata If provided we use the metadata to define the attribute name and use it to map the given value(s) to attributeValue(s)
* @returns {Expression}
* @hidden
Expand All @@ -33,7 +33,7 @@ export function buildUpdateExpression(
attributePath: string,
operation: UpdateActionDef,
values: any[],
existingValueNames: string[] | undefined,
existingValueNames: Record<string, string> | undefined,
metadata: Metadata<any> | undefined,
): UpdateExpression {
// get rid of undefined values
Expand All @@ -54,7 +54,7 @@ export function buildUpdateExpression(
* person.age
*/
const resolvedAttributeNames = resolveAttributeNames(attributePath, metadata)
const valuePlaceholder = uniqueAttributeValueName(attributePath, existingValueNames)
const valuePlaceholder = uniqueAttributeValueNameRecord(attributePath, existingValueNames)

/*
* build the statement
Expand All @@ -80,7 +80,7 @@ function buildDefaultExpression(
valuePlaceholder: string,
attributeNames: Record<string, string>,
values: any[],
_existingValueNames: string[] | undefined,
_existingValueNames: Record<string, string> | undefined,
propertyMetadata: PropertyMetadata<any> | undefined,
operator: UpdateActionDef,
): UpdateExpression {
Expand Down
6 changes: 1 addition & 5 deletions src/dynamo/request/get/get.request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,10 @@ export class GetRequest<T, T2 extends Partial<T> = T> extends StandardRequest<
.getItem(this.params)
.then(promiseTap((response) => this.logger.debug('response', response)))
.then((getItemResponse) => {
// TODO post-v3: investigate on how to remove any
// tslint:disable-next-line:no-unnecessary-type-assertion
const response: GetResponse<T2> = { ...(getItemResponse as any) }
const response: GetResponse<T2> = { ...getItemResponse, ...{ Item: undefined } }

if (getItemResponse.Item) {
response.Item = fromDb(<Attributes<T2>>getItemResponse.Item, <any>this.modelClazz)
} else {
response.Item = null
}

return response
Expand Down
2 changes: 1 addition & 1 deletion src/dynamo/request/get/get.response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface GetResponse<T> {
/**
* A map of attribute names to AttributeValue objects (subset if ProjectionExpression was defined).
*/
Item: T | null
Item: T | undefined
/**
* The capacity units consumed by the GetItem operation. The data returned includes the total provisioned throughput consumed, along with statistics for the table and any indexes involved in the operation. ConsumedCapacity is only returned if the ReturnConsumedCapacity parameter was specified. For more information, see Provisioned Throughput in the Amazon DynamoDB Developer Guide.
*/
Expand Down
2 changes: 1 addition & 1 deletion v2-v3-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## 📋 To complete before releasing
- [x] Make sure the snippets compile
- [x] Make tests compile and run successfully
- [ ] Search for `TODO v3:` in the code to see open issues / notes, and create followup issues to decide if we have more to fix prior to a major release or keep it for later follow-up
- [x] Search for `TODO v3:` in the code to see open issues / notes, and create followup issues to decide if we have more to fix prior to a major release or keep it for later follow-up
- [ ] Check on new attribute type [$UnknownAttribute](./src/mapper/type/attribute.type.ts) and implement tests
- [ ] Remove [sessionValidityEnsurer](./src/config/dynamo-easy-config.ts) and add demo with using [middleware stack](https://github.com/aws/aws-sdk-js-v3#middleware-stack)
to implement the same
Expand Down