4. Make it live
We keep saying the catalog “stays live.” This chapter makes that concrete — and shows that you already built it, without writing a line of real-time plumbing.
Why the screen already moves
Section titled “Why the screen already moves”Go back to every query we’ve written: each observes storage rather than returning a one-shot list. The C# ISubject<…>, JVM Flow<List<…>>, and TypeScript ObservableSource<…> all expose streams. Arc serves each as an observable query; on the React side, the generated .use() hook subscribes to it. So the data flow isn’t “fetch once and render” — it’s a standing subscription:
When a command writes and the configured observation source notifies, the observable query publishes updated data to subscribed React components, which re-render. Open the app in two browser tabs, add a book in one, and watch it appear in the other — you wrote none of that plumbing. It falls out of returning Observe() and calling .use().
Verify the scope of observation
Section titled “Verify the scope of observation”Continue from chapter 3. Open two browser tabs connected to the same backend process. Subscribe to the same author’s catalog in both; add a book in one and verify the other updates without reload. This tests the tutorial’s live path, including SQLite. It does not prove cross-process observation.
External writes are a separate test: MongoDB change streams can report them when configured; SQLite cannot. For EF’s other providers, configure their database notification mechanism before testing a write from a second process. No notification source means an observable endpoint can stay connected without fresh data.
What makes a query live
Section titled “What makes a query live”The following are alternative MongoDB method fragments in Author, using the tutorial imports. Replace the method rather than adding a second method with the same signature.
Nothing special — just the return type. A one-shot query returns the data:
public static IEnumerable<Author> AllAuthors(IMongoCollection<Author> authors) => authors.Find(_ => true).ToList();public static List<Author> allAuthors(@FromServices AuthorRepository authors) { return authors.findAll();}@JvmStaticfun allAuthors(@FromServices authors: AuthorRepository): List<Author> = authors.findAll()@query(service(AuthorRepository))static allAuthors(authors: AuthorRepository): Promise<Author[]> { return authors.all();}Make it return a stream instead — an ISubject<…> on C#, a Flow<List<…>> on the JVM, or an ObservableSource<Author[]> on TypeScript — and the method becomes a live subscription. The proxy and the React .use() adapt automatically, and the frontend code is identical for every backend:
public static ISubject<IEnumerable<Author>> AllAuthors(IMongoCollection<Author> authors) => authors.Observe();public static Flow.Publisher<List<Author>> allAuthors(@FromServices MongoObservableQuery queries) { return queries.observePublisher(Author.class);}@JvmStaticfun allAuthors(@FromServices queries: MongoObservableQuery): Flow<List<Author>> = queries.observe<Author>()@query({ observable: true }, service(AuthorRepository))static allAuthors(authors: AuthorRepository): ObservableSource<Author[]> { return authors.observeAll();}That’s the whole switch between “load once” and “stay live.” You choose per query: a rarely-changing lookup can be a plain list; a screen the librarian watches should observe.
What you built
Section titled “What you built”- A clear picture of why your screens update themselves — observable queries are standing subscriptions, database to browser.
- The method-level difference between a one-shot query and a live one — the return type and observation call, with a configured notification source underneath.
The catalog is live. There’s just one thing left before it’s a real back office: right now anyone can register authors and add books. Let’s decide who’s allowed. Lock it down →