Model
The Model class: look up the models in your schema, create items from them, list models, define new models in code, and how model edits are saved.
A Model is one content type in a schema, such as Post. You usually get one with Model.find and use it to create items. You can also define new models in code with Model.create.
import { Model, AmbiguousModelError } from '@seedprotocol/sdk'Call client.init() (or render SeedClientGate) before using Model. The examples use the blog schema from Your first schema.
Model.find
Finds a model by name or ID, loading it from the local database if it isn't loaded yet, and waits for it to be ready.
static find(options: {
modelName?: string
schemaName?: string
modelFileId?: string
waitForReady?: boolean
readyTimeout?: number
hold?: boolean
}): Promise<Model | undefined>Parameters
| Name | Type | Default | Description |
|---|---|---|---|
modelName | string | none | The model name, for example 'Post'. |
schemaName | string | none | The schema's metadata.name. Pass it whenever you know it; model names are only unique within a schema. |
modelFileId | string | none | The model's stable ID (model.id). Takes priority over modelName. |
waitForReady | boolean | true | Wait for the model to finish loading. |
readyTimeout | number | 15000 | How long to wait, in milliseconds. The Promise rejects on timeout. |
hold | boolean | false | Take a cache hold, released with unload(). The model stays cached without one. A call that rejects gives its hold back. |
Returns
The Model, or undefined if no model matches (or you passed neither modelName nor modelFileId).
If you pass modelName without schemaName and two loaded schemas both define that model, find throws an AmbiguousModelError.
Example
const Post = await Model.find({ modelName: 'Post', schemaName: 'blog' })
if (!Post) throw new Error('Post model is not loaded')
const post = await Post.create({ title: 'Hello, world!' })model.create (new items)
Creates an item of this model. This is the instance method; for defining a new model, see Model.create.
create(values: Partial<ItemData> & Record<string, any>): Promise<Item<any>>Parameters
| Name | Type | Description |
|---|---|---|
values | object | Property values keyed by property name. Keys that aren't properties of the model are rejected. |
The model's name, schema name and the model itself are passed along for you, so the item is created in the right schema even when another schema has a model with the same name.
Returns
A Promise that resolves to the new Item once it's written to the local database and ready (up to 15 seconds). Throws Cannot create item: model name is not set on this Model instance if the model has no name, and EntityUnloadedError if the model was destroyed or evicted (see unload()).
Example
const Author = await Model.find({ modelName: 'Author', schemaName: 'blog' })
const Post = await Model.find({ modelName: 'Post', schemaName: 'blog' })
if (!Author || !Post) throw new Error('blog schema is not loaded')
const author = await Author.create({ name: 'Jane Doe' })
const post = await Post.create({
title: 'Hello, world!',
published: false,
author: author.seedLocalId,
})Values passed to create() are validated first, as in Item.create: an unknown key or an invalid value rejects with ItemCreateValidationError, and nothing is written. Unlike Item.create, there's no option to skip the check. Values read back as strings; see What you get back.
Instance properties
| Property | Type | Description |
|---|---|---|
name | string | The model name. Read-only in TypeScript; to rename, assign modelName. |
modelName | string | undefined | The model name. Assigning renames the model (see Editing a model). |
schemaName | string | undefined | The name of the schema the model belongs to. |
id | string | undefined | The model's stable ID (modelFileId): the schema file's, one derived from the schema and model names when the file has none (see id), or a random one for a model made with Model.create. |
properties | ModelProperty[] | The model's ModelProperty instances. Typed as any[]. A snapshot, not reactive; use useModelProperties in React. |
status | 'loading' | 'ready' | 'error' | The load status: 'loading' until the model is loaded and any properties passed to Model.create exist, then 'ready', or 'error' if the load failed. |
isEdited | boolean | true when the model has unsaved edits. |
validationErrors | ValidationError[] | Errors from the last validation. |
isValid | boolean | true when validationErrors is empty. |
lastSaveError | Error | undefined | The error from the last background save that a rename started. Cleared when a later one succeeds. |
for (const property of Post.properties) {
console.log(property.name, property.dataType) // 'title' 'Text', …
}waitForReady({ timeout }) resolves once status is 'ready'. It rejects with the load's error, or after timeout milliseconds (default 15000). While status is 'error', entityLoadError(model) returns that error.
properties and description can't be assigned: both throw. A model's description from the schema file isn't loaded onto the instance.
Model.all
Lists models as Model instances.
static all(
schemaName?: string,
options?: { waitForReady?: boolean; readyTimeout?: number; hold?: boolean },
): Promise<Model[]>| Name | Type | Default | Description |
|---|---|---|---|
schemaName | string | none | Only models in this schema. Also accepts the schema's ID. |
options.waitForReady | boolean | false | Wait for each model to load. Models that fail to load or time out are left out of the result. |
options.readyTimeout | number | 15000 | How long to wait for each model, in milliseconds. |
options.hold | boolean | false | Take a cache hold on each model returned, released with unload(). |
const models = await Model.all('blog')
console.log(models.map((model) => model.name)) // ['Post', 'Author']Without schemaName, the result includes the SDK's internal models (Seed, Version, Metadata, Image, File, Html) along with yours.
Model.create (new models)
Defines a new model in a schema. It never returns an existing model: if the schema already has a model with this name, the new one gets a numbered name such as Post 1, then Post 2. To get a model from your schema file, use Model.find.
static create(
modelName: string,
schemaNameOrSchema: string | Schema,
options?: {
properties?: { [propertyName: string]: PropertyDefinition }
modelFileId?: string
registerWithSchema?: boolean
waitForReady?: true
readyTimeout?: number
hold?: boolean
},
): Promise<Model>The real options type is wider: properties values are typed any, there's a deprecated id alias for modelFileId, and passing { waitForReady: false } returns a Model instead of a Promise.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
modelName | string | required | The model name. Throws Model name is required if empty. |
schemaNameOrSchema | string | Schema | required | The schema to add the model to, by name or instance. |
options.properties | object | {} | Property definitions keyed by name, in the code shape: { dataType: 'Text' }, { dataType: 'Relation', ref: 'Author' }. See ModelProperty. |
options.modelFileId | string | generated | Use this ID instead of generating one. If a model with this ID is already loaded, you get that instance back and no new model is made. |
options.registerWithSchema | boolean | true when you pass a Schema | Register the model with the schema instance. |
options.waitForReady | boolean | true | Wait until the model is written and loaded. |
options.readyTimeout | number | 15000 | How long to wait, in milliseconds. |
options.hold | boolean | false | Take a cache hold, released with unload(). A call that rejects gives its hold back. |
Example
import { Schema, Model } from '@seedprotocol/sdk'
const blog = await Schema.create('blog')
const Tag = await Model.create('Tag', blog, {
properties: {
label: { dataType: 'Text', required: true },
color: { dataType: 'Text' },
},
})
const news = await Tag.create({ label: 'news' })The model and its properties are written to the local database as a draft of the schema. They aren't added to your seed.schema.json. A model created in code but missing from config.schema stays in the local database across restarts, with the validation rules of its properties; see Validation.
When it waits for ready (the default), Model.create resolves only once the properties you passed exist, so model.properties is filled straight after the await. It rejects if they can't be created, or aren't created within readyTimeout.
Model.create refuses a name that publishes as a reserved Solidity word before it makes a model: the model's name, or a property name in options.properties. Model.create('Event', blog) rejects with Model "Event" publishes as "event", …, and a property view with Property "view" publishes as "view", …. With waitForReady: false the call throws instead. Nothing stays in schema.models or Model.getByName.
A call with modelFileId skips this check. Model.create('Fallback', blog, { modelFileId }) resolves, the model stays in schema.models and Model.getByName, and a later saveNewVersion() rejects with Schema blog: Fallback: Model "Fallback" publishes as "fallback", ….
Model.getByName and AmbiguousModelError
Model.getByName is a synchronous lookup in the loaded models, with no waiting.
static getByName(
modelName: string,
schemaName?: string,
options?: { hold?: boolean },
): Model | undefinedIt takes no hold unless options.hold is true. With schemaName, it returns that schema's model or undefined. Without it, it returns the only loaded model with that name, or throws AmbiguousModelError when models in several schemas share the name. Model.find, Item.create and other name-only lookups throw the same error.
AmbiguousModelError extends Error and adds:
| Field | Type | Description |
|---|---|---|
name | 'AmbiguousModelError' | |
modelName | string | The name you looked up. |
schemaNames | string[] | The schemas that define it, sorted. |
try {
Model.getByName('Post')
} catch (error) {
if (error instanceof AmbiguousModelError) {
console.log(error.schemaNames) // ['blog', 'shop']
}
}The fix is always to pass the schema name: Model.find({ modelName: 'Post', schemaName: 'blog' }).
Other static methods
| Method | Returns | Description |
|---|---|---|
Model.getById(modelFileId, { hold? }) | Model | undefined | Synchronous lookup of a loaded model by ID. |
Model.createById(modelFileId, { hold? }) | Promise<Model | undefined> | Loads a model by ID from the local database if it isn't loaded. |
Model.createBySchemaId(schemaNameOrId, { hold? }) | Promise<Model[]> | Loads every model of a schema from the database. Doesn't wait for them to be ready. |
Model.resolve(modelName, { modelFileId?, schemaName? }) | Model | undefined | Synchronous lookup by the most specific scope given: ID, then schema and name, then name alone. Takes no hold. |
Model.retain(model) | boolean | Takes one more hold on a cached, running model, released with unload(). false, with no hold taken, when the model was stopped. |
hold defaults to false on each of them.
The class also exposes cache and bookkeeping helpers used by the SDK and @seedprotocol/react (getByNameAsync, resolveAsync, findByModelType, getCachedInstancesForSchema, cachedModelNames, findUniqueModelName, evictForSchema, refreshPropertiesForDbId). You don't need them in app code.
Instance methods
| Method | Returns | Description |
|---|---|---|
create(values) | Promise<Item> | Creates an item. See model.create. |
validate() | Promise<{ isValid, errors }> | Validates the model definition. Resolves within 15 seconds. |
reload() | Promise<void> | Reloads the model from the database. Rejects with Failed to reload model from database if the load fails, and with EntityUnloadedError if the model was destroyed or evicted. |
refreshProperties() | Promise<void> | Re-reads the model's property list from the database. |
unload() | void | Gives back one hold taken with hold: true or Model.retain; does nothing when there's none to give back. Releasing the last hold doesn't stop the model, and nothing in the database changes. |
destroy() | Promise<void> | Deletes the model and its property definitions from the local database, and stops the model and its cached properties, whoever holds them. Items of the model aren't deleted; their stored values are kept but unlinked from the deleted properties. Rejects if the delete fails. |
getService() | actor | The underlying xstate actor. |
A model stays cached and running until it, or its schema, is destroyed or evicted, whether or not anything holds it; client.stop() evicts every cached model. A hold only counts your use of the instance. Once a model is destroyed or evicted, create(), reload() and assigning modelName throw EntityUnloadedError, whose message names the model and says to look it up again with Model.find. EntityUnloadedError is exported from the main entry.
Editing a model
Assigning model.modelName = 'Article' renames the model. The SDK updates its lookup tables at once and renames the model in the local database in the background. A failure isn't thrown: it's logged with console.warn and kept on model.lastSaveError. The schema is marked as edited.
To add or change properties, use ModelProperty.
Renaming a published model changes the EAS schema its items are attested under to the new name's EAS name (bytes32 post becomes bytes32 article; see What's published where), so other apps stop finding them. Treat published model names as fixed. See Schemas, Models, Items, Properties.
Conflicts
Model saves don't detect conflicts. The model's conflict check always passes, because the models table has no updatedAt column to compare. The ConflictError you may see comes from the schema: see Saving and conflicts.
Related
- Creating items: create items and set relations
- Modeling relations
- Schema, ModelProperty and Item
- React model hooks:
useModel,useModels,useCreateModel
Schema
The Schema class and the schema file format: load schemas by name, list them, inspect their models, and the field reference for the schema file.
ModelProperty
Every property type and option you can declare on a model, how each type is stored, published and read back, and the ModelProperty class for working with property definitions in code.