Internal Attributes
The Entity
constructor automatically adds internal attributes to your schemas:
- An Entity Attribute (required) that tags items with the
name
of the entity. - Two Timestamp Attributes (optional) that record when the item was created and last modified with timestamps in ISO 8601 format.
If the schema contains a conflicting attribute, the constructor throws a reservedAttributeName
error. To avoid this, DynamoDB-Toolbox allows you to customize the name
and savedAs
properties of internal attributes.
You can get familiar with the internal attributes by using the FormattedItem
and SavedItem
types (see Type Inference for more details):
🔎 Show code
import type { FormattedItem, SavedItem } from 'dynamodb-toolbox/entity'
const PokemonEntity = new Entity({
name: 'Pokemon',
schema: item({
pokemonClass: string().key().savedAs('pk'),
pokemonId: string().key().savedAs('sk'),
level: number()
}),
...
})
// Pokemons in DynamoDB:
type SavedPokemon = SavedItem<typeof PokemonEntity>
// => {
// pk: string,
// sk: string,
// level: number,
// _et: "Pokemon",
// _ct: string,
// _md: string,
// }
// Fetched Pokemons: (`entity` attribute is hidden)
type FormattedPokemon = FormattedItem<typeof PokemonEntity>
// => {
// pokemonClass: string,
// pokemonId: string,
// level: number,
// created: string,
// modified: string,
// }
entity
A string attribute that tags your items with the Entity
name.
While this attribute can be opted out of, we strongly recommend keeping it enabled if you use Single Table Design: It improves performance and enables entity-based filtering when fetching items from multiple entities within the same Table
(e.g. Queries or Scans).
If you use it, note that:
- The library automatically re-introduces the entity attribute during reads and writes, so you can start using DynamoDB-Toolbox even if your items don't contain it.
- However, we still recommend migrating your items for improved performance and entity-based filtering.
- Finally, the
name
of anEntity
cannot be updated once it has its first items (at least not without a data migration).
By default, the attribute is hidden and named entity
. This can be overridden via the entityAttribute
property:
- Default
- Disabled
- Custom
const PokemonEntity = new Entity({
name: 'Pokemon',
entityAttribute: true,
...
})
const PokemonEntity = new Entity({
name: 'Pokemon',
entityAttribute: false,
...
})
const PokemonEntity = new Entity({
name: 'Pokemon',
entityAttribute: {
name: '__entity__',
hidden: false
},
...
})
The savedAs
property must be specified at the Table
level, via the entityAttributeSavedAs
property.
Timestamp Attributes
There are two timestamp attributes. Both of them are string attributes containing timestamps in ISO 8601 format:
created
records when the item was createdmodified
records when the item was last modified
Timestamp attributes are optional. You can opt out by setting off the timestamps
property:
const PokemonEntity = new Entity({
...
// 👇 deactivates both timestamps
timestamps: false
})
You can also manage them independently:
const PokemonEntity = new Entity({
...
timestamps: {
created: true,
// 👇 deactivates `modified` attribute
modified: false
}
})
Customizing Timestamps:
Instead of true
, you can provide an object to fine-tune each attribute. Available options:
name
The name of the attribute:
const PokemonEntity = new Entity({
...
timestamps: {
...
modified: {
// `modified` by default
name: 'lastModified'
}
}
})
savedAs
The savedAs
property of the attribute:
const PokemonEntity = new Entity({
...
timestamps: {
...
modified: {
// `_md` by default
savedAs: '__lastMod__'
}
}
})
hidden
Whether the attribute is hidden or not when formatting:
const PokemonEntity = new Entity({
...
timestamps: {
...
modified: {
// `false` by default
hidden: true
}
}
})
From variable
When assigned to a variable, narrow the object type with as const
to preserve correct typescript types for the entity.
const customTimestamps = {
created: false,
modified: {
name: 'updatedAt'
}
} as const
const PokemonEntity = new Entity({
...
timestamps: customTimestamps
})
Linking Timestamps
It can be useful to link Timestamp Attributes elsewhere in the schema, for instance to query sorted items efficiently.
Timestamp Attributes are automatically added by the Entity
class constructor. Thus, while direct references work in vanilla JS, they need to be reintroduced manually in TypeScript to keep type inference working.
In this example, we define a PostEntity
to allow querying posts by ownerId
(using a GSI) and returning them sorted by creation date:
const now = () => new Date().toISOString()
const PostEntity = new Entity({
name: 'POST',
table: PostsTable,
// Deactivate internal attributes
timestamps: false,
schema: item({
postId: string().key(),
ownerId: string(),
// We set the timestamp attributes manually
created: string().default(now).savedAs('_ct'),
modified: string()
.putDefault(now)
.updateDefault(now)
.savedAs('_md')
}).and(prevSchema => ({
byOwnerPK: string()
.link<typeof prevSchema>(({ ownerId }) => ownerId)
.hidden(),
byOwnerSK: string()
.link<typeof prevSchema>(
// 🙌 Type inference works!
({ created, postId }) => `${created}#${postId}`
)
.hidden()
}))
})
We can now query all posts by ownerId
, sorted by creation date, using the byOwner
GSI:
import { QueryCommand } from 'dynamodb-toolbox/table/actions/query'
await PostsTable.build(QueryCommand)
.entities(PostEntity)
.query({ index: 'byOwner', partition: ownerId })
.options({
maxPages: Infinity, // Beware of RAM issues
reverse: true // Sort descending (new first)
})
.send()