Skip to content

Latest commit

 

History

49 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Yeti


demo-basic

Yeti License

Yeti - The Performance Platform for Agent-Driven Development. Schema-driven APIs, real-time streaming, and vector search. From prompt to production.

The starter demo for yeti. Persistent counter, custom Rust endpoint, React frontend -- in under 20 lines of backend code.

demo-basic shows the two fundamental building blocks of every yeti application: a schema-defined table that gives you a full REST API with zero code, and a custom Rust resource that lets you add arbitrary server-side logic. A React/Vite frontend ties them together into an interactive UI with syntax-highlighted source panels so you can see exactly what powers each feature.


Why demo-basic

Most "hello world" demos stop at printing a string. demo-basic goes further -- it demonstrates the core developer loop for yeti applications:

  • Schema to API in one file -- define a GraphQL type, get REST endpoints, SSE streaming, MQTT pub/sub, and MCP tools. No controllers, no routes, no boilerplate.
  • Custom logic in pure Rust -- the resource!() macro turns a struct with handler methods into a compiled native endpoint. Seven lines of code, sub-millisecond response times.
  • Persistent state out of the box -- the counter value survives server restarts. RocksDB storage is automatic from the schema. No database setup, no migrations, no ORM.
  • Frontend build integration -- Vite builds are triggered automatically by yeti on first load. Static files are served with SPA routing. Hot module replacement works in dev mode.
  • Public access control -- the @export(public: [read, update]) directive makes the counter table accessible without authentication, declared directly in the schema.

Quick Start

1. Install

cd ~/yeti/applications
git clone https://github.com/yetirocks/demo-basic.git

Restart yeti. The Rust resource compiles automatically on first load (~2 minutes) and is cached for subsequent starts (~10 seconds). The Vite frontend builds on first request.

2. Open the UI

https://localhost:9996/demo-basic/

The interactive UI shows a counter panel with increment/decrement buttons and a greeting panel with a "Call /greeting" button. Each panel displays the source code (GraphQL schema and Rust resource) that powers it.

3. Increment the counter

curl -s -X PUT https://localhost:9996/demo-basic/api/TableName/main-counter \
  -H "Content-Type: application/json" \
  -d '{"id": "main-counter", "count": 1}'

Response:

{
  "id": "main-counter",
  "count": 1
}

4. Read the counter

curl -s https://localhost:9996/demo-basic/api/TableName/main-counter

Response:

{
  "id": "main-counter",
  "count": 1
}

The value persists across server restarts -- it is stored in RocksDB, not in memory.

5. Call the custom greeting endpoint

curl -s https://localhost:9996/demo-basic/api/greeting

Response:

{
  "greeting": "Hello, World!"
}

This response is generated by resources/greeting.rs -- a compiled Rust plugin, not an interpreted script.

6. List all counter records

curl -s "https://localhost:9996/demo-basic/api/TableName?limit=10"

Response:

[
  {
    "id": "main-counter",
    "count": 1
  }
]

7. Stream counter changes in real-time

# SSE -- server-sent events (use Ctrl+C to stop)
curl -s --max-time 30 "https://localhost:9996/demo-basic/api/TableName?stream=sse"

Open a second terminal and update the counter -- the SSE stream will emit the change immediately.


Architecture

Browser / curl / AI Agent
    |
    +-- GET /demo-basic/ -----------> Static files (React/Vite SPA)
    +-- GET/PUT /api/TableName/{id} ----> Schema-driven REST (RocksDB)
    +-- GET /api/greeting --------------> Custom Rust resource (plugin)
    +-- GET /api/TableName?stream=sse --> Real-time SSE stream
    +-- POST /demo-basic/mcp ------> Auto-generated MCP tools
          |
          v
    +------------------------------------------+
    |             demo-basic                   |
    |  +------------+       +-----------+      |
    |  | TableName  |       | Greeting  |      |
    |  | (RocksDB)  |       | (Rust)    |      |
    |  +------------+       +-----------+      |
    |  Schema-driven         resource!()       |
    |  REST + SSE + MQTT     macro endpoint    |
    +------------------------------------------+
          |
          v
    Yeti (embedded RocksDB, plugin compiler, static file server)

Schema path: GraphQL type definition -> yeti schema loader -> REST routes + SSE + MQTT + MCP auto-generated -> RocksDB storage.

Plugin path: Rust source file -> yeti plugin compiler -> wasm32-wasip2 component -> loaded at startup -> routed by name.

Static path: Vite source -> npm run build (auto-triggered) -> web/ directory -> served at /demo-basic/ with SPA fallback.


Features

Persistent Counter (Schema-Driven)

The counter is a schema-defined table with automatic REST endpoints. No handler code exists for CRUD operations -- they are generated entirely from the GraphQL schema:

type TableName @table(database: "demo-basic") @export(public: [read, update]) {
    id: ID! @primaryKey
    count: Int!
}

This single type definition produces:

Endpoint Method Description
/demo-basic/api/TableName GET List all records (with ?limit=N)
/demo-basic/api/TableName POST Create a new record
/demo-basic/api/TableName/{id} GET Read a single record
/demo-basic/api/TableName/{id} PUT Update a record
/demo-basic/api/TableName/{id} DELETE Delete a record
/demo-basic/api/TableName?stream=sse GET Real-time change stream

The @export(public: [read, update]) directive allows unauthenticated GET and PUT requests. Create and delete still require authentication in production.

Custom Greeting Endpoint (Rust Resource)

The greeting endpoint demonstrates the resource!() macro -- yeti's minimal syntax for custom server-side logic:

use yeti_sdk::prelude::*;

resource!(Greeting {
  get => json!({"greeting": "Hello, World!"})
});

This compiles to a native Rust plugin that responds at /demo-basic/api/greeting. The resource!() macro supports all HTTP methods (get, post, put, delete) and has access to the full Request and ExtensionContext when needed:

resource!(Greeting {
  get(request, ctx) => {
    // Access headers, query params, tables, etc.
    json!({"greeting": "Hello, World!"})
  }
});

React Frontend (Vite SPA)

The frontend is a React/TypeScript application built with Vite. It provides:

  • Counter panel -- increment/decrement buttons that call PUT /api/TableName/{id} and display the current count
  • Greeting panel -- a button that calls GET /api/greeting and displays the JSON response
  • Source panels -- syntax-highlighted displays of the GraphQL schema and Rust resource code, so the demo is self-documenting
  • Responsive layout -- two-column grid on desktop, single column on mobile

The frontend builds automatically on first load via yeti's static file build integration. During development, run npm run dev in the source/ directory for hot module replacement.

Real-Time Streaming (Auto-Generated)

The @export directive on the schema automatically enables real-time streaming:

# SSE -- server-sent events
curl -s --max-time 30 "https://localhost:9996/demo-basic/api/TableName?stream=sse"

# MQTT -- subscribe to changes
mosquitto_sub -t "demo-basic/TableName" -h localhost -p 8883

When the counter is updated via REST or the UI, all subscribers receive the change immediately.

MCP Tools (Auto-Generated)

MCP tools for table operations are auto-generated from the @export schema. Any MCP-compatible agent (Claude Code, Cursor, Windsurf) can discover and use them via the standard MCP protocol at POST /demo-basic/mcp.


Data Model

TableName

Field Type Description
id ID! (primary key) Unique record identifier (e.g., "main-counter")
count Int! The counter value

Public access: read, update (via @export(public: [read, update]))

Storage: embedded RocksDB, database "demo-basic"


Configuration

App configuration lives in Cargo.toml under [package.metadata.app]. There is no separate config.yaml or services.yaml -- yeti reads everything it needs from the crate manifest.

[package]
name = "demo-basic"
version = "1.0.0"
description = "Simple counter with persistent state and a custom Rust greeting endpoint"

[package.metadata.app]
schemas = "schemas/basic.graphql"
resources = "resources/*.rs"
static = { path = "web", source = "source", spa = true, build = "npm install && npm run build" }
Key Purpose
schemas GraphQL schema file. Each @table type becomes a RocksDB-backed table with auto-generated REST/SSE/MQTT/MCP endpoints.
resources Glob pattern for Rust resource files. Each file is compiled into a native plugin and routed by struct name.
static.path Directory containing built static assets (output of npm run build).
static.source React/Vite source directory used when static.path is empty.
static.spa Enables SPA routing -- unknown paths serve index.html with a 200 status.
static.build Build command yeti runs from source when no built assets exist.

Hooks

Pre- and post-request shell hooks can be declared under [package.metadata.app.hooks]:

[package.metadata.app.hooks]
pre_request = ["./hooks/validate.sh"]
post_request_failure = ["./hooks/alert.sh"]

Frontend Development

cd ~/yeti/applications/demo-basic/source

# Install dependencies
npm install

# Start Vite dev server with HMR (proxied through yeti)
npm run dev

# Production build (also triggered automatically by yeti)
npm run build

Authentication

demo-basic uses yeti's declarative public access model. The @export(public: [read, update]) directive on the TableName schema allows unauthenticated read and update operations. No auth configuration is needed for this demo.

In development mode, all endpoints are accessible without authentication regardless of directives. In production:

Endpoint Auth Required Notes
GET /api/TableName No Public read via schema directive
GET /api/TableName/{id} No Public read via schema directive
PUT /api/TableName/{id} No Public update via schema directive
POST /api/TableName Yes Create not listed in public access
DELETE /api/TableName/{id} Yes Delete not listed in public access
GET /api/greeting No Custom resources are public by default in dev
Static files (/) No Always public

To add authentication, declare a [package.metadata.auth] section in Cargo.toml -- supported methods, JWT settings, OAuth providers, and role rules all live there. UI apps can drop in the shared Login.tsx page and useAuth hook to gate the SPA without writing custom code.


Project Structure

demo-basic/
├── Cargo.toml               # App configuration under [package.metadata.app]
├── schemas/
│   └── basic.graphql        # Counter table schema with @export directive
├── resources/
│   └── greeting.rs          # Custom Rust greeting endpoint (7 lines)
├── source/                  # React/Vite frontend source
│   ├── package.json         # Dependencies (React 18, Vite 5, TypeScript, highlight.js)
│   ├── tsconfig.json
│   ├── vite.config.ts
│   └── src/
│       ├── main.tsx              # Entry point
│       ├── App.tsx               # Thin shell -- wires auth gate + page
│       ├── api.ts                # Fetch helpers
│       ├── types.ts              # Shared TypeScript types
│       ├── utils.ts              # JSON syntax highlighting utility
│       ├── components/
│       │   └── Footer.tsx        # Shared UI primitives
│       ├── hooks/
│       │   └── useAuth.ts        # Auth state hook (template)
│       ├── pages/
│       │   ├── BasicPage.tsx     # Counter + greeting panels with source display
│       │   └── Login.tsx         # Configurable login page (template)
│       └── styles/
│           ├── _vars.css         # Per-app brand colors and shared tokens
│           ├── yeti.css          # Canonical Yeti stylesheet
│           └── index.css         # App-specific overrides
└── web/                     # Built static assets (auto-generated)

The src/ layout is the standard yeti UI app structure: a thin App.tsx, root utility files (api.ts, types.ts, utils.ts), shared UI in components/, hooks in hooks/, page components in pages/, and stylesheets in styles/. yeti.css is the canonical stylesheet shared across all yeti apps; _vars.css holds this app's brand tokens; index.css carries app-specific overrides.


Comparison

demo-basic Traditional Approach
Backend code 7 lines (greeting.rs) + 6 lines (schema) Express/Flask route handlers, ORM models, migrations
Database setup Zero -- declared in schema, auto-provisioned Install DB, create database, run migrations, configure connection
REST API Auto-generated from schema Hand-written controllers for each CRUD operation
Real-time Auto-generated SSE + MQTT from @export WebSocket server, custom event wiring, external broker
MCP tools Auto-generated from schema Separate MCP server process with hand-written tool definitions
Auth model Declarative in schema (public: [read, update]) Middleware chains, role checks in every handler
Frontend build Auto-triggered by yeti on first load Separate build step, CI/CD pipeline, deployment config
Deployment Drop folder in ~/yeti/applications/ Dockerfile, docker-compose, reverse proxy, process manager
Cold start ~2 min compile (cached: ~10 sec) Depends on runtime, dependency install, DB connection
Runtime Native Rust binary (sub-ms responses) Interpreted (Node.js/Python) or JIT (JVM)

Built with Yeti | The Performance Platform for Agent-Driven Development

About

Simple counter with persistent state and a custom Rust greeting endpoint. A Yeti demo.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages