Skip to main content

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 🥳

caution

Well, probably... as there are two exceptions:

  • If you de-activated the internal entity attribute (by setting entityField to false), 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):

const MyTable = new Table({
indexes: {
- byTrainerId: { partitionKey: 'trainerId', sortKey: 'level' },
+ byTrainerId: {
+ type: 'global',
+ partitionKey: { name: 'trainerId', type: 'string' },
+ 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:

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
+ }
+ }
})

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 as keyDefault on key attributes and putDefault 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 as keyLink on key attributes and putLink 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
+ ),
+ })),
})
note

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.MethodActionDescription
Fetching.scan(...)ScanCommandPerforms a Scan Operation on a Table
.query(...)QueryCommandPerforms a Query Operation on a Table
Batching.batchGet(...)BatchGetCommandGroups one or several BatchGetRequest from the Table entities to execute a BatchGetItem
.batchWrite(...)BatchWriteCommandGroups one or several BatchPutRequest and BatchDeleteRequest from the Table entities to execute a BatchWriteItem operation
Transactions.transactGet(...)GetTransactionBuilds a transaction to get an entity item, to be used within TransactGetItems operations
.transactWrite(...)WriteTransactionBuilds a transaction to write entity items, to be used within TransactWriteItems operations

Entity methods

Cat.MethodActionDescription
General.get(...)GetItemCommandPerforms a GetItem Operation on an entity item
.put(...)PutItemCommandPerforms a PutItem Operation on an entity item
.update(...)UpdateItemCommandPerforms a UpdateItem Operation on an entity item
.delete(...)DeleteItemCommandPerforms a DeleteItem Operation on an entity item
Batching.getBatch(...)BatchGetRequestBuilds a request to get an entity item, to be used within BatchGetCommands
.putBatch(...)BatchPutRequestBuilds a request to put an entity item, to be used within BatchWriteCommands
.deleteBatch(...)BatchDeleteRequestBuilds a request to delete an entity item, to be used within BatchWriteCommands
Transactions.getTransaction(...)GetTransactionBuilds a transaction to get an entity item, to be used within TransactGetItems operations
.putTransaction(...)PutTransactionBuilds a transaction to put an entity item, to be used within TransactWriteItems operations
.updateTransaction(...)UpdateTransactionBuilds a transaction to update an entity item, to be used within TransactWriteItems operations
.deleteTransaction(...)DeleteTransactionBuilds a transaction to delete an entity item, to be used within TransactWriteItems operations
.conditionCheck(...)ConditionCheckBuilds 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'
]
}