dynamo-easy
dynamo-easy copied to clipboard
filter expression values won't map as expected
@Person()
export class Person {
@PartitionKey
id: string
@DateProperty
creationDate: Date
}
we want to search for a person where the creationDate starts with '2019-03' (does not make too much sense but its just for explanations sake)
personStore.scan()
.whereAttribute('creationDate').startsWith('2019-03')
one thing is that the compiler will complain about wrong typing, not sure if we should widen the typing for the expression building with whereAttribute or if in such cases the preferred option should be to use where(attribute('creationDate').startsWith('2019-03'))
where the typing is already accepting more
the bigger problem that this will also fail during runtime. When trying to map the value '2019-03' to a db we do the following with default date mapper
function dateToDb(modelValue: Date): StringAttribute {
// noinspection SuspiciousInstanceOfGuard
if (modelValue && modelValue instanceof Date) {
return { S: `${modelValue.toISOString()}` }
} else {
throw new Error('the given model value must be an instance of Date')
}
}
what we actually want is that the expression value will be mapped not using the default mapping behavior, instead it should just map without metadata. But this is too implicit as we don't really know if we either have to use the metadata or not. So we need an additional information on the mapper, which defined the dynamoDB attribute type the custom mapper maps too.
export interface MapperForType<
T,
R extends
| StringAttribute
| NumberAttribute
| BinaryAttribute
| StringSetAttribute
| NumberSetAttribute
| BinarySetAttribute
| MapAttribute
| ListAttribute
| NullAttribute
| BooleanAttribute
> {
fromDb: FromDbFn<T, R>
toDb: ToDbFn<T, R>
dynamoType?: 'S' | 'N' | 'B' // not sure if tuple types make sense here, need to investigate more
}
the problem does occur with the following operations:
- LE
- LT
- GE
- GT
- CONTAINS
- NOT_CONTAINS
- BEGINS_WITH
- IN
We are hitting this issue when trying to query using a begins_with operator too. Here is a simplified example:
@Model({table: 'table'})
class Test {
...
@SortKey()
@Property({name: 'sk', mapper: mapper})
id: Id
...
}
class Id {
type: string;
value: string
}
const mapper = {
fromDb: (val) => { // transform "obj-123" into an actual ID type
var id = new Id;
var parts = val.split('-');
id.type = val[0];
id.value = parts[1];
return id;
},
toDb: (id) => ({S: `${id.type}-{id.value}`}) //transform ID type into "obj-123" string format
}
Usually this is fine, the ID gets mapped and passed around correctly.
But when we try to do something like:
return await Store
.query()
.wherePartitionKey(...)
.whereSortKey().beginsWith(`tag-`)
.execFullResponse()
It fails because the mapper's toDb
method is invoked with tag-
as an argument, which results in undefined.
It seems like that when using begins_with/lt/gt etc. it shouldn't invoke the mapper toDb
function, but just simply carry the given value straight through? Doing this would mean the key expression would correctly be e.g. begins_with 'tag-'
Is there a workaround or other solution here?