Direct event subscriptions are a good fit for service-side processing where event-type authorization is enough. Browser apps usually need something narrower: a live view filtered by the caller’s domain permissions.

Expose a feed for that case. The feed is part of the service contract, just like RPCs and events.

First, add feed schemas to schemas.ts:

export const OrdersLiveRequest = Type.Object({
  customerId: Type.Optional(Type.String()),
});

export const OrdersLiveEvent = Type.Union([
  Type.Object({
    name: Type.Literal("Orders.Shipped"),
    event: OrderShippedEvent,
  }),
]);

Then declare the feed in contracts/orders_service.ts:

import * as schemas from "../schemas.ts";

// ...

// In the first defineServiceContract argument:
{ schemas }

// In the returned contract body:
  feeds: {
    "Orders.Live": {
      version: "v1",
      input: ref.schema("OrdersLiveRequest"),
      event: ref.schema("OrdersLiveEvent"),
      capabilities: { subscribe: ["orders.read"] },
    },
  },

Now handle the feed in main.ts. Feed handlers should use service-owned state or an application-local stream; they must not register Trellis event listeners inside the handler. This example watches the service’s local order projection and only emits frames the caller is allowed to see:

await app.handle.feed.orders.live(async ({ input, caller, emit, signal, deps }) => {
  const visibleCustomerIds = await listCustomersVisibleTo(caller);
  const shippedOrders = deps.orderProjection.watchShippedOrders({ signal });

  for await (const event of shippedOrders) {
    if (input.customerId && event.customerId !== input.customerId) {
      continue;
    }
    if (!visibleCustomerIds.has(event.customerId)) {
      continue;
    }

    await emit({ name: "Orders.Shipped", event }).orThrow();
  }
});

The feed handler receives an authenticated caller. Your service is still responsible for checking the requested filter and every emitted frame. If the feed needs data from contract events, consume those events through a startup service.event...listen(...) or app.event...listen(...) listener that updates service-local state, then have feed handlers watch that state through deps.

Upgrade the contract and restart.