A Sinatra-flavoured web framework that compiles to a native binary via Spinel.
Pre-alpha. Tep exists primarily to exercise Spinel against real-world Ruby code. The framework happens to be useful too — it's a fast, single-binary HTTP server with the Sinatra DSL most Rubyists already know — but the point of the project is to shake bugs out of Spinel's codegen, file PRs upstream, and grow Spinel's coverage of idiomatic Ruby. If something doesn't work here, the bug is usually in Spinel, and that's interesting.
# 1. install Spinel
git clone https://github.com/matz/spinel
cd spinel && make all
export PATH="$PWD:$PATH"
# 2. install Tep
git clone https://github.com/OriPekelman/tep
cd tep && make
./examples/hello -p 4567Your own app:
# hello.rb
require 'sinatra'
get '/' do
"hello from Tep"
end
get '/hi/:name' do
"hi, " + params[:name] + "!"
endtep build hello.rb # -> ./hello (~80 KB binary, no Ruby runtime)
./hello -p 4567The translator (bin/tep) needs CRuby >= 3.4 — Prism ships with
the stdlib from 3.4 onward. Recommended Ruby manager:
rv — fast version+gem
manager from the Spinel Cooperative (separate project from the
matz/spinel AOT compiler Tep compiles through; same Ruby
neighbourhood). .ruby-version in this repo pins 3.4.0; rv shell makes rv run rake test just work. Build deps on Linux:
build-essential, libsqlite3-dev. macOS: Xcode CLI tools.
For a full walkthrough — auth, persistence, deploy — see the Getting started wiki page.
Yes. ~150k req/s on a small Linux server with the request path doing
actual work (SQLite SELECT + JSON), microsecond median latency. The
hot path is C from end to end (epoll, request parsing, dispatch,
response writer); the Ruby you wrote is compiled, not interpreted.
HTTP/1.1 keep-alive and prefork with SO_REUSEPORT are the usual
wins applied.
Two scenarios, both wrk -t8 -c256 -d10s on Linux 6.x / aarch64,
8 workers a side (Sinatra: 8 workers × 4 threads):
| Scenario | Server | Req/sec | p50 | p99 |
|---|---|---|---|---|
| hello (raw plumbing) | Tep | 167,150 | 40 µs | <1 ms |
| hello | Sinatra + Puma | 31,184 | 40 ms | 171 ms |
| api (SQLite + JSON) | Tep | 145,290 | 43 µs | 243 µs |
| api | Sinatra + Puma | 24,926 | 1.8 ms | 171 ms |
Numbers are conservative floors; a clean re-run on a quiet host is
expected to come in higher. Reproduce with bench/run_all.sh.
macOS note. Linux is Tep's primary deployment target. Builds and runs on macOS too, but Darwin's
SO_REUSEPORTdoesn't load- balance new connections across prefork workers — a single long- running response on the busy worker blocks every other request on the same listener. On Linux 3.9+ the kernel distributes accepts correctly, so prefork scales as the table above.
Sinatra-shaped routing, filters, responses, templates, sessions, plus a collection of "batteries" — pure-Tep modules that cover the gem ecosystem's most common needs in a way that lowers cleanly through Spinel.
| Battery | What it covers |
|---|---|
Tep::SQLite |
libsqlite3 wrapper via a small C shim — exec / prepare / bind / step / col / first_str / first_int. |
Tep::Json |
encode primitives + flat-key decoder for JSON-over-HTTP. |
Tep::Logger |
levelled logger (debug/info/warn/error), stderr or file. |
Tep::Jwt |
HS256 JWT encode / verify / decode. |
Tep::Password |
PBKDF2-SHA256, 200k iters, self-describing storage. |
Tep::Security |
Cors (before-filter) + Headers (HSTS, nosniff, ...). |
Tep::Assets |
compile-time bundling for <app>/assets/*. |
Tep::Scheduler |
cooperative fiber scheduler with timer + I/O parking. |
Tep::Shell |
popen-based shell-out + small-file reader (/proc, /sys, /etc). |
Tep::Http |
Faraday-shaped outbound HTTP/1.0 client. |
Tep::Parallel |
grosser/parallel-shaped fork fan-out. |
Tep::Job |
sidekiq-shaped queue over SQLite. |
Per-battery API docs and cookbooks live on the wiki. The full Sinatra-compatibility matrix is in SINATRA_COMPAT.md.
~180 tests pass make test. 9 real-world test apps build and serve
end-to-end (smoke-tested through Net::HTTP), and the bundled
examples/gx10_dashboard/ — an
operator dashboard for an NVIDIA GB10 server — exercises every
public Tep feature in ~640 lines.
tep build accepts either the Sinatra DSL or a lower-level
Tep-class style without the translator:
require_relative '../lib/tep'
class Hi < Tep::Handler
def handle(req, res)
"<p>hi, " + req.params["name"] + "!</p>"
end
end
Tep.get "/hi/:name", Hi.new
Tep.run!(4567, 1, false)Useful for tracing where the translator's textual rewrites go.
Tep deliberately exists to find Spinel's edges. If you hit a Sinatra idiom that doesn't translate, a Spinel-emitted miscompile, a runtime hang — please file an issue with a minimal reproduction. "Your app doesn't build" is a useful data point.
MIT, see LICENSE.
