Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/hypertekorg/hyperstack/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Views provide strongly-typed access to entity data with real-time synchronization. HyperStack supports two view modes:
  • State views - Single entities keyed by ID
  • List views - Collections of entities

Accessing Views

Views are accessed through the client.views property, organized by entity and view name.
const stack = {
  name: 'my-stack',
  url: 'wss://api.example.com',
  views: {
    users: {
      state: { mode: 'state' as const, view: 'users.state' },
      list: { mode: 'list' as const, view: 'users.list' },
    },
    posts: {
      byUser: { mode: 'list' as const, view: 'posts.by_user' },
    },
  },
};

const client = await HyperStack.connect(stack);

// Access views
const userState = client.views.users.state;
const userList = client.views.users.list;
const postsByUser = client.views.posts.byUser;

State Views

State views represent single entities identified by a key.

TypedStateView Interface

interface TypedStateView<T> {
  use<TSchema = T>(key: string, options?: WatchOptions<TSchema>): AsyncIterable<TSchema>;
  watch(key: string, options?: WatchOptions): AsyncIterable<Update<T>>;
  watchRich(key: string, options?: WatchOptions): AsyncIterable<RichUpdate<T>>;
  get(key: string): Promise<T | null>;
  getSync(key: string): T | null | undefined;
}

get()

Fetch a single entity by key.
const user = await client.views.users.state.get('user-123');
if (user) {
  console.log(user.name);
}
Returns: Promise<T | null> - Entity or null if not found

getSync()

Synchronously retrieve an entity from local storage.
const user = client.views.users.state.getSync('user-123');
if (user) {
  console.log(user.name);
}
Returns: T | null | undefined
  • T - Entity found in cache
  • null - Entity explicitly does not exist
  • undefined - No cached data available

use()

Subscribe to real-time updates for an entity. Returns an async iterable that yields the current entity whenever it changes.
for await (const user of client.views.users.state.use('user-123')) {
  console.log('User updated:', user);
}
key
string
required
Entity key to subscribe to
options
WatchOptions
Optional subscription options
React Example:
import { useEffect, useState } from 'react';

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const subscription = (async () => {
      for await (const userData of client.views.users.state.use(userId)) {
        setUser(userData);
      }
    })();

    return () => subscription.return?.();
  }, [userId]);

  if (!user) return <div>Loading...</div>;
  return <div>{user.name}</div>;
}

watch()

Subscribe to update events for an entity. Yields Update<T> objects describing changes.
for await (const update of client.views.users.state.watch('user-123')) {
  if (update.type === 'upsert') {
    console.log('User created/replaced:', update.data);
  } else if (update.type === 'patch') {
    console.log('User patched:', update.data);
  } else if (update.type === 'delete') {
    console.log('User deleted');
  }
}
Update Types:
type Update<T> =
  | { type: 'upsert'; key: string; data: T }
  | { type: 'patch'; key: string; data: Partial<T> }
  | { type: 'delete'; key: string };

watchRich()

Subscribe to rich update events with before/after snapshots.
for await (const update of client.views.users.state.watchRich('user-123')) {
  if (update.type === 'created') {
    console.log('User created:', update.data);
  } else if (update.type === 'updated') {
    console.log('Before:', update.before);
    console.log('After:', update.after);
  } else if (update.type === 'deleted') {
    console.log('Last known state:', update.lastKnown);
  }
}
RichUpdate Types:
type RichUpdate<T> =
  | { type: 'created'; key: string; data: T }
  | { type: 'updated'; key: string; before: T; after: T; patch?: unknown }
  | { type: 'deleted'; key: string; lastKnown?: T };

List Views

List views represent collections of entities.

TypedListView Interface

interface TypedListView<T> {
  use<TSchema = T>(options?: WatchOptions<TSchema>): AsyncIterable<TSchema>;
  watch(options?: WatchOptions): AsyncIterable<Update<T>>;
  watchRich(options?: WatchOptions): AsyncIterable<RichUpdate<T>>;
  get(): Promise<T[]>;
  getSync(): T[] | undefined;
}

get()

Fetch all entities in the list.
const users = await client.views.users.list.get();
console.log(`Found ${users.length} users`);
Returns: Promise<T[]> - Array of entities

getSync()

Synchronously retrieve all entities from local storage.
const users = client.views.users.list.getSync();
if (users) {
  console.log(`Cached ${users.length} users`);
}
Returns: T[] | undefined
  • T[] - Cached entities
  • undefined - No cached data available

use()

Subscribe to real-time updates for all entities in the list.
for await (const entity of client.views.users.list.use()) {
  console.log('Entity in list:', entity);
}
The use() method yields individual entities as they are added, updated, or when the subscription initializes.

watch()

Subscribe to update events for entities in the list.
for await (const update of client.views.users.list.watch()) {
  console.log(`${update.type} on ${update.key}`);
}

watchRich()

Subscribe to rich update events for entities in the list.
for await (const update of client.views.users.list.watchRich()) {
  if (update.type === 'updated') {
    console.log('Changed from', update.before, 'to', update.after);
  }
}

Watch Options

All subscription methods (use, watch, watchRich) accept optional configuration.

WatchOptions

take
number
Limit the number of entities to retrieve
skip
number
Skip the first N entities
filters
Record<string, string>
Filter entities by field values
schema
Schema<TSchema>
Runtime validation schema (e.g., Zod)

Filtering

// Get only active users
for await (const user of client.views.users.list.use({
  filters: { status: 'active' },
})) {
  console.log('Active user:', user);
}

Pagination

// Get first 10 users
const firstPage = await client.views.users.list.use({
  take: 10,
  skip: 0,
});

// Get next 10 users
const secondPage = await client.views.users.list.use({
  take: 10,
  skip: 10,
});

Schema Validation

Validate entities at runtime using Zod or similar libraries.
import { z } from 'zod';

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
});

for await (const user of client.views.users.state.use('user-123', {
  schema: UserSchema,
})) {
  // user is validated against UserSchema
  console.log(user.email); // Type-safe
}

View Helper Functions

Low-level functions for creating typed views programmatically.

createTypedStateView()

function createTypedStateView<T>(
  viewDef: ViewDef<T, 'state'>,
  storage: StorageAdapter,
  subscriptionRegistry: SubscriptionRegistry
): TypedStateView<T>

createTypedListView()

function createTypedListView<T>(
  viewDef: ViewDef<T, 'list'>,
  storage: StorageAdapter,
  subscriptionRegistry: SubscriptionRegistry
): TypedListView<T>

createTypedViews()

function createTypedViews<TStack extends StackDefinition>(
  stack: TStack,
  storage: StorageAdapter,
  subscriptionRegistry: SubscriptionRegistry
): TypedViews<TStack['views']>
These functions are used internally by HyperStack.connect(). You typically don’t need to call them directly.

Type Definitions

import type {
  ViewDef,
  ViewGroup,
  TypedViews,
  TypedViewGroup,
  TypedStateView,
  TypedListView,
  WatchOptions,
  Update,
  RichUpdate,
} from '@hyperstack/core';

Best Practices

Use getSync() for Optimistic UI

function UserAvatar({ userId }: { userId: string }) {
  // Show cached data immediately
  const cached = client.views.users.state.getSync(userId);
  
  const [user, setUser] = useState(cached);

  useEffect(() => {
    // Subscribe to updates
    (async () => {
      for await (const u of client.views.users.state.use(userId)) {
        setUser(u);
      }
    })();
  }, [userId]);

  return <img src={user?.avatar} alt={user?.name} />;
}

Cleanup Subscriptions

Always cleanup async iterables when components unmount or dependencies change.
useEffect(() => {
  const controller = new AbortController();

  (async () => {
    try {
      for await (const user of client.views.users.state.use(userId)) {
        if (controller.signal.aborted) break;
        setUser(user);
      }
    } catch (err) {
      if (!controller.signal.aborted) throw err;
    }
  })();

  return () => controller.abort();
}, [userId]);

Handle Loading States

for await (const user of client.views.users.state.use('user-123')) {
  if (user === null) {
    console.log('User does not exist');
  } else {
    console.log('User loaded:', user);
  }
}

Next Steps

Subscriptions

Deep dive into subscription management

Storage Adapters

Integrate with state management libraries

Build docs developers (and LLMs) love