Fields
Every field of an Item (or object) type requires at least:
- A name, which is used in generated code and can be referenced in key path templates.
- A
type
which determines what kind of values can go in the field, how the field is stored, and what type the generated code will use for it. - A
fieldNum
(field number) which must be unique within fields and which must never be reused. Field numbers are used to store and transfer data more compactly.
Besides that, there are some optional properties:
required
: By default, all fields are required, meaning it must be set or the item is invalid. The definition of “set” is a value other than the zero value for that type. Setrequired: false
to allow a field to be unset.valid
: A validation expression for the field, expressed in the CEL language. This is in addition to any validation inherent in the data type itself.deprecated
: This will cause a deprecation annotation to be added to the generated code.fromMetadata
which populates the field from Item metadata.initialValue
which automatically sets the field value when the Item is created.
For example, here’s a User
item type with a few fields:
Initial Value Fields
There are times when you want your database to choose a value for you. One common pattern is picking a unique identifier for an Item. For example, in the relational database world this is often an auto-incrementing integer (eg: AUTO_INCREMENT
in MySQL or SERIAL
/SEQUENCE
in Postgres). When StatelyDB chooses an identifier via initialValue
, it guarantees that no Item already exists with the same key path. You can specify the initialValue
property when configuring a field, with the following variants:
uuid
initialValue: 'uuid'
produces a globally unique UUIDv4. UUIDs are a good choice for when you need an ID to be globally unique no matter where the ID is in its key path. The type of the field must also be uuid
.
rand53
initialValue: 'rand53'
generates an unsigned random 53-bit integer. 53-bit integers are the maximum safe integer size in Javascript, so this is a good choice if you are primarily using StatelyDB in a Javascript environment and want maximum compatibility. It is also a 50% smaller alternative to UUIDs that can still be unique within your Store. The type of the field must be a uint
. Note that unlike UUIDs, rand53
values may repeat in different key paths—for example you might have /user-1234
and /post-1234
and /post-6789/comment-1234
. The only thing that matters here is that the entire key path is unique.
sequence
initialValue: 'sequence'
generates an unsigned, monotonically increasing integer starting from 1, where the counter is unique per parent Key Path. This is ideal for when data should have a consistent order based on insertion order, such as a list of messages within a conversation. The type of the field must be an integer type, ideally uint
. This can only be used for items within a Group—it cannot be used in a Group Key.
For example, if we have the key path template /customer-:customerID/order-:orderId
for the Order Item type. orderId
can be a sequence
, and each order ID for a customer will count up 1, 2, 3, and so on. You can have another key path template for a LineItem that looks like /customer-:customerId/order-:orderId/li-:lineItemId
and lineItemId
can be a sequence
, and each line item within an Order will count up 1, 2, 3, and so on. In this setup, the third line item for the fifth order a Customer makes would have the key path /customer-12957/order-5/li-3
. However, customerId
cannot be a sequence
, because it is a Group Key. If we allowed Group Keys to be sequence
s, then Groups would no longer be well-partitioned, which could impact scaling.
Metadata Fields
Every Item in StatelyDB automatically tracks the following metadata:
createdAtTime
- The timestamp in microseconds when the Item was created.lastModifiedTime
- The timestamp in microseconds when the Item was last modified.createdAtVersion
- The Group Version of the Item’s Group minted when the Item was created.lastModifiedVersion
- The Group Version of the Item’s Group minted when the Item was last modified.
By default these fields are not exposed. In order to use these fields you need to define a field in the Item type definition in your schema, and map it to one of these metadata with fromMetadata
. Any field with fromMetadata
is read-only and any values written to it will be ignored.
Note: Metadata fields still require the correct type so that we know how to present the underlying data. For instance, it is necessary to choose the granularity of timestamp desired to access any of the timestamp-metadata fields.
Here is an example of a model that uses all of these metadata fields: