JOLT DATA SDK GUIDE · 04
Change an Item
A Resource decides which changes an application may make. TypeScript then exposes only those methods on its Items. Application code does not manage paths, content identifiers, or concurrency bookkeeping.
Declare the allowed changes
This Task Collection allows its owner to perform the complete Item lifecycle:
import {
App,
Collection,
Field,
Read,
Schema,
} from "jolt-sdk/data";
@Schema({ version: 1 })
export class Task {
@Field.string()
title!: string;
@Field.boolean()
done!: boolean;
}
export const Tasks = Collection.create(Task, {
access: {
read: Read.OwnIdentity,
create: true,
update: true,
delete: true,
restore: true,
},
});
export const TaskList = App.create({
id: "task-list.example",
name: "Task List",
namespace: "tasks",
data: { tasks: Tasks },
});Remove an operation from access when the application does not need it. For
example, without delete: true, a Present Item has no delete() method.
Keep the new Item
Items are immutable snapshots. A successful mutation returns a new Item at the
same stable ref; it does not change the older snapshot in place.
import { Ref, State } from "jolt-sdk/data";
import { Task, TaskList } from "./mutations";
type TaskCollection = ReturnType<typeof TaskList.test>["tasks"];
export async function reviseTask(tasks: TaskCollection) {
const created = await tasks.create({ title: "Read the guide", done: false });
const updated = await created.update({ done: true });
const replaced = await updated.replace({ title: "Build an app", done: true });
const deleted = await replaced.delete();
const restored = await deleted.restore({ title: "Build an app", done: false });
return { created, updated, replaced, deleted, restored };
}
export async function describeTask(tasks: TaskCollection, ref: Ref<Task>) {
const item = await tasks.get(ref);
switch (item.state) {
case State.Present:
return item.value.title;
case State.Deleted:
return "This task was deleted";
case State.Missing:
return "Task not found";
case State.Unavailable:
return "Task is temporarily unavailable";
default:
throw new Error("Unknown task state");
}
}update() is a shallow patch. Omitted fields stay unchanged, while a supplied
array or nested value replaces that whole field. Use replace() when you mean
to supply the complete value.
delete() returns a Deleted Item. restore() also takes a complete value,
which must match the current Schema Class. This lets an application restore old
logical data into its current shape.
Read the Item state
The describeTask() example uses a normal switch over item.state:
State.Presenthas a schema-validvalue.State.Deletedis deliberately deleted and may be restorable.State.Missinghas no known record at that logical reference.State.Unavailablemeans Jolt cannot safely determine the current state.
The matching isPresent() and isDeleted() helpers are useful when an early
return reads more clearly than a switch. Both forms narrow the TypeScript type.
Catch expected failures by type
Keep error handling close to the user action. Catch the specific failures the screen can explain, and rethrow anything it does not understand:
import {
AccessRevokedError,
ConflictError,
ItemUnavailableError,
SchemaValidationError,
} from "jolt-sdk/data";
export function mutationMessage(error: unknown): string {
if (error instanceof ConflictError) {
return "This item changed. Read it again before retrying.";
}
if (error instanceof ItemUnavailableError) {
return "Jolt cannot safely change this item right now.";
}
if (error instanceof AccessRevokedError) {
return "Reconnect to Jolt and request approval again.";
}
if (error instanceof SchemaValidationError) {
return `Check the ${error.field} field.`;
}
throw error;
}ConflictError means the Item snapshot became stale before the mutation. Read
the current Item, show it to the user when needed, and retry only if the action
still makes sense. ItemUnavailableError prevents a change when current state
is unknown. AccessRevokedError asks the application to reconnect and request
approval again. SchemaValidationError identifies invalid application data.
Automatic conflicts are the default
Resources need no conflict configuration for normal application code. Jolt combines concurrent changes to different top-level fields, resolves concurrent changes to the same field deterministically, and lets deletion win over a concurrent update.
That automatic distributed behavior is separate from a ConflictError raised
when application code tries to mutate an already stale Item snapshot. Advanced
applications can override the automatic policies and expose Manual alternatives;
the beginner path does not need that machinery. When a real product does, use
the advanced Manual conflicts guide.
Continue learning
- Return to Data SDK fundamentals.
- Evolve stored values with Schema migrations.
- Build these actions into the beginner Chirp app.