Migration Guide
If you're currently using the v1 of DynamoDB-Toolbox, here are the changes you need to be aware of when migrating to the v2.
Rework of the entity
attribute
The entityAttributeName
and entityAttributeHidden
settings have been merged into a single entityAttribute
setting, similar to the timestamp attributes:
import { Entity } from 'dynamodb-toolbox/entity'
const PokemonEntity = new Entity({
- entityAttributeName: '__entity__',
- entityAttributeHidden: false,
+ entityAttribute: {
+ name: '__entity__',
+ hidden: false
+ },
...
})
The internal entity
attribute can now be disabled by setting entityAttribute
to false
:
import { Entity } from 'dynamodb-toolbox/entity'
const PokemonEntity = new Entity({
entityAttribute: false,
...
})
This change makes migrating to DynamoDB-Toolbox easier, especially when using multiple tables (i.e., one Entity
per Table
), where the entity
attribute is often unnecessary.
If you use the Single Table Design approach, we still strongly recommend using the entity
attribute, as it improves performance and enables entity-based filtering when fetching items from multiple entities within the same Table
.
Indeed, in queries and scans, the behavior regarding Entities
has changed as follows:
- If all
Entities
use the internalentity
attribute, a Filter Expression is applied based on it. - If at least one
Entity
does not use the internalentity
attribute, or ifentityAttrFilter
is set tofalse
, no Filter Expression is applied. In this case:- Entity-based filtering is denied if more than two
Entities
are provided. - Returned items that lack the internal
entity
attribute are formatted by allEntities
in order until formatting succeeds, which can lead to decreased performance. - If a returned item cannot be formatted by any
Entity
, DynamoDB-Toolbox raises an error. This behavior can be modified by setting thenoEntityMatchBehavior
option to'DISCARD'
:
- Entity-based filtering is denied if more than two
const { Items } = await PokeTable.build(QueryCommand)
.entities(PokemonEntity, TrainerEntity)
.query({ partition: 'ashKetchum' })
.options({
entityAttrFilter: false,
noEntityMatchBehavior: 'DISCARD'
})
.send()
This update provides greater flexibility while maintaining performance optimizations when needed.
Rework of Schemas
In v1, schemas existed in one of two states: Warm for building/composition time and Frozen for usage.
import { string } from 'dynamodb-toolbox/attributes/string'
const warmStrSchema = string()
const frozenStrSchema = warmStrSchema.freeze()
const parsed = frozenStrSchema.build(Parser).parse(input)
This additional step made schema syntax cumbersome, so in v2, the freezing step has been removed:
// v2 🙌 `string` is directly a "schema"
import { string } from 'dynamodb-toolbox/schema/string'
const parsed = string().build(Parser).parse(input)
The attr
and attr
shorthands have been renamed to schema
and s
, and the original schema
declaration has been renamed to item
. It is now just another type of schema:
import { item } from 'dynamodb-toolbox/schema/item'
import { string } from 'dynamodb-toolbox/schema/string'
const pokemonSchema = item({
name: string()
})
// ...or 👇
import { s } from 'dynamodb-toolbox/schema'
const pokemonSchema = s.item({
name: s.string()
})
Actually, you don't really need to use item
schemas as map
schemas now have the .pick
, .omit
and .and
methods and can be used within the Entity
constructor:
import { map } from 'dynamodb-toolbox/schema/map'
import { string } from 'dynamodb-toolbox/schema/string'
import { number } from 'dynamodb-toolbox/schema/number'
import { Entity } from 'dynamodb-toolbox/entity'
const pokemonSchema = map({
name: string(),
level: number(),
...
})
const PokemonEntity = new Entity({
// 👇 Creates an `item` schema w. the same attributes + internal attributes
schema: pokemonSchema,
...
})
You can still inspect a schema's properties at runtime and through its types via the props
attribute:
const hiddenStr = string().hidden()
const isHidden = hiddenStr.props.hidden // => true
Previously, calling .freeze()
validated the schema. In v2, validation is now done using the check
method (which is also called in the Entity
constructor):
const stringWithDefault = string().default('foo')
const invalidSchema = list(stringWithDefault)
// ❌ List elements cannot have defaults
invalidSchema.check()
Hopefully, this makes it easier to build and re-use schemas accross your codebase.
Rework of record
Record schemas now properly translate to the Record
type in TS:
import { record } from 'dynamodb-toolbox/schema/record'
import { string } from 'dynamodb-toolbox/schema/string'
import { number } from 'dynamodb-toolbox/schema/number'
import { Parser } from 'dynamodb-toolbox/schema/actions/parse'
const rec = record(string(), number())
const parsed = rec.build(Parser).parse(...)
// => Record<string, number>
In particular, if the key schema is a string
enum, the parsing expects an element to be present for all enum values:
const rec = record(string().enum('foo', 'bar'), number())
// ❌ Raises an error as 'bar' is missing
rec.build(Parser).parse({ foo: 42 })
You can change this behavior with the partial
property:
const rec = record(...).partial()
// ✅ Succeeds
rec.build(Parser).parse({ foo: 42 })
Renamings
Entity
and Table
names
The name
property of Entity
and Table
instances have been renamed to entityName
and tableName
:
- const entityName = PokemonEntity.name
+ const entityName = PokemonEntity.entityName
- const tableName = PokeTable.name
+ const tableName = PokeTable.tableName
Transformers
Transformers' parse
and format
properties have been renamed to encode
and decode
for clarity:
const prefix = {
- parse: (input: string) => [PREFIX, input].join(''),
+ encode: (input: string) => [PREFIX, input].join(''),
- format: (saved: string) => saved.slice(PREFIX.length),
+ decode: (saved: string) => saved.slice(PREFIX.length),
}
const prefixedStrSchema = string().transform(prefix)
Similarly, the ReadValue
and ReadItem
types have been renamed to DecodedValue
and DecodedItem
:
- type ReadPokemonItem = ReadItem<typeof PokemonEntity>
+ type DecodedPokemonItem = DecodedItem<typeof PokemonEntity>
- type ReadPokemonValue = ReadValue<typeof pokemonSchema>
+ type DecodedPokemonValue = DecodedValue<typeof pokemonSchema>