Skip to main content

UpdateItemCommand

Performs a UpdateItem Operation on an entity item:

import { UpdateItemCommand } from 'dynamodb-toolbox/entity/actions/update'

const updateItemCommand = PokemonEntity.build(
UpdateItemCommand
)

const params = updateItemCommand.params()
await updateItemCommand.send()

Request

.item(...)

(required)

The attributes to update, including the key:

import { UpdateItemCommand, $add } from 'dynamodb-toolbox/entity/actions/update'

await PokemonEntity.build(UpdateItemCommand)
.item({
pokemonId: 'pikachu1',
level: $add(1),
...
})
.send()

You can use the UpdateItemInput type to explicitly type an object as a UpdateItemCommand item object:

import type { UpdateItemInput } from 'dynamodb-toolbox/entity/actions/update'

const item: UpdateItemInput<typeof PokemonEntity> = {
pokemonId: 'pikachu1',
level: $add(1),
...
}

await PokemonEntity.build(UpdateItemCommand).item(item).send()

UpdateItemInput differs from PutItemInput as it is partial by default, except for always required attributes without defaults or links.

It also benefits from an extended syntax that reflects the capabilities of DynamoDB:

Removing an attribute

Any optional attribute can be removed with the $remove extension:

import { $remove } from 'dynamodb-toolbox/entity/actions/update'

await PokemonEntity.build(UpdateItemCommand)
.item({
pokemonId: 'pikachu1',
// 👇 clear 'statusEffect' from pokemon
statusEffect: $remove()
})
.send()

References

You can indicate DynamoDB to resolve an attribute at write time with the $get extension:

import { $get } from 'dynamodb-toolbox/entity/actions/update'

await PokemonEntity.build(UpdateItemCommand)
.item({
...
level: 42,
// 👇 fill 'previousLevel' with current 'level'
previousLevel: $get('level')
})
.send()

Self-references are possible. You can also provide a fallback as a second argument (which can also be a reference) in case the specified attribute path misses from the item:

await PokemonEntity.build(UpdateItemCommand)
.item({
...
previousLevel: $get('level', 1),
// 👇 fallback can also be a reference!
chainedRefs: $get(
'firstRef',
$get('secondRef', 'Sky is the limit!')
)
})
.send()

Note that the attribute path is type-checked, but whether its attribute value extends the updated attribute value is not for the moment, so be extra-careful:

await PokemonEntity.build(UpdateItemCommand)
.item({
// ❌ Raises a type error
name: $get('non.existing[0].attribute'),
// ...but overriding a number by a string doesn't 🙈
level: $get('name')
})
.send()

Flat attributes

In the case of flat attributes (primitives and sets), updates completely override their current values:

await PokemonEntity.build(UpdateItemCommand)
.item({
...
// 👇 Set fields to desired values
isLegendary: true,
nextLevel: 42,
name: 'Pikachu',
binEncoded: new Uint8Array(...),
skills: new Set(['thunder'])
})
.send()

Numbers benefit from additional $sum, $subtract and $add extensions, which can use references:

import {
$add,
$subtract,
$get
} from 'dynamodb-toolbox/entity/actions/update'

await PokemonEntity.build(UpdateItemCommand)
.item({
...
// 👇 lose 20 health points
health: $subtract($get('health'), 20),
// 👇 gain 1 level
level: $sum($get('level', 0), 1),
// ...similar to
level: $add(1)
})
.send()

Sets benefit from additional $add and $delete extensions, which can be used to add or remove specific values:

import {
$add,
$delete
} from 'dynamodb-toolbox/entity/actions/update'

await PokemonEntity.build(UpdateItemCommand)
.item({
...,
skills: $add(new Set(['thunder', 'dragon-tail'])),
types: $delete(new Set(['flight']))
})
.send()

Deep attributes

In the case of deep attributes (e.g. lists, maps and records), updates are partial by default:

// 👇 Partial overrides
await PokemonEntity.build(UpdateItemCommand)
.item({
...
// 👇 Elements 0 and 2 are updated
skills: ['thunder', undefined, $remove()],
// ...similar to
skills: {
0: 'thunder',
2: $remove()
},
// 👇 Map
some: {
deep: {
field: 'foo'
}
},
// 👇 Record
bestSkillByType: {
electric: 'thunder',
flight: $remove()
}
})
.send()

You can use the $set extension to specify a complete override:

import { $set } from 'dynamodb-toolbox/entity/actions/update'

// 👇 Complete overrides
await PokemonEntity.build(UpdateItemCommand).item({
...
// Resets list
skills: $set(['thunder']),
// Removes all other map attributes
some: $set({
deep: {
field: 'foo',
otherField: 42
}
}),
// Removes all other record keys
bestSkillByType: $set({
electric: 'thunder'
})
})

Lists benefit from additional $append and $prepend extensions, which can use references:

import {
$append,
$prepend
} from 'dynamodb-toolbox/entity/actions/update'

PokemonEntity.build(UpdateItemCommand).item({
...
skills: $append(['thunder', 'dragon-tail']),
levelHistory: $append($get('level')),
types: $prepend(['flight']),
})
info

$append and $prepend are upserts: they create a new list if the attribute is missing from the item.

.options(...)

Provides additional options:

await PokemonEntity.build(UpdateItemCommand)
.item(...)
.options({
returnValues: 'UPDATED_OLD',
capacity: 'TOTAL',
...
})
.send()

You can use the UpdateItemOptions type to explicitly type an object as an UpdateItemCommand options object:

import type { UpdateItemOptions } from 'dynamodb-toolbox/entity/actions/update'

const options: UpdateItemOptions<typeof PokemonEntity> = {
returnValues: 'UPDATED_OLD',
capacity: 'TOTAL',
...
}

await PokemonEntity.build(UpdateItemCommand)
.item(...)
.options(options)
.send()

Available options (see the DynamoDB documentation for more details):

OptionTypeDefaultDescription
conditionCondition<typeof PokemonEntity>-A condition that must be satisfied in order for the UpdateItemCommand to succeed.

See the ConditionParser action for more details on how to write conditions.
returnValuesReturnValuesOption"NONE"To get the item attributes as they appeared before they were updated with the request.

Possible values are "NONE", "UPDATED_NEW", "ALL_NEW", "UPDATED_OLD" and "ALL_OLD".
metricsMetricsOption"NONE"Determines whether item collection metrics are returned.

Possible values are "NONE" and "SIZE".
capacityCapacityOption"NONE"Determines the level of detail about provisioned or on-demand throughput consumption that is returned in the response.

Possible values are "NONE", "TOTAL" and "INDEXES".
tableNamestring-Overrides the Table name. Mostly useful for multitenancy.
Examples
await PokemonEntity.build(UpdateItemCommand)
.item({
pokemonId: 'pikachu1',
level: $add(1)
})
.options({
// 👇 Make sure that 'level' stays <= 99
condition: { attr: 'level', lt: 99 }
})
.send()

Response

The data is returned using the same response syntax as the DynamoDB UpdateItem API, with an additional ToolboxItem property, which allows you to retrieve the item generated by DynamoDB-Toolbox:

const { ToolboxItem: generatedPokemon } =
await PokemonEntity.build(UpdateItemCommand)
.item(...)
.send()

// 👇 Great for auto-generated attributes
const modifiedTimestamp = generatedPokemon.modified

If present, the returned attributes are formatted by the Entity.

You can use the UpdateItemResponse type to explicitly type an object as an UpdateItemCommand response object:

import type { UpdateItemResponse } from 'dynamodb-toolbox/entity/actions/update'

const response: UpdateItemResponse<
typeof PokemonEntity,
// 👇 Optional options
{ returnValues: 'ALL_OLD' }
// 👇 Typed as Pokemon | undefined
> = { Attributes: ... }

Extended Syntax

In some contexts, like when defining updateLinks, it may be useful to understand extended syntax in greater details.

To avoid conflicts with regular syntax, extensions are defined through objects with symbols as keys:

import {
$add,
// 👇 Unique symbols
$ADD,
$IS_EXTENSION
} from 'dynamodb-toolbox/entity/actions/update/symbols'

const addOne = $add(1)

// 👇 Equivalent to
const addOne = {
[$ADD]: 1,
[$IS_EXTENSION]: true
}

If you need to build complex update links, all those symbols are exported, as well as dedicated type guards. If you don't, you can exclude extended syntax altogether with the isExtension type guard.

Here's an example in which we automatically derive pokemon level upgrades:

import {
$add,
$get,
$subtract,
isAddition,
isExtension,
isGetting,
$ADD
} from 'dynamodb-toolbox/entity/actions/update/symbols'

const pokemonSchema = schema({
...
level: number()
}).and(prevSchema => ({
lastLevelUpgrade: number().updateLink<typeof prevSchema>(
({ level }) => {
if (level === undefined) {
return undefined
}

if (isAddition(level)) {
return level[$ADD]
}

if (!isExtension(level) || isGetting(level)) {
return $subtract(level, $get('level'))
}
}
)
}))

await PokemonEntity.build(UpdateItemCommand)
.item({
pokemonId,
// 👇 lastLevelUpgrade = 3
level: $add(3)
// 👇 lastLevelUpgrade = $subtract(10, $get('level'))
level: 10,
// 👇 lastLevelUpgrade = $subtract($get('otherAttr'), $get('level'))
level: $get('otherAttr'),
})
.send()
Example
🔎 $remove
import {
$remove,
$REMOVE,
$IS_EXTENSION,
isRemoval
} from 'dynamodb-toolbox/entity/actions/update/symbols'

const removal = $remove()

// 👇 Equivalent to
const removal = {
[$REMOVE]: true,
[$IS_EXTENSION]: true
}

const link = (input: UpdateItemInput) => {
if (isRemoval(input)) {
input[$REMOVE] // => true
}
}
🔎 $get
import {
$get,
$GET,
$IS_EXTENSION,
isGetting
} from 'dynamodb-toolbox/entity/actions/update/symbols'

const getting = $get('attr', 'fallback')

// 👇 Equivalent to
const getting = {
[$GET]: ['attr', 'fallback'],
[$IS_EXTENSION]: true
}

const link = (input: UpdateItemInput) => {
if (isGetting(input)) {
input[$GET] // => ['attr', 'fallback']
}
}
🔎 $sum
import {
$sum,
$SUM,
$IS_EXTENSION,
isSum
} from 'dynamodb-toolbox/entity/actions/update/symbols'

const sum = $sum(1, 2)

// 👇 Equivalent to
const sum = {
[$SUM]: [1, 2],
[$IS_EXTENSION]: true
}

const link = (input: UpdateItemInput) => {
if (isSum(input)) {
input[$SUM] // => [1, 2]
}
}
🔎 $subtract
import {
$subtract,
$SUBTRACT,
$IS_EXTENSION,
isSubtraction
} from 'dynamodb-toolbox/entity/actions/update/symbols'

const subtraction = $subtract(1, 2)

// 👇 Equivalent to
const subtraction = {
[$SUBTRACT]: [1, 2],
[$IS_EXTENSION]: true
}

const link = (input: UpdateItemInput) => {
if (isSubtraction(input)) {
input[$SUBTRACT] // => [1, 2]
}
}
🔎 $add
import {
$add,
$ADD,
$IS_EXTENSION,
isAddition
} from 'dynamodb-toolbox/entity/actions/update/symbols'

const addition = $add(1)

// 👇 Equivalent to
const addition = {
[$ADD]: 1,
[$IS_EXTENSION]: true
}

const link = (input: UpdateItemInput) => {
if (isAddition(input)) {
input[$ADD] // => 1
}
}
🔎 $delete
import {
$delete,
$DELETE,
$IS_EXTENSION,
isDeletion
} from 'dynamodb-toolbox/entity/actions/update/symbols'

const deletion = $delete(new Set(['flight']))

// 👇 Equivalent to
const deletion = {
[$DELETE]: new Set(['flight']),
[$IS_EXTENSION]: true
}

const link = (input: UpdateItemInput) => {
if (isDeletion(input)) {
input[$DELETE] // => new Set(['flight'])
}
}
🔎 $set
import {
$set,
$SET,
$IS_EXTENSION,
isSetting
} from 'dynamodb-toolbox/entity/actions/update/symbols'

const setting = $set({
deep: {
field: 'foo',
otherField: 42
}
})

// 👇 Equivalent to
const setting = {
[$SET]: {
deep: {
field: 'foo',
otherField: 42
}
},
[$IS_EXTENSION]: true
}

const link = (input: UpdateItemInput) => {
if (isSetting(input)) {
input[$SET] // => {
// deep: {
// field: 'foo',
// otherField: 42
// }
// }
}
}
🔎 $append
import {
$append,
$APPEND,
$IS_EXTENSION,
isAppending
} from 'dynamodb-toolbox/entity/actions/update/symbols'

const appending = $append(['thunder', 'dragon-tail'])

// 👇 Equivalent to
const appending = {
[$APPEND]: ['thunder', 'dragon-tail'],
[$IS_EXTENSION]: true
}

const link = (input: UpdateItemInput) => {
if (isAppending(input)) {
input[$APPEND] // => ['thunder', 'dragon-tail']
}
}
🔎 $prepend
import {
$prepend,
$PREPEND,
$IS_EXTENSION,
isPrepending
} from 'dynamodb-toolbox/entity/actions/update/symbols'

const prepending = $prepend(['thunder', 'dragon-tail'])

// 👇 Equivalent to
const prepending = {
[$PREPEND]: ['thunder', 'dragon-tail'],
[$IS_EXTENSION]: true
}

const link = (input: UpdateItemInput) => {
if (isPrepending(input)) {
input[$PREPEND] // => ['thunder', 'dragon-tail']
}
}