npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bundt/prev

v0.1.2

Published

Agent-native dynamic UI framework. Server-composed, streaming React SSR with fragment-based micro-frontends.

Downloads

342

Readme

@bundt/prev

Agent-native dynamic UI framework. Server-composed, streaming React SSR with fragment-based micro-frontends.

Status: Pre-release (0.1.0). Core composition engine, streaming SSR, WebSocket interactions, session persistence, client runtime, and build tooling are implemented and tested (89 tests, 184 assertions across 13 test files). API surface may change before 1.0.

What is prev?

prev is a React SSR framework where the server composes the UI from registered building blocks called Fragments. Instead of static page routes, an AI agent (or any orchestrator) sends a structured composition request describing which fragments to show, how to lay them out, and how to wire data between them. The server assembles a Frame, streams it to the browser, and handles ongoing interactions via WebSocket.

Install

bun add @bundt/prev react react-dom zod

Core Concepts

| Concept | Description | |---------|-------------| | Fragment | A self-contained UI component with typed props, data requirements, interaction declarations, and layout hints | | Data Source | A server-side data fetcher with typed params and returns, optional TTL caching | | Frame | A composed workspace — a set of fragment instances with layout positions, data bindings, and interaction wiring | | Session | Persistent state across compositions — tracks frame history with back/forward navigation | | Binding | A wire connecting one fragment's interaction output to another fragment's prop or data param, with optional path transforms | | Mutation | Runtime modifications to a live frame — add, remove, replace, resize fragments, or bind/unbind interactions |

Quick Start

import { createPrevServer, defineFragment, defineDataSource } from '@bundt/prev';
import { z } from 'zod';

// 1. Define data sources
const usersSource = defineDataSource({
  id: 'users',
  name: 'Users API',
  params: z.object({ limit: z.number().optional() }),
  returns: z.array(z.object({ id: z.string(), name: z.string(), email: z.string() })),
  fetch: async (params) => {
    return Array.from({ length: params.limit ?? 10 }, (_, i) => ({
      id: `${i + 1}`,
      name: `User ${i + 1}`,
      email: `user${i + 1}@example.com`
    }));
  }
});

// 2. Define fragments
const userList = defineFragment({
  id: 'user-list',
  name: 'User List',
  tags: ['users'],
  props: z.object({ title: z.string().optional() }),
  data: { users: { source: 'users' } },
  interactions: {
    selectUser: { payload: z.object({ userId: z.string() }) }
  },
  render: ({ props, data }) => (
    <div>
      <h2>{props.title ?? 'Users'}</h2>
      <ul>
        {Array.isArray(data.users) && data.users.map((user: any) => (
          <li key={user.id} data-prev-interaction="selectUser"
              data-prev-payload={JSON.stringify({ userId: user.id })}>
            {user.name} — {user.email}
          </li>
        ))}
      </ul>
    </div>
  )
});

const userDetail = defineFragment({
  id: 'user-detail',
  name: 'User Detail',
  props: z.object({ selectedUserId: z.string().optional() }),
  data: {},
  interactions: {},
  render: ({ props }) => (
    <div>
      <h2>User Detail</h2>
      {props.selectedUserId
        ? <p>Selected user: {props.selectedUserId}</p>
        : <p style={{ opacity: 0.5 }}>Select a user from the list</p>}
    </div>
  )
});

// 3. Create and start the server
const server = createPrevServer({
  port: 3000,
  fragments: [userList, userDetail],
  dataSources: [usersSource],
  dbPath: './prev.db'
});

server.listen();

Compose a UI:

curl -X POST http://localhost:3000/prev/compose \
  -H "Content-Type: application/json" \
  -d '{
    "intent": "Show user management workspace",
    "fragments": [
      {
        "fragmentId": "user-list",
        "props": { "title": "Team Members" },
        "data": { "users": { "source": "users", "params": { "limit": 5 } } }
      },
      { "fragmentId": "user-detail" }
    ],
    "bindings": [{
      "id": "list-to-detail",
      "sourceFragmentInstanceId": "<from-compose-response>",
      "sourceInteraction": "selectUser",
      "targetFragmentInstanceId": "<from-compose-response>",
      "targetType": "prop",
      "targetKey": "selectedUserId",
      "transform": "userId"
    }],
    "layout": "split-horizontal"
  }'

API Reference

defineFragment(definition)

Creates a typed, frozen fragment definition.

defineFragment({
  id: string;                    // Unique identifier (required)
  name: string;                  // Human-readable name (required)
  description?: string;          // What this fragment does
  tags?: string[];               // For filtering/discovery
  props: ZodType;                // Zod schema for component props
  data: Record<string, {         // Data field declarations
    source: string;              //   Data source ID to bind
    params?: ZodType;            //   Optional params schema
  }>;
  interactions: Record<string, { // Interaction event declarations
    payload: ZodType;            //   Payload schema
  }>;
  layoutHints?: {                // Layout solver hints
    minWidth?: string;
    minHeight?: string;
    resizable?: boolean;
    preferredAspectRatio?: number;
  };
  render: (ctx: {               // SSR render function (required)
    props: TProps;
    data: Record<string, unknown>;
    emit: EmitFn;
  }) => ReactNode;
})

defineDataSource(definition)

Creates a typed, frozen data source definition.

defineDataSource({
  id: string;                      // Unique identifier (required)
  name: string;                    // Human-readable name (required)
  description?: string;
  tags?: string[];
  params: ZodType;                 // Zod schema for input params
  returns: ZodType;                // Zod schema for return type
  ttl?: number;                    // Cache TTL in milliseconds
  fetch: (params: TParams) => Promise<TReturns>;  // (required)
})

createPrevServer(config)

Creates and returns a server instance.

createPrevServer({
  port?: number;                   // Default: 3000
  hostname?: string;               // Default: 'localhost'
  dbPath?: string;                 // SQLite path. ':memory:' for ephemeral
  fragments: FragmentDefinition[];
  dataSources: DataSourceDefinition[];
})
// Returns: { listen(): void; close(): void }

Server Routes

| Route | Method | Description | |-------|--------|-------------| | / | GET | Empty workspace shell | | /prev/compose | POST | Accept composition request, return streaming HTML | | /prev/frame/:id | GET | Re-stream an existing frame (session restore) | | /prev/frame/:id/glue.js | GET | Per-frame client glue bundle | | /prev/client.js | GET | Client runtime bundle | | /prev/ws | WS | WebSocket for interactions + partial updates | | /prev/api/session?sessionId= | GET | Session state (debug) |

Composition Request

{
  fragments: Array<{
    fragmentId: string;               // Registered fragment ID
    props?: Record<string, unknown>;  // Initial props
    data?: Record<string, {           // Data source bindings
      source: string;
      params: Record<string, unknown>;
    }>;
    position?: { row, col, rowSpan, colSpan };  // Explicit grid position
    size?: { width, height };
  }>;
  bindings?: Array<{
    id: string;
    sourceFragmentInstanceId: string;
    sourceInteraction: string;
    targetFragmentInstanceId: string;
    targetType: 'prop' | 'dataParam';
    targetKey: string;
    transform?: string;               // Dot-path into interaction payload
  }>;
  layout?: LayoutType;
  intent?: string;                     // Human-readable workspace description
}

Layout Types

| Type | Description | Auto-selected when | |------|-------------|--------------------| | single | One fragment, full viewport | 1 fragment | | split-horizontal | Side by side, equal width | 2 fragments | | split-vertical | Stacked, equal height | — | | grid | Auto-grid (sqrt(n) columns) | — | | primary-detail | 2/3 + 1/3 split | 3 fragments | | dashboard | 3-column grid | 4+ fragments |

Frame Mutations

Mutate a live frame without full recomposition:

{
  type: 'add' | 'remove' | 'replace' | 'resize' | 'bind' | 'unbind';
  // Fields vary by type — see FrameMutation type
}

Fragment Interactions

Fragments declare interactions and emit them either declaratively (SSR-safe) or programmatically:

// Declarative: data attributes (works during SSR + client)
<button
  data-prev-interaction="selectItem"
  data-prev-payload={JSON.stringify({ itemId: '123' })}
>
  Select
</button>

// Programmatic: emit function (client-only, no-op during SSR)
render: ({ emit }) => (
  <button onClick={() => emit('selectItem', { itemId: '123' })}>
    Select
  </button>
)

The client runtime uses event delegation on [data-prev-interaction] elements — no per-element event listeners.

Data Flow

Composition Request
  → Fragment instances created with assigned IDs
  → Layout solver assigns CSS Grid positions
  → Data binder builds fetch plan (parallel where independent, sequential for deps)
  → Binding resolver validates interaction → prop/data wiring
  → Frame assembled and persisted to SQLite
  → React SSR streams HTML to browser via renderToReadableStream
  → Client runtime connects WebSocket

User Interaction
  → Event delegation captures click on [data-prev-interaction]
  → WebSocket sends interaction event to server
  → Server finds affected bindings
  → Re-fetches data for target fragments
  → Re-renders target fragments via renderToString
  → Sends partial HTML update over WebSocket
  → Client patches target fragment DOM

Session Persistence

Sessions and frames are persisted to SQLite via bun:sqlite:

  • Server restarts preserve all session state
  • Browser refreshes restore the current frame
  • Frame history supports back/forward navigation
  • Set dbPath to a file path for persistence, or ':memory:' for ephemeral sessions

Client Runtime

The @bundt/prev/client export provides the browser runtime:

  • WebSocket connection — Auto-connects to /prev/ws for real-time updates
  • Event delegation — Captures interactions on [data-prev-interaction] elements
  • DOM patching — Applies partial HTML updates from server re-renders
  • React hydration — Hydrates server-rendered HTML for client interactivity
  • CSS Grid layout — Client-side layout management

Build Tooling

The @bundt/prev/build export provides build-time utilities:

  • Composition bundler — Generates per-frame client glue bundles that wire up specific fragment instances

Architecture

packages/prev/
  src/
    index.ts                    Public API (defineFragment, defineDataSource, createPrevServer)
    types.ts                    All type definitions (50+ exported types)

    registry/                   Fragment + data source registries
      fragment-registry.ts      Register/lookup/list/filter fragments
      data-source-registry.ts   Register/lookup data sources

    composition/                Composition engine
      engine.ts                 Orchestrator (coordinate layout, data, bindings)
      layout-solver.ts          CSS Grid layout algorithms (6 layout types)
      data-binder.ts            Data fetch planning + parallel execution
      binding-resolver.ts       Inter-fragment wiring validation
      mutation.ts               Runtime frame mutations (add/remove/replace/resize/bind/unbind)

    server/                     HTTP + WebSocket server
      server.ts                 Bun.serve() with all routes
      ssr.tsx                   Streaming React SSR (renderToReadableStream)
      websocket.tsx             Interaction handling + partial re-renders
      session.ts                Session lifecycle management
      database.ts               SQLite schema + query helpers

    client/                     Browser runtime
      runtime.ts                WebSocket + event delegation
      hydration.ts              React hydration
      bindings.ts               Client-side binding evaluation
      layout.ts                 CSS Grid layout manager

    build/                      Build tooling
      composition-bundler.ts    Per-frame glue bundle generation

Roadmap

  • Phase 2: MCP tool integration — expose fragment/data source registries as MCP tools for AI agent discovery
  • Phase 3: Agent chat panel — embedded conversational UI for natural language composition
  • Phase 4: Third-party fragments — load fragments from remote manifests, sandboxed execution

License

MIT