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.

src/feed.tsts
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():

src/open-author-posts.tsts
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.

src/watch-posts.tsts
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:

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