JOLT DATA SDK GUIDE · 06
Keep a remote Collection current
A Data Subscription keeps a verified local view of one identity's public Collection. Application code can show retained data immediately, while Jolt refreshes it within bounded network and storage limits.
Start with a public Collection
Only a Resource using Read.AnyIdentity has .for(identity), because only
that Resource can be read through another identity's public view.
import {
App,
Collection,
Field,
Read,
Schema,
} from "jolt-sdk/data";
@Schema({ version: 1 })
export class Post {
@Field.string()
text!: string;
@Field.dateTime()
postedAt!: Date;
}
export const Posts = Collection.create(Post, {
access: {
read: Read.AnyIdentity,
create: true,
},
});
export const Feed = App.create({
id: "feed.example",
name: "Feed",
namespace: "feed",
data: { posts: Posts },
});Create the Subscription
Pass the remote Collection view to Subscription.create():
import { Subscription } from "jolt-sdk/data";
import { Feed } from "./subscriptions";
type FeedApplication = ReturnType<typeof Feed.test>;
export async function openAuthorPosts(
feed: FeedApplication,
identity: string,
) {
const subscription = await Subscription.create(feed.posts.for(identity));
const posts = await subscription.get();
return { subscription, posts };
}subscription.get() returns the current retained verified Items. It does not
make the application enumerate the network. Calling Subscription.create()
again for the same authorized target safely reuses the daemon's bounded
subscription work.
App.connect() derives the required subscription access for Jolt Console
approval. If the node cannot admit more retained views, creation throws
SubscriptionCapacityError.
Listen without polling
A Change Stream is an async sequence of changes to the local retained view. A
new stream starts with a full ChangeType.Snapshot, then reports changes and
freshness transitions.
import {
ChangeType,
type DataSubscription,
type PresentItem,
type SubscriptionStateValue,
} from "jolt-sdk/data";
import type { Post } from "./subscriptions";
type PostItem = PresentItem<Post>;
type ViewListener = (posts: readonly PostItem[]) => void;
type StateListener = (state: SubscriptionStateValue) => void;
export function watchPosts(
subscription: DataSubscription<Post>,
showPosts: ViewListener,
showState: StateListener,
) {
const stream = subscription.changes();
const done = (async () => {
try {
for await (const change of stream) {
switch (change.type) {
case ChangeType.Snapshot:
showPosts(change.items);
showState(change.state);
break;
case ChangeType.Changed:
case ChangeType.ResyncRequired:
showPosts(await subscription.get());
break;
case ChangeType.State:
showState(change.state);
break;
case ChangeType.Cancelled:
case ChangeType.Revoked:
return;
}
}
} finally {
await stream.cancel();
}
})();
return Object.freeze({
done,
async close() {
await stream.cancel();
await done;
},
});
}The example rereads the complete local view after Changed or
ResyncRequired. That is the simplest correct choice for one small
subscription. A larger feed can instead apply change.items and
change.removed to its own map, as Chirp does.
There is no timer in this code. Jolt wakes the local stream when verified data or its freshness changes.
Understand freshness
subscription.state and State on an Item answer different questions. Item
state says whether one logical record is Present, Deleted, Missing, or
Unavailable. SubscriptionState says how current the retained Collection view
is:
Loading: the application has not received a verified view yet.Updating: a retained view is available while Jolt refreshes it.Ready: the latest refresh succeeded.Stale: retained data remains usable, but refresh failed; inspectreason.Unavailable: Jolt has no usable view at the moment.CancelledandRevoked: the Subscription is terminal.
lastVerifiedAt records the last successful verification time when one is
available. A stale view is intentionally different from an empty Collection.
Close the right thing
stream.cancel() stops one listener and leaves the retained Data Subscription
available. subscription.remove() removes the Subscription when the
application no longer wants Jolt to maintain that view.
Always close a Change Stream when its screen or controller stops. Treat
ChangeType.Cancelled and ChangeType.Revoked as terminal instead of starting
a hidden retry loop.
Continue learning
- See this pattern inside the beginner Chirp app.
- Return to Data SDK fundamentals.
- Use the normal local lifecycle in Change an Item.