high-performance HTTP server for Tana smart contract handlers.
tana-edge serves the read-only HTTP endpoints that contracts export. when contracts define get() or post() functions, tana-edge makes them available at HTTP endpoints, executing contract code for each request.
when a request arrives for a contract endpoint, tana-edge fetches the contract code, creates a fresh V8 isolate, executes the appropriate handler function, and returns the response. each request gets complete isolation.
HTTP request
|
v
tana-edge router
|
v
fetch contract from ledger
|
v
create V8 isolate
|
v
execute get() or post()
|
v
HTTP response
contracts export optional HTTP handlers:
export async function get(req: Request) {
// read-only operations
const data = await kv.get("key")
return { data }
}
export async function post(req: Request) {
// also read-only (despite the name)
const body = await req.json()
return { received: body }
}
unlike contract mutations (which go through transactions), HTTP handlers are strictly read-only. they can read from key-value storage but cannot write. this allows serving requests without consensus.
handlers receive authenticated caller information when available:
export async function get(req: Request) {
const caller = req.tana.caller // user ID or null
// personalize response based on caller
}
built with Rust using the same V8 integration as tana-runtime. designed for high throughput with fresh isolates per request providing security isolation without sacrificing performance.
tana-edge runs as a standalone service, accessed through the tana-api gateway at /contracts/{name}/*. it fetches contract code from the ledger service and shares the key-value storage layer.
the codebase is organized into focused modules (~5,000 lines total):
src/
├── main.rs (1,266 lines) - HTTP handlers, SSR execution, router
├── isolate_pool.rs (1,381 lines) - V8 isolate pool for warm starts
├── ops/ (769 lines) - Deno-style JS runtime operations
│ ├── mod.rs - Op exports and registration
│ ├── basic.rs - Core ops (print, fetch, sleep)
│ ├── kv.rs - Key-value storage ops
│ ├── block.rs - Blockchain query ops
│ ├── blockchain.rs - Extended blockchain ops
│ ├── tx.rs - Transaction ops
│ ├── timer.rs - Timer queue ops
│ └── streaming.rs - HTTP streaming ops
├── contract_validator.rs (438 lines) - AST validation for contracts
├── db.rs (369 lines) - Database utilities (dev mode)
├── contract_db.rs (224 lines) - Per-contract database isolation
├── tana_modules.rs (188 lines) - Module resolution for tana/* imports
├── error_analysis.rs (124 lines) - Smart error suggestions
├── types.rs (83 lines) - Shared type definitions
├── state.rs (62 lines) - Global state (KV storage, etc.)
├── t4_client.rs (62 lines) - T4 content storage client
└── log.rs (33 lines) - Logging utilities
| module | purpose |
|---|---|
main.rs |
HTTP routing, request handlers, streaming SSR execution |
isolate_pool.rs |
Warm V8 isolate pool for ~5ms request latency |
ops/ |
JavaScript runtime operations exposed to contract code |
contract_validator.rs |
AST-based validation of contract exports |
db.rs |
PostgreSQL connection pool for dev mode |
contract_db.rs |
Per-contract isolated databases for production |
tana_modules.rs |
Resolution for tana/kv, tana/http, etc. imports |
error_analysis.rs |
Developer-friendly error messages with suggestions |
t4_client.rs |
Fetching contract code from T4 storage |
isolate pooling: the isolate pool maintains warm V8 instances per contract, reducing cold start overhead from ~100ms to ~5ms. controlled by DISABLE_ISOLATE_POOL=true for debugging.
streaming SSR: uses Rust channels to stream HTML chunks from V8 to HTTP responses as they're rendered, enabling TTFB optimization for React pages.
per-contract databases: in production, each contract gets an isolated PostgreSQL database ({address}_{contract_id}) accessed through PgBouncer for connection pooling.
handler structure in main.rs: HTTP handlers remain in main.rs because they're tightly coupled with the isolate pool static and multiple modules. extracting them would require significant refactoring (passing pool through all layers or creating a shared context struct).
cargo build --release
# development mode (reads from ../contracts/)
CONTRACTS_DIR=../contracts ./target/release/tana-edge
# production mode (reads from T4 storage)
T4_URL=http://t4:8507 ./target/release/tana-edge
# with per-contract databases
CONTRACT_DB_ADMIN_URL=postgres://... CONTRACT_DB_BOUNCER_URL=postgres://... ./target/release/tana-edge
| endpoint | purpose |
|---|---|
GET /health |
Health check with pool stats |
POST /_admin/cache/evict |
Evict all cached isolates |
/_dev/{contractId}/* |
Development: serve from filesystem |
/{address}/{contractId}/* |
Production: serve from T4 storage |
high-performance HTTP server for Tana smart contract handlers.
tana-edge serves the read-only HTTP endpoints that contracts export. when contracts define get() or post() functions, tana-edge makes them available at HTTP endpoints, executing contract code for each request.
when a request arrives for a contract endpoint, tana-edge fetches the contract code, creates a fresh V8 isolate, executes the appropriate handler function, and returns the response. each request gets complete isolation.
HTTP request
|
v
tana-edge router
|
v
fetch contract from ledger
|
v
create V8 isolate
|
v
execute get() or post()
|
v
HTTP response
contracts export optional HTTP handlers:
export async function get(req: Request) {
// read-only operations
const data = await kv.get("key")
return { data }
}
export async function post(req: Request) {
// also read-only (despite the name)
const body = await req.json()
return { received: body }
}
unlike contract mutations (which go through transactions), HTTP handlers are strictly read-only. they can read from key-value storage but cannot write. this allows serving requests without consensus.
handlers receive authenticated caller information when available:
export async function get(req: Request) {
const caller = req.tana.caller // user ID or null
// personalize response based on caller
}
built with Rust using the same V8 integration as tana-runtime. designed for high throughput with fresh isolates per request providing security isolation without sacrificing performance.
tana-edge runs as a standalone service, accessed through the tana-api gateway at /contracts/{name}/*. it fetches contract code from the ledger service and shares the key-value storage layer.
the codebase is organized into focused modules (~5,000 lines total):
src/
├── main.rs (1,266 lines) - HTTP handlers, SSR execution, router
├── isolate_pool.rs (1,381 lines) - V8 isolate pool for warm starts
├── ops/ (769 lines) - Deno-style JS runtime operations
│ ├── mod.rs - Op exports and registration
│ ├── basic.rs - Core ops (print, fetch, sleep)
│ ├── kv.rs - Key-value storage ops
│ ├── block.rs - Blockchain query ops
│ ├── blockchain.rs - Extended blockchain ops
│ ├── tx.rs - Transaction ops
│ ├── timer.rs - Timer queue ops
│ └── streaming.rs - HTTP streaming ops
├── contract_validator.rs (438 lines) - AST validation for contracts
├── db.rs (369 lines) - Database utilities (dev mode)
├── contract_db.rs (224 lines) - Per-contract database isolation
├── tana_modules.rs (188 lines) - Module resolution for tana/* imports
├── error_analysis.rs (124 lines) - Smart error suggestions
├── types.rs (83 lines) - Shared type definitions
├── state.rs (62 lines) - Global state (KV storage, etc.)
├── t4_client.rs (62 lines) - T4 content storage client
└── log.rs (33 lines) - Logging utilities
| module | purpose |
|---|---|
main.rs |
HTTP routing, request handlers, streaming SSR execution |
isolate_pool.rs |
Warm V8 isolate pool for ~5ms request latency |
ops/ |
JavaScript runtime operations exposed to contract code |
contract_validator.rs |
AST-based validation of contract exports |
db.rs |
PostgreSQL connection pool for dev mode |
contract_db.rs |
Per-contract isolated databases for production |
tana_modules.rs |
Resolution for tana/kv, tana/http, etc. imports |
error_analysis.rs |
Developer-friendly error messages with suggestions |
t4_client.rs |
Fetching contract code from T4 storage |
isolate pooling: the isolate pool maintains warm V8 instances per contract, reducing cold start overhead from ~100ms to ~5ms. controlled by DISABLE_ISOLATE_POOL=true for debugging.
streaming SSR: uses Rust channels to stream HTML chunks from V8 to HTTP responses as they're rendered, enabling TTFB optimization for React pages.
per-contract databases: in production, each contract gets an isolated PostgreSQL database ({address}_{contract_id}) accessed through PgBouncer for connection pooling.
handler structure in main.rs: HTTP handlers remain in main.rs because they're tightly coupled with the isolate pool static and multiple modules. extracting them would require significant refactoring (passing pool through all layers or creating a shared context struct).
cargo build --release
# development mode (reads from ../contracts/)
CONTRACTS_DIR=../contracts ./target/release/tana-edge
# production mode (reads from T4 storage)
T4_URL=http://t4:8507 ./target/release/tana-edge
# with per-contract databases
CONTRACT_DB_ADMIN_URL=postgres://... CONTRACT_DB_BOUNCER_URL=postgres://... ./target/release/tana-edge
| endpoint | purpose |
|---|---|
GET /health |
Health check with pool stats |
POST /_admin/cache/evict |
Evict all cached isolates |
/_dev/{contractId}/* |
Development: serve from filesystem |
/{address}/{contractId}/* |
Production: serve from T4 storage |