Migration Guide
If you used the v0 of DynamoDB-Toolbox, here are the changes you need to be aware of when migrating to the v1.
The good news is that the breaking changes only concern the API: You won't need any data migration 🥳
Well, probably... as there are two exceptions:
- If you de-activated the internal
entity
attribute (by settingentityField
tofalse
), it is now required, so must be re-introduced in the data - If you used the
saved: false
option on an attribute, there is no equivalent yet
Table
Primary Key
Primary Key attributes now have a type
along with their names
:
import { Table } from 'dynamodb-toolbox/table'
const MyTable = new Table({
- partitionKey: 'pk',
+ partitionKey: { name: 'pk', type: 'string' },
- sortKey: 'sk',
+ sortKey: { name: 'sk', type: 'string' },
...
})
Indexes
In the same way, index attributes now have a type
property as well as an explicit type
(global
or local
):
- Global Index
- Local index
const MyTable = new Table({
indexes: {
- byTrainerId: { partitionKey: 'trainerId', sortKey: 'level' },
+ byTrainerId: {
+ type: 'global',
+ partitionKey: { name: 'trainerId', type: 'string' },
+ sortKey: { name: 'level', type: 'number' }
+ },
},
...
})
const MyTable = new Table({
indexes: {
- byLevel: { sortKey: 'level' },
+ byLevel: {
+ type: 'local',
+ sortKey: { name: 'level', type: 'number' }
+ },
},
...
})
entityField
entityField
has been renamed to entityAttributeSavedAs
to be more closely aligned with the new schema
syntax.
It cannot be set to false
anymore (as it is required to infer correct formatting during Scans and Queries):
const MyTable = new Table({
...,
- entityField: '__entity__',
+ entityAttributeSavedAs: '__entity__',
})
Misc.
The autoExecute
and autoParse
options have been removed for the sake of simplicity: All commands have a .params()
method to inspect the resulting query. This query can also be used directly to fetch the unformatted response.
The removeNullAttributes
option has been removed. Attribute removals benefit from a dedicated $remove
symbol.
The alias
option has also been removed. Feel free to open a discussion if you need it back.
Finally, table attributes
have not been re-implemented yet (but you can still share attribute schemas between entities, see the Schema documentation for more details)
Entity
Entity attribute
The typeAlias
and typeHidden
have respectively been renamed to entityAttributeName
and entityAttributeHidden
:
const PokemonEntity = new Entity({
...,
- typeAlias: 'ent',
- typeHidden: true,
+ entityAttributeName: 'ent',
+ entityAttributeHidden: true,
})
Timestamps attribute
The timestamps
, created
, modified
, createdAlias
, modifiedAlias
options have been merged in a single timestamps
option that is either a boolean
or a configuration object.
Timestamp attributes can also be hidden and independently disabled:
- Configured
- Enabled/Disabled
const PokemonEntity = new Entity({
...,
- timestamps: true,
- created: '__created__',
- createdAlias: 'creationDate',
- modified: '__modified__',
- modifiedAlias: 'modifiedDate',
+ timestamps: {
+ created: {
+ savedAs: '__created__',
+ name: 'creationDate'
+ },
+ modified: {
+ savedAs: '__modified__',
+ name: 'modifiedDate',
+ hidden: true
+ }
+ }
})
const PokemonEntity = new Entity({
...,
- timestamps: false,
+ timestamps: {
+ created: true,
+ modified: false
+ }
})
Misc.
The autoExecute
and autoParse
options have been removed for the sake of simplicity: All commands have a .params()
method to inspect the resulting query. This query can also be used directly to fetch the unformatted response.
Attributes
The schema definition API (previous attributes
) is the part that received the most significant overhaul:
+ import { schema } from 'dynamodb-toolbox/schema'
+ import { string } from 'dynamodb-toolbox/attributes/string'
const PokemonEntity = new Entity({
...,
- attributes: {
- trainer: { type: 'string', map: '_t' },
- _p: { type: 'string', alias: 'pokemon' }
- ...,
- },
+ schema: schema({
+ trainer: string().optional().savedAs('_t'),
+ pokemon: string().optional().savedAs('_p'),
+ ...
+ }),
})
See the Schema documentation for a complete documentation on the new syntax.
map
& alias
map
and alias
options have been simplified to a single savedAs
options:
const PokemonEntity = new Entity({
...,
- attributes: {
- trainer: { type: 'string', required: true, map: '_t' },
- _p: { type: 'string', required: true, alias: 'pokemon' },
- ...,
- },
+ schema: schema({
+ trainer: string().savedAs('_t'),
+ pokemon: string().savedAs('_p'),
+ ...,
+ }),
})
partitionKey
& sortKey
Instead of partitionKey
and sortKey
booleans that mapped attributes to the primary key attributes, the v1 exposes a key
boolean option to tag attributes as being part of the primary key.
The renaming can simply be done through the savedAs
option, which is more explicit:
const PokemonEntity = new Entity({
...,
- attributes: {
- trainerId: { type: 'string', partitionKey: true },
- pokemonId: { type: 'string', sortKey: true },
- ...,
- }
+ schema: schema({
+ trainerId: string().key().savedAs('pk'),
+ pokemonId: string().key().savedAs('sk'),
+ ...,
+ })
})
The schema is validated against the Table
primary key. A computeKey
function is required if it doesn't match:
const PokemonEntity = new Entity({
...,
schema: schema({
- trainerId: string().key().savedAs('pk'),
- pokemonId: string().key().savedAs('sk'),
+ trainerId: string().key(),
+ pokemonId: string().key()
...,
}),
+ // 🙌 Type-safe!
+ computeKey: ({ trainerId, pokemonId }) => ({
+ pk: trainerId,
+ sk: pokemonId
+ })
})
saved
There are no equivalent to the saved: false
option for the moment. Feel free to open a discussion if you need it back.
required
Attributes are now required by default. You can tag them as optional via the .required("never")
method (or the equivalent .optional()
shorthand):
const PokemonEntity = new Entity({
...,
- attributes: {
- optional: { type: 'string' },
- required: { type: 'string', required: true },
- always: { type: 'string', required: 'always' },
- ...,
- }
+ schema: schema({
+ optional: string().optional(),
+ required: string(),
+ always: string().required('always'),
+ ...,
+ })
})
default
& onUpdate
The default
and onUpdate
options have been reworked into the following options:
putDefault
: Applied on put actions (e.g.PutItemCommand
)updateDefault
: Applied on update actions (e.g.UpdateItemCommand
)keyDefault
: Overrides other defaults on key attributes (ignored otherwise)default
: Shorthand that acts askeyDefault
on key attributes andputDefault
otherwise
const PokemonEntity = new Entity({
...,
- attributes: {
- level: { type: 'number', required: true, default: 1 },
- created: {
- type: 'string',
- required: true,
- default: () => new Date().toISOString()
- },
- ...,
- }
+ schema: schema({
+ level: number().default(1),
+ created: string().default(() => new Date().toISOString()),
+ ...,
+ })
})
If a default value is derived from other attributes, the v1 introduces a new notion called links
. See the Defaults & Links section for more details:
putLink
: Applied on put actions (e.g.PutItemCommand
)updateLink
: Applied on update actions (e.g.UpdateItemCommand
)keyLink
: Overrides other links on key attributes (ignored otherwise)link
: Shorthand that acts askeyLink
on key attributes andputLink
otherwise
const PokemonEntity = new Entity({
...,
- attributes: {
- level: { type: 'number', required: true },
- levelPlusOne: {
- type: 'number',
- required: true,
- default: ({ level }) => level + 1,
- },
- ...,
- }
+ schema: schema({
+ level: number(),
+ ...,
+ }).and(prevSchema => ({
+ levelPlusOne: number().link<typeof prevSchema>(
+ ({ level }) => level + 1
+ ),
+ })),
})
In vanilla JS, links
can be used directly in the original schema.
For example, we can make use of links to compute the primary key instead of using the computeKey
function:
const PokemonEntity = new Entity({
...,
- computeKey: ({ trainerId, pokemonId }) => ({
- pk: trainerId,
- sk: pokemonId
- })
schema: schema({
trainerId: string().key(),
pokemonId: string().key()
...,
+ }).and(prevSchema => ({
+ pk: string().key().link<typeof prevSchema>(
+ ({ trainerId }) => trainerId
+ ),
+ sk: string().key().link<typeof prevSchema>(
+ ({ pokemonId }) => pokemonId
+ ),
+ })),
})
dependsOn
The dependsOn
option has been removed.
Note that links are applied after defaults, but links and defaults in themselves are computed in no guaranteed order.
You can avoid link dependencies by factorizing the underlying code:
const PokemonEntity = new Entity({
...,
- attributes: {
- level: { type: 'number', required: true },
- levelPlusOne: {
- type: 'number',
- required: true,
- default: ({ level }) => level + 1,
- },
- levelPlusTwo: {
- type: 'number',
- required: true,
- default: ({ levelPlusOne }) => levelPlusOne + 1,
- dependsOn: ['levelPlusOne']
- },
- ...,
- }
+ schema: schema({
+ level: number(),
+ ...,
+ }).and(prevSchema => ({
+ levelPlusOne: number().link<typeof prevSchema>(
+ ({ level }) => level + 1
+ ),
+ levelPlusTwo: number().link<typeof prevSchema>(
+ ({ level }) => level + 2
+ ),
+ })),
})
transform
& format
The transform
and format
options have been merged into a single transform
option:
const PokemonEntity = new Entity({
...,
- attributes: {
- status: {
- type: 'string',
- required: true,
- transform: input => `STATUS#${input}`,
- format: output => output.slice(7)
- },
- ...,
- }
+ schema: schema({
+ status: string().transform({
+ parse: input => `STATUS#${input}`,
+ format: output => output.slice(7)
+ }),
+ ...,
+ })
})
The prefix
and suffix
options are now examples of transformers (see the list of available transformers for more infos).
coerce
The coerce
option has not been re-implemented yet, but is on the roadmap.
Misc.
The type
and setType
options are not useful and have been removed.
Using an array for composite keys is not supported anymore: Use links instead.
Commands
Instead of having dedicated methods, Tables
and Entities
now have a single .build
method which acts as a gateway to perform actions:
+ import { GetItemCommand } from 'dynamodb-toolbox/entity/actions/get'
- const { Item } = await PokemonEntity.get({ pokemonId })
+ const { Item } = await PokemonEntity.build(GetItemCommand)
+ .key({ pokemonId })
+ .send()
See the Getting Started section for more details on why we think this is a better syntax.
Adding custom parameters and clauses is not possible anymore, but you can always use the .params()
methods and build from there.
Table methods
Cat. | Method | Action | Description |
---|---|---|---|
Fetching | .scan(...) | ScanCommand | Performs a Scan Operation on a Table |
.query(...) | QueryCommand | Performs a Query Operation on a Table | |
Batching | .batchGet(...) | BatchGetCommand | Groups one or several BatchGetRequest from the Table entities to execute a BatchGetItem |
.batchWrite(...) | BatchWriteCommand | Groups one or several BatchPutRequest and BatchDeleteRequest from the Table entities to execute a BatchWriteItem operation | |
Transactions | .transactGet(...) | GetTransaction | Builds a transaction to get an entity item, to be used within TransactGetItems operations |
.transactWrite(...) | WriteTransaction | Builds a transaction to write entity items, to be used within TransactWriteItems operations |
Entity methods
Cat. | Method | Action | Description |
---|---|---|---|
General | .get(...) | GetItemCommand | Performs a GetItem Operation on an entity item |
.put(...) | PutItemCommand | Performs a PutItem Operation on an entity item | |
.update(...) | UpdateItemCommand | Performs a UpdateItem Operation on an entity item | |
.delete(...) | DeleteItemCommand | Performs a DeleteItem Operation on an entity item | |
Batching | .getBatch(...) | BatchGetRequest | Builds a request to get an entity item, to be used within BatchGetCommands |
.putBatch(...) | BatchPutRequest | Builds a request to put an entity item, to be used within BatchWriteCommands | |
.deleteBatch(...) | BatchDeleteRequest | Builds a request to delete an entity item, to be used within BatchWriteCommands | |
Transactions | .getTransaction(...) | GetTransaction | Builds a transaction to get an entity item, to be used within TransactGetItems operations |
.putTransaction(...) | PutTransaction | Builds a transaction to put an entity item, to be used within TransactWriteItems operations | |
.updateTransaction(...) | UpdateTransaction | Builds a transaction to update an entity item, to be used within TransactWriteItems operations | |
.deleteTransaction(...) | DeleteTransaction | Builds a transaction to delete an entity item, to be used within TransactWriteItems operations | |
.conditionCheck(...) | ConditionCheck | Builds a condition to check against an entity item for the transaction to succeed, to be used within TransactWriteItems operations | |
Fetching | .scan(...) | - | Not implemented yet, use the Table ScanCommand instead |
.query(...) | - | Not implemented yet, use the Table QueryCommand instead |
Condition Expressions
Conditions benefit from improved typing, and clearer logical combinations:
const v0Condition = [
{ attr: 'pokemonId', exists: false },
// 👇 'and' combination by default
{ attr: 'level', lte: 99 },
[
// 👇 'or' in first condition means 'or' for group
{ or: true, negate: true, ... }
...,
]
]
const v1Condition = {
and: [
{ attr: 'pokemonId', exists: false },
// 🙌 "lte" is correcly typed
{ attr: 'level', lte: 99 }
// 🙌 You can nest logical combinations
{
or: [
{ not: { ... } },
...,
]
},
]
}
Projection Expressions
Projections expressions can now be deep:
const projection = {
attributes: [
'pokemonId',
'level',
'some.deep.map.value',
'some.array[0].element'
]
}