Skip to content

Latest commit

 

History

History
538 lines (420 loc) · 11.4 KB

File metadata and controls

538 lines (420 loc) · 11.4 KB

Stack API Reference

The Stack API is the client-side interface to your deployed Hyperstack stream. It provides type-safe access to views, transactions, and helpers.


Overview

import { defineStack, useHyperstack, createListView, createStateView } from 'hyperstack-react';

// 1. Define your stack
const MyStack = defineStack({
  name: 'my-app',
  views: { /* view definitions */ },
  transactions: { /* optional */ },
  helpers: { /* optional */ },
});

// 2. Use in components
function Component() {
  const stack = useHyperstack(MyStack);
  const { data } = stack.views.tokens.list.use();
}

defineStack

Creates a typed stack definition.

function defineStack<TViews, TTxs, THelpers>(definition: {
  name: string;
  views: TViews;
  transactions?: TTxs;
  helpers?: THelpers;
}): StackDefinition

Parameters

Parameter Type Required Description
name string Yes Unique identifier for the stack
views object Yes View definitions grouped by entity
transactions object No Transaction builders with refresh logic
helpers object No Utility functions

Example

const TokenStack = defineStack({
  name: 'token-tracker',
  
  views: {
    tokens: {
      list: createListView<Token>('Token/list'),
      state: createStateView<Token>('Token/state'),
    },
    trades: {
      recent: createListView<Trade>('Trade/list', {
        transform: (raw) => ({ ...raw, timestamp: new Date(raw.timestamp) })
      }),
    },
  },
  
  transactions: {
    swap: {
      build: (params: { mint: string; amount: number }) => ({
        instruction: 'swap',
        accounts: { mint: params.mint },
        data: { amount: params.amount },
      }),
      refresh: [{ view: 'tokens/state', key: (p) => p.mint }],
    },
  },
  
  helpers: {
    formatPrice: (lamports: number) => (lamports / 1e9).toFixed(6) + ' SOL',
    calculateMarketCap: (token: Token) => token.supply * token.price,
  },
});

useHyperstack

Main hook that provides access to a stack's features.

function useHyperstack<TStack>(stack: TStack): StackClient<TStack>

Return Value

interface StackClient<TStack> {
  views: ViewInterface;      // Real-time data subscriptions
  tx: TransactionMethods;    // Transaction submission
  helpers: TStack['helpers']; // Utility functions
  store: HyperstackStore;    // Direct store access
  runtime: HyperstackRuntime; // Runtime instance
}

Example

function TokenDashboard() {
  const stack = useHyperstack(TokenStack);
  
  // Access views
  const { data: tokens } = stack.views.tokens.list.use();
  
  // Access helpers
  const formatted = stack.helpers.formatPrice(1000000000);
  
  // Access transactions
  const { submit } = stack.tx.useMutation();
  
  return <div>{/* ... */}</div>;
}

View Factory Functions

createListView

Creates a view that returns entities as an array.

function createListView<T>(
  viewPath: string,
  options?: ViewOptions<T>
): ListViewDefinition<T>

Usage:

// Basic
const tokenList = createListView<Token>('Token/list');

// With transform
const tokenList = createListView<Token>('Token/list', {
  transform: (raw) => ({
    ...raw,
    priceFormatted: formatPrice(raw.price),
  })
});

In component:

const { data, isLoading, error, refresh } = stack.views.tokens.list.use({
  limit: 50,
  where: { volume: { gte: 1000 } },
});
// data: Token[] | undefined

createStateView

Creates a view that returns a single entity by key.

function createStateView<T>(
  viewPath: string,
  options?: ViewOptions<T>
): StateViewDefinition<T>

Usage:

const tokenState = createStateView<Token>('Token/state');

In component:

const { data, isLoading, error } = stack.views.tokens.state.use({ 
  key: mintAddress 
});
// data: Token | undefined

View Options

interface ViewOptions<T> {
  transform?: (data: any) => T;
}
Option Description
transform Transform raw data before returning

Transform Examples

// Convert bigint strings to BigInt
createListView<Token>('Token/list', {
  transform: (raw) => ({
    ...raw,
    supply: BigInt(raw.supply),
  })
});

// Flatten nested data
createStateView<Token>('Token/state', {
  transform: (raw) => ({
    mint: raw.id.mint,
    name: raw.info.name,
    volume: raw.trading.total_volume,
  })
});

View Hook Return Values

ListViewHookResult

interface ListViewHookResult<T> {
  data: T[] | undefined;     // Array of entities
  isLoading: boolean;        // True while loading
  error?: Error;             // Error if failed
  refresh: () => void;       // Force refresh
}

StateViewHookResult

interface StateViewHookResult<T> {
  data: T | undefined;       // Single entity
  isLoading: boolean;
  error?: Error;
  refresh: () => void;
}

List Query Parameters

interface ListParams {
  key?: string;                    // Filter by partition key
  where?: WhereClause;             // Field filters
  limit?: number;                  // Max results
  filters?: Record<string, string>; // Additional filters
}

interface WhereClause {
  [field: string]: {
    eq?: any;    // Equals
    gt?: any;    // Greater than
    gte?: any;   // Greater than or equal
    lt?: any;    // Less than
    lte?: any;   // Less than or equal
  };
}

Query Examples

// Limit results
stack.views.tokens.list.use({ limit: 10 })

// Filter by field
stack.views.tokens.list.use({
  where: { volume: { gte: 10000 } }
})

// Multiple conditions
stack.views.tokens.list.use({
  where: {
    volume: { gte: 10000 },
    created_at: { gt: Date.now() - 86400000 }
  },
  limit: 20
})

View Path Format

View paths follow the pattern: {EntityName}/{mode}

Mode Returns Example
list T[] Token/list
state T Token/state

The entity name must match the #[entity(name = "EntityName")] in your Rust definition.


Transaction Definitions

interface TransactionDefinition<TParams> {
  build: (params: TParams) => TransactionInstruction;
  refresh?: RefreshSpec[];
}

interface RefreshSpec {
  view: string;
  key?: string | ((params: any) => string);
}

Example

const MyStack = defineStack({
  name: 'my-app',
  views: { /* ... */ },
  transactions: {
    createToken: {
      build: (params: { name: string; symbol: string }) => ({
        instruction: 'createToken',
        data: { name: params.name, symbol: params.symbol },
      }),
      // Refresh token list after transaction
      refresh: [{ view: 'tokens/list' }],
    },
    
    updateToken: {
      build: (params: { mint: string; newName: string }) => ({
        instruction: 'updateToken',
        accounts: { mint: params.mint },
        data: { name: params.newName },
      }),
      // Refresh specific token state
      refresh: [{ view: 'tokens/state', key: (p) => p.mint }],
    },
  },
});

useMutation Hook

Submit transactions with status tracking.

interface UseMutationReturn {
  submit: (instruction: any) => Promise<string>;
  status: 'idle' | 'pending' | 'success' | 'error';
  error?: string;
  signature?: string;
  reset: () => void;
}

Example

function CreateTokenButton() {
  const stack = useHyperstack(MyStack);
  const { submit, status, error, signature } = stack.tx.useMutation();
  
  const handleCreate = async () => {
    const instruction = stack.transactions.createToken.build({
      name: 'My Token',
      symbol: 'MTK',
    });
    
    try {
      const sig = await submit(instruction);
      console.log('Transaction:', sig);
    } catch (e) {
      console.error('Failed:', e);
    }
  };
  
  return (
    <div>
      <button onClick={handleCreate} disabled={status === 'pending'}>
        {status === 'pending' ? 'Creating...' : 'Create Token'}
      </button>
      {status === 'error' && <p>Error: {error}</p>}
      {status === 'success' && <p>Success: {signature}</p>}
    </div>
  );
}

Helpers

Helpers are utility functions bundled with your stack.

const TokenStack = defineStack({
  name: 'tokens',
  views: { /* ... */ },
  helpers: {
    formatPrice: (lamports: number) => `${(lamports / 1e9).toFixed(6)} SOL`,
    formatSupply: (supply: bigint) => supply.toLocaleString(),
    calculatePrice: (token: Token) => 
      Number(token.sol_reserves) / Number(token.token_reserves),
  },
});

Usage:

function TokenCard({ token }: { token: Token }) {
  const stack = useHyperstack(TokenStack);
  
  return (
    <div>
      <p>Price: {stack.helpers.formatPrice(token.price)}</p>
      <p>Supply: {stack.helpers.formatSupply(token.supply)}</p>
    </div>
  );
}

Connection State

Monitor WebSocket connection status via useHyperstack:

import { useHyperstack } from 'hyperstack-react';
import { MY_STACK } from './stack';

function ConnectionIndicator() {
  const { connectionState, isConnected } = useHyperstack(MY_STACK);
  // connectionState: 'disconnected' | 'connecting' | 'connected' | 'error' | 'reconnecting'
  // isConnected: boolean (true when connectionState === 'connected')
  
  const colors = {
    disconnected: 'gray',
    connecting: 'yellow',
    connected: 'green',
    error: 'red',
    reconnecting: 'orange',
  };
  
  return <span style={{ color: colors[connectionState] }}>{isConnected ? 'Live' : connectionState}</span>;
}

TypeScript Integration

The Stack API is fully typed. TypeScript infers types from your stack definition:

// Types are inferred from createListView<Token>
const { data } = stack.views.tokens.list.use();
// data: Token[] | undefined

// Helper types are preserved
const formatted = stack.helpers.formatPrice(1000);
// formatted: string (inferred from helper return type)

// Transaction params are typed
stack.transactions.createToken.build({ 
  name: 'Test',  // TypeScript knows this is required
  // symbol: missing - TypeScript error!
});

Common Patterns

Conditional Subscription

function TokenDetail({ mint }: { mint: string | null }) {
  const stack = useHyperstack(TokenStack);
  
  // Only subscribe when mint is available
  const { data } = stack.views.tokens.state.use(
    mint ? { key: mint } : undefined
  );
  
  if (!mint) return <p>Select a token</p>;
  if (!data) return <p>Loading...</p>;
  return <p>{data.name}</p>;
}

Combining Multiple Views

function Dashboard() {
  const stack = useHyperstack(MyStack);
  
  const { data: tokens } = stack.views.tokens.list.use();
  const { data: stats } = stack.views.stats.state.use({ key: 'global' });
  
  const isLoading = !tokens || !stats;
  
  if (isLoading) return <Loading />;
  return <DashboardView tokens={tokens} stats={stats} />;
}

Refresh on Demand

function RefreshableList() {
  const stack = useHyperstack(TokenStack);
  const { data, refresh, isLoading } = stack.views.tokens.list.use();
  
  return (
    <div>
      <button onClick={refresh} disabled={isLoading}>
        {isLoading ? 'Refreshing...' : 'Refresh'}
      </button>
      {data?.map(token => <TokenRow key={token.mint} token={token} />)}
    </div>
  );
}