AnyOf
Defines a union of types, i.e. a range of possible types:
import { anyOf } from 'dynamodb-toolbox/attributes/anyOf'
const pokemonSchema = schema({
...
pokeType: anyOf(
string().const('fire'),
string().const('grass'),
string().const('water')
)
})
type FormattedPokemon = FormattedItem<typeof PokemonEntity>
// => {
// ...
// pokeType: 'fire' | 'grass' | 'water'
// }
In this example, an enum
would have done the trick. However, anyOf
becomes particularly powerful when used in conjunction with a map
and the enum
or const
directives of a primitive attribute, to implement polymorphism:
const pokemonSchema = schema({
...
captureState: anyOf(
map({
status: string().const('caught'),
// 👇 captureState.trainerId exists if status is "caught"...
trainerId: string()
}),
// ...but not otherwise! 🙌
map({ status: string().const('wild') })
)
})
type FormattedPokemon = FormattedItem<typeof PokemonEntity>
// => {
// ...
// captureState:
// | { status: "caught", trainerId: string }
// | { status: "wild" }
// }
Parsing an anyOf
attribute value returns the parsed output of the first sub-schema it validates against.
This means the order of the sub-schemas matters: you should always start with the strictest schemas.
For the moment, anyOf
properties can only be set by using methods.
AnyOf elements can have any type. However, they must respect some constraints:
- They cannot be
optional
or always required - They cannot be
hidden
orkey
(tagging therecord
itself askey
is enough) - They cannot have
default
orlinks
// ❌ Raises a type AND a run-time error
const union = anyOf(number(), string().optional())
const union = anyOf(number(), string().hidden())
const union = anyOf(number(), string().key())
const union = anyOf(number(), string().default('foo'))
Options
.required()
string | undefined
Tags the attribute as required (at root level or within Maps). Possible values are:
'atLeastOnce' (default)
: Required (starting value)'always'
: Always required (including updates)'never'
: Optional
const pokeTypeSchema = anyOf(
string().const('fire'),
string().const('grass'),
string().const('water')
).required()
// shorthand for `.required('never')`
const pokeTypeSchema = anyOf(...).optional()
.hidden()
boolean | undefined
Skips the attribute when formatting items:
const pokeTypeSchema = anyOf(
string().const('fire'),
string().const('grass'),
string().const('water')
).hidden()
.key()
boolean | undefined
Tags the attribute as a primary key attribute or linked to a primary attribute:
// Note: The method also sets the `required` property to 'always'
// (it is often the case in practice, you can still use `.optional()` if needed)
const pokeTypeSchema = anyOf(
string().const('fire'),
string().const('grass'),
string().const('water')
).key()
.savedAs(...)
string
Renames the attribute during the transformation step (at root level or within Maps):
const pokeTypeSchema = anyOf(
string().const('fire'),
string().const('grass'),
string().const('water')
).savedAs('pkt')
.default(...)
ValueOrGetter<ATTRIBUTES>
Specifies default values for the attribute. See Defaults and Links for more details:
- Put/Update
- Key
const now = () => new Date().toISOString()
const hasUpdateSchema = anyOf(
map({ hasUpdate: boolean().const(false) }),
map({ hasUpdate: boolean().const(true), date: string() })
)
.default(() => ({ hasUpdate: false }))
.updateDefault(() => ({ hasUpdate: true, date: now() }))
// 👇 Similar to
const timestampsSchema = anyOf('...')
.putDefault(() => ({ hasUpdate: false }))
.updateDefault(() => ({ hasUpdate: true, date: now() }))
const idsSchema = anyOf(
map({
hasSubId: boolean().const(false),
id: string()
}),
map({
hasSubId: boolean().const(true),
id: string(),
subId: string()
})
)
.key()
.default({ hasSubId: false, id: '123' })
// 👇 Similar to
const idsSchema = anyOf(...)
.key()
.keyDefault({ hasSubId: false, id: '123' })
.link<Schema>(...)
Link<SCHEMA, ELEMENTS>
Similar to .default(...)
but allows deriving the default value from other attributes. See Defaults and Links for more details:
const pokemonSchema = schema({
name: string().optional(),
level: number()
}).and(prevSchema => ({
metadata: anyOf(string(), number()).link<
typeof prevSchema
>(
// 🙌 Correctly typed!
({ name, level }) => name ?? level
)
}))
.validate(...)
Validator<ELEMENTS>
Adds custom validation to the attribute. See Custom Validation for more details:
const nonEmptyListSchema = anyOf(
list(string()),
list(number())
).validate(input => input.length > 0)
// 👇 Similar to
const nonEmptyListSchema = anyOf(
list(string()),
list(number())
).putValidate(input => input.length > 0)