diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index dc75631..7e3d30d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,16 +1,12 @@
name: CI
on:
- push:
- branches: [main]
pull_request:
jobs:
rust:
name: Rust
runs-on: ubuntu-latest
- # Skip CI for release-please commits (already tested on the source commit)
- if: github.actor != 'release-please[bot]'
steps:
- uses: actions/checkout@v4
@@ -42,7 +38,6 @@ jobs:
typescript-core:
name: TypeScript Core
runs-on: ubuntu-latest
- if: github.actor != 'release-please[bot]'
defaults:
run:
working-directory: typescript/core
@@ -68,7 +63,6 @@ jobs:
typescript-react:
name: TypeScript React
runs-on: ubuntu-latest
- if: github.actor != 'release-please[bot]'
steps:
- uses: actions/checkout@v4
@@ -96,10 +90,6 @@ jobs:
typescript-stacks:
name: TypeScript Stacks
runs-on: ubuntu-latest
- if: github.actor != 'release-please[bot]'
- defaults:
- run:
- working-directory: stacks/sdk/typescript
steps:
- uses: actions/checkout@v4
@@ -107,13 +97,17 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: '20'
- cache: 'npm'
- cache-dependency-path: stacks/sdk/typescript/package-lock.json
+
+ - name: Build core dependency
+ working-directory: typescript/core
+ run: npm ci && npm run build
- name: Install dependencies
+ working-directory: stacks/sdk/typescript
run: npm ci
- name: Build
+ working-directory: stacks/sdk/typescript
run: npm run build
# Validates that generated SDKs are up-to-date with their source ASTs.
@@ -121,7 +115,6 @@ jobs:
validate-generated-sdks:
name: Validate Generated SDKs
runs-on: ubuntu-latest
- if: github.actor != 'release-please[bot]'
steps:
- uses: actions/checkout@v4
@@ -183,7 +176,6 @@ jobs:
python:
name: Python
runs-on: ubuntu-latest
- if: github.actor != 'release-please[bot]'
defaults:
run:
working-directory: python/hyperstack-sdk
@@ -209,7 +201,6 @@ jobs:
docs:
name: Docs
runs-on: ubuntu-latest
- if: github.actor != 'release-please[bot]'
defaults:
run:
working-directory: docs
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
index 8d873cc..864e796 100644
--- a/.github/workflows/release-please.yml
+++ b/.github/workflows/release-please.yml
@@ -152,12 +152,22 @@ jobs:
bundle-templates:
name: Bundle CLI Templates
- needs: release-please
+ needs: [release-please, publish-npm, publish-rust]
runs-on: ubuntu-latest
if: needs.release-please.outputs.cli_release_created == 'true'
steps:
- uses: actions/checkout@v4
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Update example versions
+ run: |
+ VERSION="${{ needs.release-please.outputs.cli_version }}"
+ ./scripts/update-example-versions.sh "$VERSION"
+
- name: Create templates tarball
run: |
VERSION="${{ needs.release-please.outputs.cli_version }}"
diff --git a/Cargo.lock b/Cargo.lock
index 20ac4b6..fb5e9b7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1172,6 +1172,22 @@ dependencies = [
"tokio-rustls 0.24.1",
]
+[[package]]
+name = "hyper-rustls"
+version = "0.27.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
+dependencies = [
+ "http 1.4.0",
+ "hyper 1.8.1",
+ "hyper-util",
+ "rustls 0.23.36",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls 0.26.4",
+ "tower-service",
+]
+
[[package]]
name = "hyper-timeout"
version = "0.4.1"
@@ -1197,12 +1213,29 @@ dependencies = [
"tower-service",
]
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper 1.8.1",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
[[package]]
name = "hyper-util"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
dependencies = [
+ "base64 0.22.1",
"bytes",
"futures-channel",
"futures-core",
@@ -1210,12 +1243,16 @@ dependencies = [
"http 1.4.0",
"http-body 1.0.1",
"hyper 1.8.1",
+ "ipnet",
"libc",
+ "percent-encoding",
"pin-project-lite",
"socket2 0.6.1",
+ "system-configuration",
"tokio",
"tower-service",
"tracing",
+ "windows-registry",
]
[[package]]
@@ -1230,6 +1267,7 @@ dependencies = [
"hyperstack-macros",
"hyperstack-sdk",
"hyperstack-server",
+ "reqwest 0.11.27",
"serde",
"serde_json",
"smallvec",
@@ -1256,7 +1294,7 @@ dependencies = [
"hyperstack-interpreter",
"indicatif",
"regex",
- "reqwest",
+ "reqwest 0.11.27",
"rpassword",
"serde",
"serde_json",
@@ -1280,6 +1318,7 @@ dependencies = [
"prost 0.13.5",
"prost-reflect",
"prost-types 0.13.5",
+ "reqwest 0.12.28",
"serde",
"serde_json",
"sha2",
@@ -1293,6 +1332,7 @@ dependencies = [
name = "hyperstack-macros"
version = "0.4.3"
dependencies = [
+ "bs58",
"hex",
"proc-macro2",
"quote",
@@ -1539,6 +1579,16 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+[[package]]
+name = "iri-string"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
@@ -2379,7 +2429,7 @@ dependencies = [
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.32",
- "hyper-rustls",
+ "hyper-rustls 0.24.2",
"ipnet",
"js-sys",
"log",
@@ -2405,6 +2455,46 @@ dependencies = [
"winreg",
]
+[[package]]
+name = "reqwest"
+version = "0.12.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "h2 0.4.13",
+ "http 1.4.0",
+ "http-body 1.0.1",
+ "http-body-util",
+ "hyper 1.8.1",
+ "hyper-rustls 0.27.7",
+ "hyper-tls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper 1.0.2",
+ "tokio",
+ "tokio-native-tls",
+ "tower 0.5.2",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
[[package]]
name = "ring"
version = "0.17.14"
@@ -2896,6 +2986,9 @@ name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
[[package]]
name = "synstructure"
@@ -3439,6 +3532,24 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "tower-http"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
+dependencies = [
+ "bitflags 2.10.0",
+ "bytes",
+ "futures-util",
+ "http 1.4.0",
+ "http-body 1.0.1",
+ "iri-string",
+ "pin-project-lite",
+ "tower 0.5.2",
+ "tower-layer",
+ "tower-service",
+]
+
[[package]]
name = "tower-layer"
version = "0.3.3"
@@ -3873,6 +3984,17 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+[[package]]
+name = "windows-registry"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
+dependencies = [
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
[[package]]
name = "windows-result"
version = "0.4.1"
diff --git a/docs/concepts/stack-api.mdx b/docs/concepts/stack-api.mdx
index a9c15b9..2b53113 100644
--- a/docs/concepts/stack-api.mdx
+++ b/docs/concepts/stack-api.mdx
@@ -437,14 +437,16 @@ function TokenCard({ token }: { token: Token }) {
## Connection State
-Monitor WebSocket connection status.
+Monitor WebSocket connection status via `useHyperstack`:
```typescript
-import { useConnectionState } from 'hyperstack-react';
+import { useHyperstack } from 'hyperstack-react';
+import { MY_STACK } from './stack';
function ConnectionIndicator() {
- const state = useConnectionState();
- // 'disconnected' | 'connecting' | 'connected' | 'error' | 'reconnecting'
+ const { connectionState, isConnected } = useHyperstack(MY_STACK);
+ // connectionState: 'disconnected' | 'connecting' | 'connected' | 'error' | 'reconnecting'
+ // isConnected: boolean (true when connectionState === 'connected')
const colors = {
disconnected: 'gray',
@@ -454,7 +456,7 @@ function ConnectionIndicator() {
reconnecting: 'orange',
};
- return {state};
+ return {isConnected ? 'Live' : connectionState};
}
```
diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx
index a449228..251217c 100644
--- a/docs/src/content/docs/index.mdx
+++ b/docs/src/content/docs/index.mdx
@@ -30,19 +30,22 @@ function App() {
}
function LatestRounds() {
- const stack = useHyperstack(ORE_STREAM_STACK);
- const { data: rounds, isLoading } = stack.views.OreRound.latest.use({ take: 5 });
+ const { views, isConnected } = useHyperstack(ORE_STREAM_STACK);
+ const { data: rounds, isLoading } = views.OreRound.latest.use({ take: 5 });
if (isLoading) return
Connecting...
;
return (
-
- {rounds?.map((round) => (
- -
- Round #{round.id?.round_id} โ Motherlode: {round.state?.motherlode}
-
- ))}
-
+
+
{isConnected ? "๐ข Live" : "Connecting..."}
+
+ {rounds?.map((round) => (
+ -
+ Round #{round.id?.round_id} โ Motherlode: {round.state?.motherlode}
+
+ ))}
+
+
);
}
```
diff --git a/docs/src/content/docs/sdks/react.mdx b/docs/src/content/docs/sdks/react.mdx
index 079597a..1e12e37 100644
--- a/docs/src/content/docs/sdks/react.mdx
+++ b/docs/src/content/docs/sdks/react.mdx
@@ -27,17 +27,20 @@ function App() {
}
function Dashboard() {
- const stack = useHyperstack(ORE_STREAM_STACK);
- const { data: rounds, isLoading } = stack.views.OreRound.latest.use();
+ const { views, isConnected } = useHyperstack(ORE_STREAM_STACK);
+ const { data: rounds, isLoading } = views.OreRound.latest.use();
if (isLoading) return Loading...
;
return (
-
- {rounds?.map((round) => (
- - Round #{round.id?.round_id}
- ))}
-
+
+
{isConnected ? "๐ข Live" : "Connecting..."}
+
+ {rounds?.map((round) => (
+ - Round #{round.id?.round_id}
+ ))}
+
+
);
}
```
@@ -136,8 +139,8 @@ import { useHyperstack } from "hyperstack-react";
import { ORE_STREAM_STACK } from "hyperstack-stacks/ore";
function RoundList() {
- const stack = useHyperstack(ORE_STREAM_STACK);
- const { data: rounds, isLoading, error } = stack.views.OreRound.list.use();
+ const { views } = useHyperstack(ORE_STREAM_STACK);
+ const { data: rounds, isLoading, error } = views.OreRound.list.use();
if (isLoading) return Loading...
;
if (error) return Error: {error.message}
;
@@ -158,8 +161,8 @@ function RoundList() {
```tsx
function RoundList() {
- const stack = useHyperstack(ORE_STREAM_STACK);
- const { data: rounds, isLoading, error } = stack.views.OreRound.list.use();
+ const { views } = useHyperstack(ORE_STREAM_STACK);
+ const { data: rounds, isLoading, error } = views.OreRound.list.use();
if (isLoading) return Connecting...
;
if (error) return Error: {error.message}
;
@@ -180,8 +183,8 @@ function RoundList() {
```tsx
function RoundDetail({ roundAddress }: { roundAddress: string }) {
- const stack = useHyperstack(ORE_STREAM_STACK);
- const { data: round, isLoading } = stack.views.OreRound.state.use({
+ const { views } = useHyperstack(ORE_STREAM_STACK);
+ const { data: round, isLoading } = views.OreRound.state.use({
key: roundAddress,
});
@@ -205,16 +208,18 @@ function RoundDetail({ roundAddress }: { roundAddress: string }) {
### Single Item Query
```tsx
+const { views } = useHyperstack(MY_STACK);
+
// Get only one item with type-safe return (T | undefined instead of T[])
-const { data: latestToken } = stack.views.tokens.list.use({ take: 1 });
+const { data: latestToken } = views.tokens.list.use({ take: 1 });
// data: Token | undefined
// Or use the dedicated useOne method
-const { data: latestToken } = stack.views.tokens.list.useOne();
+const { data: latestToken } = views.tokens.list.useOne();
// data: Token | undefined
// useOne with filters
-const { data: topToken } = stack.views.tokens.list.useOne({
+const { data: topToken } = views.tokens.list.useOne({
where: { volume: { gte: 10000 } },
});
```
@@ -226,7 +231,8 @@ The SDK supports both server-side and client-side filtering:
**Server-side** options reduce data sent over the wire:
```tsx
-const { data: rounds } = stack.views.OreRound.list.use({
+const { views } = useHyperstack(ORE_STREAM_STACK);
+const { data: rounds } = views.OreRound.list.use({
take: 10, // Limit to 10 entities from server
skip: 20, // Skip first 20 (for pagination)
});
@@ -237,7 +243,8 @@ For advanced server-side filtering, use [custom views](/using-stacks/filtering-f
**Client-side** filtering happens after data is received. Use `where` with comparison operators:
```tsx
-const { data: highValueRounds } = stack.views.OreRound.list.use({
+const { views } = useHyperstack(ORE_STREAM_STACK);
+const { data: highValueRounds } = views.OreRound.list.use({
where: {
// Supported operators: gte, lte, gt, lt, or exact match
motherlode: { gte: 1000000 }, // Greater than or equal
@@ -257,7 +264,8 @@ const { data: highValueRounds } = stack.views.OreRound.list.use({
```tsx
// Exact match example
-const { data } = stack.views.OreRound.list.use({
+const { views } = useHyperstack(ORE_STREAM_STACK);
+const { data } = views.OreRound.list.use({
where: { status: "active" }, // Exact equality
});
```
@@ -265,8 +273,9 @@ const { data } = stack.views.OreRound.list.use({
### Conditional Subscription
```tsx
+const { views } = useHyperstack(ORE_STREAM_STACK);
// Only subscribe when we have a valid address
-const { data: round } = stack.views.OreRound.state.use(
+const { data: round } = views.OreRound.state.use(
{ key: roundAddress },
{ enabled: !!roundAddress },
);
@@ -275,8 +284,9 @@ const { data: round } = stack.views.OreRound.state.use(
### Initial Data
```tsx
+const { views } = useHyperstack(ORE_STREAM_STACK);
// Show placeholder while connecting
-const { data: rounds } = stack.views.OreRound.list.use({}, { initialData: [] });
+const { data: rounds } = views.OreRound.list.use({}, { initialData: [] });
```
### View Hook Parameters
@@ -293,7 +303,8 @@ The `.use()` method accepts two arguments: params and options.
| `limit` | `number` | Client | Max results to keep after filtering |
```typescript
-const { data } = stack.views.OreRound.list.use({
+const { views } = useHyperstack(ORE_STREAM_STACK);
+const { data } = views.OreRound.list.use({
take: 50, // Server sends max 50
skip: 0, // Start from beginning
where: { motherlode: { gte: 100000 } }, // Filter locally
@@ -304,7 +315,8 @@ const { data } = stack.views.OreRound.list.use({
#### View Hook Options
```typescript
-const { data } = stack.views.tokens.list.use(
+const { views } = useHyperstack(MY_STACK);
+const { data } = views.tokens.list.use(
{ limit: 10 }, // Params
{
enabled: true, // Enable/disable subscription
@@ -326,13 +338,14 @@ const { data } = stack.views.tokens.list.use(
## Connection State
-Monitor WebSocket health:
+The `useHyperstack` hook returns connection state directly:
```tsx
-import { useConnectionState } from "hyperstack-react";
+import { useHyperstack } from "hyperstack-react";
+import { ORE_STREAM_STACK } from "hyperstack-stacks/ore";
function ConnectionStatus() {
- const state = useConnectionState();
+ const { connectionState, isConnected } = useHyperstack(ORE_STREAM_STACK);
const statusColors = {
connected: "green",
@@ -343,13 +356,24 @@ function ConnectionStatus() {
};
return (
-
- {state === "connected" ? "Live" : state}
+
+ {isConnected ? "Live" : connectionState}
);
}
```
+### useHyperstack Return Values
+
+| Property | Type | Description |
+| ----------------- | ----------------- | ------------------------------------------------ |
+| `views` | `object` | Typed view accessors |
+| `connectionState` | `ConnectionState` | Current WebSocket state |
+| `isConnected` | `boolean` | Convenience: `true` when `state === 'connected'` |
+| `isLoading` | `boolean` | `true` until client is ready |
+| `error` | `Error \| null` | Connection error if any |
+| `client` | `HyperStack` | Low-level client instance |
+
### Connection States
| State | Description |
@@ -360,28 +384,70 @@ function ConnectionStatus() {
| `reconnecting` | Auto-reconnecting after failure |
| `error` | Connection failed |
+### Standalone useConnectionState Hook
+
+For cases where you need connection state outside of a component using `useHyperstack`, you can use the standalone hook:
+
+```tsx
+import { useConnectionState } from "hyperstack-react";
+
+function GlobalConnectionIndicator() {
+ const state = useConnectionState();
+ return
{state}
;
+}
+```
+
+:::note
+`useConnectionState()` without arguments returns the state of the single active client. If you have multiple stacks, pass the stack definition: `useConnectionState(MY_STACK)`.
+:::
+
---
## API Reference
-### `useHyperstack(stackDefinition)`
+### `useHyperstack(stackDefinition, options?)`
+
+Returns a typed interface to your stack's views, along with connection state.
+
+```typescript
+const { views, connectionState, isConnected, isLoading, error, client } = useHyperstack(ORE_STREAM_STACK);
+
+// Access views
+views.OreRound.list.use()
+views.OreRound.state.use({ key })
+views.OreRound.latest.use()
+
+// Check connection
+if (isConnected) {
+ console.log('Live!');
+}
+```
+
+#### Options
-Returns a typed interface to your stack's views. The stack definition includes its URL, so the connection is established automatically.
+| Option | Type | Description |
+| ------ | -------- | -------------------------------- |
+| `url` | `string` | Override the stack's default URL |
```typescript
-const stack = useHyperstack(ORE_STREAM_STACK);
-// stack.views.OreRound.list.use()
-// stack.views.OreRound.state.use({ key })
-// stack.views.OreRound.latest.use() // Custom view
+// Connect to local development server
+const { views, isConnected } = useHyperstack(ORE_STREAM_STACK, {
+ url: "ws://localhost:8878"
+});
```
-### `useConnectionState()`
+### `useConnectionState(stack?)`
-Returns the current WebSocket connection state.
+Standalone hook for connection state. Prefer using `connectionState` from `useHyperstack` when possible.
```typescript
+// Without argument: returns state of single active client
const state = useConnectionState();
-// 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error'
+
+// With stack: returns state for specific stack
+const state = useConnectionState(ORE_STREAM_STACK);
+
+// Returns: 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error'
```
### `HyperstackProvider`
@@ -410,7 +476,8 @@ To override the URL for all stacks (e.g., for local development):
A convenience method for fetching a single item from a list view with proper typing.
```typescript
-const { data } = stack.views.tokens.list.useOne();
+const { views } = useHyperstack(MY_STACK);
+const { data } = views.tokens.list.useOne();
// data: Token | undefined (not Token[])
```
@@ -424,18 +491,17 @@ A full React component combining everything:
```tsx
// src/App.tsx
-import { useHyperstack, useConnectionState } from "hyperstack-react";
+import { useHyperstack } from "hyperstack-react";
import { ORE_STREAM_STACK } from "hyperstack-stacks/ore";
function OreDashboard() {
- const stack = useHyperstack(ORE_STREAM_STACK);
- const connectionState = useConnectionState();
+ const { views, connectionState, isConnected } = useHyperstack(ORE_STREAM_STACK);
const {
data: rounds,
isLoading,
error,
- } = stack.views.OreRound.latest.use({
+ } = views.OreRound.latest.use({
take: 5,
});
@@ -443,7 +509,9 @@ function OreDashboard() {
Live ORE Mining Rounds
- {connectionState}
+
+ {isConnected ? "Live" : connectionState}
+
diff --git a/docs/src/content/docs/using-stacks/connect.mdx b/docs/src/content/docs/using-stacks/connect.mdx
index 8e22313..7d1e725 100644
--- a/docs/src/content/docs/using-stacks/connect.mdx
+++ b/docs/src/content/docs/using-stacks/connect.mdx
@@ -38,8 +38,8 @@ import { HyperstackProvider, useHyperstack } from "hyperstack-react";
import { ORE_STREAM_STACK } from "hyperstack-stacks/ore";
function OreRounds() {
- const stack = useHyperstack(ORE_STREAM_STACK);
- const { data: rounds, isLoading } = stack.views.OreRound.latest.use({
+ const { views, isConnected } = useHyperstack(ORE_STREAM_STACK);
+ const { data: rounds, isLoading } = views.OreRound.latest.use({
take: 5,
});
@@ -47,7 +47,7 @@ function OreRounds() {
return (
-
Live ORE Mining Rounds
+
Live ORE Mining Rounds {isConnected && "๐ข"}
{rounds?.map((round) => (
Round #{round.id?.round_id} โ Motherlode: {round.state?.motherlode}
diff --git a/docs/src/content/docs/using-stacks/quickstart.mdx b/docs/src/content/docs/using-stacks/quickstart.mdx
index ea27588..d2dfa40 100644
--- a/docs/src/content/docs/using-stacks/quickstart.mdx
+++ b/docs/src/content/docs/using-stacks/quickstart.mdx
@@ -171,15 +171,16 @@ export default function App() {
```tsx
// components/OreDashboard.tsx
-import { useHyperstack, useConnectionState } from "hyperstack-react";
+import { useHyperstack } from "hyperstack-react";
import { ORE_STREAM_STACK } from "hyperstack-stacks/ore";
export function OreDashboard() {
- const stack = useHyperstack(ORE_STREAM_STACK);
- const { data: rounds } = stack.views.OreRound.latest.use({ take: 5 });
+ const { views, isConnected } = useHyperstack(ORE_STREAM_STACK);
+ const { data: rounds } = views.OreRound.latest.use({ take: 5 });
return (
+
{isConnected ? "Live" : "Connecting..."}
{rounds?.map((round) => (
Round #{round.id?.round_id} โ Motherlode: {round.state?.motherlode}
diff --git a/examples/ore-react/package.json b/examples/ore-react/package.json
index 27ba53c..5245f8f 100644
--- a/examples/ore-react/package.json
+++ b/examples/ore-react/package.json
@@ -13,12 +13,17 @@
"hyperstack-stacks": "^0.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "zod": "^3.23.0",
"zustand": "^4.4.1"
},
"devDependencies": {
+ "@tailwindcss/postcss": "^4.1.18",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.2.0",
+ "autoprefixer": "^10.4.24",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^4.1.18",
"typescript": "^5.3.0",
"vite": "^5.0.0"
}
diff --git a/examples/ore-react/postcss.config.js b/examples/ore-react/postcss.config.js
new file mode 100644
index 0000000..a7f73a2
--- /dev/null
+++ b/examples/ore-react/postcss.config.js
@@ -0,0 +1,5 @@
+export default {
+ plugins: {
+ '@tailwindcss/postcss': {},
+ },
+}
diff --git a/examples/ore-react/src/App.tsx b/examples/ore-react/src/App.tsx
index 9a218d8..e9d4df5 100644
--- a/examples/ore-react/src/App.tsx
+++ b/examples/ore-react/src/App.tsx
@@ -1,10 +1,13 @@
-import { OreDashboard } from './components/OreDashboard';
+import { OreDashboard } from './components';
import { HyperstackProvider } from 'hyperstack-react';
+import { ThemeProvider } from './hooks/useTheme';
export default function App() {
return (
-
-
-
+
+
+
+
+
);
}
diff --git a/examples/ore-react/src/components/BlockGrid.tsx b/examples/ore-react/src/components/BlockGrid.tsx
new file mode 100644
index 0000000..2f1bc21
--- /dev/null
+++ b/examples/ore-react/src/components/BlockGrid.tsx
@@ -0,0 +1,59 @@
+import type { ValidatedOreRound } from '../schemas/ore-round-validated';
+import { MinerIcon, SolanaIcon } from './icons';
+
+interface BlockGridProps {
+ round: ValidatedOreRound | undefined;
+}
+
+export function BlockGrid({ round }: BlockGridProps) {
+ const blocks = round
+ ? round.state.deployed_per_square_ui.map((deployedUi, i) => ({
+ id: i + 1,
+ minerCount: round.state.count_per_square[i],
+ deployedUi,
+ isWinner: round.results?.winning_square === i,
+ }))
+ : Array.from({ length: 25 }, (_, i) => ({
+ id: i + 1,
+ minerCount: 0,
+ deployedUi: 0,
+ isWinner: false,
+ }));
+
+ return (
+
+ {blocks.map((block) => (
+
+
+
{block.id}
+
+ {block.minerCount}
+
+
+
+
+
+ {Number(block.deployedUi).toFixed(4)}
+
+
+ ))}
+
+ );
+}
diff --git a/examples/ore-react/src/components/ConnectionBadge.tsx b/examples/ore-react/src/components/ConnectionBadge.tsx
new file mode 100644
index 0000000..710b734
--- /dev/null
+++ b/examples/ore-react/src/components/ConnectionBadge.tsx
@@ -0,0 +1,16 @@
+interface ConnectionBadgeProps {
+ isConnected: boolean;
+}
+
+export function ConnectionBadge({ isConnected }: ConnectionBadgeProps) {
+ return (
+
+
+
+
+ {isConnected ? 'Connected' : 'Connecting'}
+
+
+
+ );
+}
diff --git a/examples/ore-react/src/components/OreDashboard.tsx b/examples/ore-react/src/components/OreDashboard.tsx
index cbe3457..d22fe2f 100644
--- a/examples/ore-react/src/components/OreDashboard.tsx
+++ b/examples/ore-react/src/components/OreDashboard.tsx
@@ -1,95 +1,41 @@
-import { useHyperstack, useConnectionState } from 'hyperstack-react';
-import {
- ORE_STREAM_STACK,
- type OreRound,
- type OreTreasury,
- type OreMiner,
-} from 'hyperstack-stacks/ore';
+import { useHyperstack } from 'hyperstack-react';
+import { ORE_STREAM_STACK } from 'hyperstack-stacks/ore';
+import { ValidatedOreRoundSchema } from '../schemas/ore-round-validated';
+import { BlockGrid } from './BlockGrid';
+import { StatsPanel } from './StatsPanel';
+import { ConnectionBadge } from './ConnectionBadge';
+import { ThemeToggle } from './ThemeToggle';
export function OreDashboard() {
- const stack = useHyperstack(ORE_STREAM_STACK);
- const { data: latestRounds } = stack.views.OreRound.latest.use({ take: 5 });
- // const { data: treasury } = stack.views.OreTreasury.list.use({ take: 1 });
- // console.log(treasury);
- // const { data: miner } = stack.views.OreMiner.list.use({ take: 1 });
- const connectionState = useConnectionState();
- console.log(connectionState);
- const isConnected = connectionState === 'connected';
+ const { views, isConnected } = useHyperstack(ORE_STREAM_STACK, { url: "ws://localhost:8878" });
+ const { data: latestRound } = views.OreRound.latest.useOne({ schema: ValidatedOreRoundSchema });
+ const { data: treasuryData } = views.OreTreasury.list.useOne();
return (
-
-