Skip to content

Defining Item Types

Stately schemas are TypeScript code that’s checked in to your code repository alongside your service code. You write your schema in TypeScript even if you’ll be using a Go, Ruby, or Python client. You write your schema using helper methods imported from the @stately-cloud/schema package. We recommend opening your schema directory in VSCode since it has built-in support for TypeScript.

Make sure to follow the Define a Schema to get set up with the tools and configuration to build schemas. This document is a reference for how the schema builder API works, and what options you have for defining schema.

Your schema will consist of as many different type declarations as you want. Each of these is constructed through one of the type builders in the @stately-cloud/schema package, such as itemType, objectType, enumType, or type. Since your schema is defined using regular TypeScript, you can use variables, shared constants, functions, even loops (please be reasonable). This also means you can break up your schema into multiple files and import them all together using JavaScript modules however you like. The only thing to keep in mind is that the CLI will load a single file to find all your types, so you should have a top level schema.ts or index.ts that imports all your other files. For example, if you’ve made a user.ts, address.ts, and orders.ts that each have some types in them, you could have an index.ts that looks like:

index.ts
import './user.js';
import './address.js';
import './orders.js';

Notice that the file extensions are .js, not .ts. That’s because TypeScript gets translated to JavaScript before being run. It’s weird, we know!

Here’s an example type declaration (the ”…” is a lot of omitted code):

import { itemType } from "@stately-cloud/schema";
itemType("User", { ... });

The type builder function itemType is configuring a new type. The name for the new type is the first argument to the itemType function.

Unlike itemTypes, custom types like objectType, enumType, or type are used to declare reusable types that you can use as fields of other types. This means we need to assign them to a variable so we can reference them in fields.

import { itemType, objectType } from "@stately-cloud/schema";
const Address = objectType("Address", { ... });
// Now we can use Address as a field of User
itemType("User", {
fields: {
...
address: { type: Address },
...
}
})

Here we assign a type to the variable const Address - the name of that variable doesn’t actually matter, but it’s less confusing if you name it the same as the name of the type you’re declaring.

There’s an alternate way to declare types, using a function (not an arrow function):

import { objectType } from "@stately-cloud/schema";
export function Address() {
return objectType("Address", { ... });
}

This has the same result, but there’s one big advantage - types declared as functions are resolved lazily, which means you can use a type before it’s declared. This is very helpful when making circular data structures—imagine a User has an Address but the Address also has a list of Users—which one needs to be declared first? If you declare at least one of them as a function, it doesn’t matter.

Each Item in your Store belongs to an Item Type which defines its shape and how it is stored. Item Types are declared using the itemType function:

import { itemType, uuid } from "@stately-cloud/schema";
itemType("User", {
keyPath: ["/user-:id"],
fields: {
id: {
type: uuid,
initialValue: "uuid",
},
// more fields...
},
});

Each Item Type has the following required properties:

  1. A name, which is the first argument to the itemType function. Item Type names must be unique within the schema, and by convention are CamelCase.
  2. At least one keyPath which provides an address to store the Item at.
  3. fields that define all the fields of the item type. This is an object where each property is the name of a field, and the value configures that field. Field names are by convention camelCase.

Along with some optional properties:

  1. ttl (Time To Live) - A way to define how long an Item should exist before being automatically deleted from the store.
  2. indexes - A list of group-local indexes that allow for different ways to list items in the same Group.

You can document your types and their fields using regular JSDoc comments (/** */). We analyze your source code to automatically bring these comments along in your generated code. Normal comments (// or /* */) are ignored. Note that if you stray from the patterns above and generate your types dynamically, your documentation might not make it through the process.

import { itemType, uuid } from "@stately-cloud/schema";
/**
* This type represents a user in my system.
*/
itemType("User", {
keyPath: ["/user-:id"],
fields: {
/** This field is documented */
id: {
type: uuid,
initialValue: "uuid",
},
// more fields...
},
});