whoosh 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +61 -46
- data/lib/whoosh/app.rb +3 -2
- data/lib/whoosh/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7276060a2fd0b8ef3edc620c087767b92de6684e6ef54a130f5c3b45f5468a52
|
|
4
|
+
data.tar.gz: 98f9f219bbc2e01af9ba63803a525d100fcd70adf397248065db796b28319ae4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a12f5a8f431ff694382192e046072596beeaced996cf47e19633ea3974ebd7bae17bc079118b28cc08b019282219d0ff4cf263291eed418a38d2f32a4021d38a
|
|
7
|
+
data.tar.gz: 76c4e804ea925a037ecd60fcc02834800fb0d2ec000d9fdfb48a7a142625c32e37dd3f8bdbf56bae934f1a6ee46a3731a1f0db06cddac660630c4f14bdccd579
|
data/README.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<h1 align="center">Whoosh</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>
|
|
9
|
-
|
|
8
|
+
<strong>The fastest way to ship a production MCP server in Ruby.</strong><br>
|
|
9
|
+
A FastAPI-style framework with MCP, schema validation, auth, streaming, and OpenAPI — built in.
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -14,18 +14,29 @@
|
|
|
14
14
|
<img src="https://img.shields.io/badge/rack-3.0-blue" alt="Rack">
|
|
15
15
|
<img src="https://img.shields.io/badge/license-MIT-green" alt="License">
|
|
16
16
|
<img src="https://img.shields.io/badge/tests-659%20passing-brightgreen" alt="Tests">
|
|
17
|
-
<img src="https://img.shields.io/badge/
|
|
17
|
+
<img src="https://img.shields.io/badge/stability-evolving-yellow" alt="Stability">
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
22
|
## Why Whoosh?
|
|
23
23
|
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **Batteries included** —
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
24
|
+
- **MCP-native** — opt routes into MCP with `mcp: true` and they become typed tools over stdio/SSE. No glue code, no separate server.
|
|
25
|
+
- **FastAPI-style DSL in Ruby** — declarative schemas, typed request/response, auto-generated OpenAPI + Swagger UI, dependency injection.
|
|
26
|
+
- **Batteries included** — auth (JWT, API key, OAuth), rate limiting, caching, background jobs, file uploads, vector search, streaming, pagination.
|
|
27
|
+
- **Agent-friendly** — `whoosh describe` emits a JSON snapshot of your app; generated `CLAUDE.md` so coding agents understand it; `whoosh check` validates config before runtime.
|
|
28
|
+
- **Competitive Ruby performance** — YJIT + Falcon fibers + Oj, ~2.5µs framework overhead. See [Performance](#performance) for honest, per-core comparisons.
|
|
29
|
+
|
|
30
|
+
## When NOT to use Whoosh
|
|
31
|
+
|
|
32
|
+
Whoosh is on 1.x but still evolving — solo-maintained, without a production track record yet, and breaking changes ship occasionally (always called out in `CHANGELOG.md`). Reach for something else when:
|
|
33
|
+
|
|
34
|
+
- **You need a managed backend.** Supabase, PocketBase, or Firebase give you DB + auth + realtime without hosting a framework. Whoosh is the app layer — use it *with* a managed DB if that fits.
|
|
35
|
+
- **You want maximum ecosystem depth.** Rails has more gems; FastAPI has the Python ML/AI library ecosystem (PyTorch, transformers, LangChain). If your core workload lives in those libraries, stay where they are.
|
|
36
|
+
- **You need a frozen API surface.** Being on 1.x doesn't mean the API is locked — breaking changes still ship when the design calls for it. If you need strict stability contracts today, wait a few releases.
|
|
37
|
+
- **Your team has no Ruby experience** and the project isn't specifically about AI/MCP. Hiring and ecosystem gravity usually beat framework features.
|
|
38
|
+
|
|
39
|
+
Whoosh's sweet spot: Ruby shops (or Ruby-curious teams) building AI / LLM / MCP-backed APIs who want typed schemas, OpenAPI, and MCP without wiring three libraries together.
|
|
29
40
|
|
|
30
41
|
## Install
|
|
31
42
|
|
|
@@ -192,21 +203,24 @@ end
|
|
|
192
203
|
|
|
193
204
|
### MCP (Model Context Protocol)
|
|
194
205
|
|
|
195
|
-
**
|
|
206
|
+
**Routes are exposed as MCP tools only when you opt in with `mcp: true`.** This prevents internal or admin endpoints from being callable as tools by accident.
|
|
196
207
|
|
|
197
208
|
```ruby
|
|
198
|
-
#
|
|
199
|
-
app.post "/summarize", request: SummarizeRequest do |req|
|
|
209
|
+
# Opt in per route:
|
|
210
|
+
app.post "/summarize", request: SummarizeRequest, mcp: true do |req|
|
|
200
211
|
{ summary: llm.summarize(req.body[:text]) }
|
|
201
212
|
end
|
|
202
213
|
|
|
203
|
-
|
|
204
|
-
|
|
214
|
+
# Or opt in a whole group:
|
|
215
|
+
app.group "/tools", mcp: true do
|
|
216
|
+
post "/translate" do |req|
|
|
217
|
+
{ result: translate(req.body["text"]) }
|
|
218
|
+
end
|
|
205
219
|
end
|
|
206
220
|
|
|
207
|
-
#
|
|
208
|
-
app.get "/internal"
|
|
209
|
-
{ debug: "not exposed
|
|
221
|
+
# Default: not exposed as an MCP tool.
|
|
222
|
+
app.get "/internal" do
|
|
223
|
+
{ debug: "not exposed" }
|
|
210
224
|
end
|
|
211
225
|
```
|
|
212
226
|
|
|
@@ -440,55 +454,56 @@ whoosh check # validates config, auth, dependencies
|
|
|
440
454
|
|
|
441
455
|
## Performance
|
|
442
456
|
|
|
443
|
-
|
|
457
|
+
> Apple Silicon arm64, 12 cores. Ruby 3.4 + YJIT. [Full benchmark suite & reproduction steps](benchmarks/comparison/)
|
|
458
|
+
>
|
|
459
|
+
> **How to read these numbers.** Benchmarks are selective by nature. A `GET /health` returning `{"status":"ok"}` tests the router + serializer, not your real app. A Postgres read tests one query pattern. We show single-process (per-core) numbers first because that's the fair cross-language comparison. Multi-worker numbers are included for deployment sizing, but scaling strategies differ per runtime (Node uses `cluster`, Python uses multiple workers, Ruby uses workers × threads or fibers) and mixing them isn't apples-to-apples.
|
|
444
460
|
|
|
445
|
-
|
|
461
|
+
### HTTP micro-benchmark — `GET /health`
|
|
446
462
|
|
|
447
|
-
**Single process
|
|
463
|
+
**Single process (per-core, fair comparison):**
|
|
448
464
|
|
|
449
465
|
| Framework | Language | Server | Req/sec |
|
|
450
466
|
|-----------|----------|--------|---------|
|
|
451
467
|
| Fastify | Node.js 22 | built-in | 69,200 |
|
|
452
|
-
| **Whoosh** | Ruby 3.4 +YJIT |
|
|
468
|
+
| **Whoosh** | Ruby 3.4 +YJIT | Falcon | **24,400** |
|
|
453
469
|
| **Whoosh** | Ruby 3.4 +YJIT | Puma (5 threads) | **15,500** |
|
|
454
470
|
| FastAPI | Python 3.13 | uvicorn | 8,900 |
|
|
455
471
|
| Sinatra | Ruby 3.4 | Puma (5 threads) | 7,100 |
|
|
456
|
-
| PHP (raw) | PHP 8.5 | built-in | 2,000 |
|
|
457
472
|
|
|
458
|
-
|
|
473
|
+
On this microbenchmark, Fastify is ~2.8× Whoosh+Falcon per-core; that's the honest picture for trivial JSON. Against other Ruby frameworks and against FastAPI on CPython, Whoosh is competitive.
|
|
459
474
|
|
|
460
|
-
**Multi-worker
|
|
475
|
+
**Multi-worker (sizing reference, not apples-to-apples):**
|
|
461
476
|
|
|
462
|
-
| Framework |
|
|
463
|
-
|
|
464
|
-
|
|
|
465
|
-
| Fastify |
|
|
466
|
-
|
|
|
467
|
-
| Roda |
|
|
477
|
+
| Framework | Server | Req/sec |
|
|
478
|
+
|-----------|--------|---------|
|
|
479
|
+
| Whoosh | Falcon (4 workers) | 87,400 |
|
|
480
|
+
| Fastify | built-in (single thread, no cluster) | 69,200 |
|
|
481
|
+
| Whoosh | Puma (4w × 4t) | 52,500 |
|
|
482
|
+
| Roda | Puma (4w × 4t) | 14,700 |
|
|
468
483
|
|
|
469
|
-
|
|
484
|
+
Fastify was not run under `cluster`; don't read this table as "Whoosh beats Fastify." Read it as "Whoosh on 4 cores handles ~87K req/s on trivial JSON."
|
|
470
485
|
|
|
471
|
-
### Real-
|
|
486
|
+
### Real-world benchmark — `GET /users/:id` from PostgreSQL (1000-row table)
|
|
472
487
|
|
|
473
|
-
**Single process:**
|
|
488
|
+
**Single process (per-core):**
|
|
474
489
|
|
|
475
|
-
| Framework |
|
|
476
|
-
|
|
477
|
-
| Fastify + pg |
|
|
478
|
-
| **Whoosh + Falcon (fiber PG pool)** |
|
|
479
|
-
|
|
|
480
|
-
| Roda + Puma |
|
|
481
|
-
| Sinatra + Puma |
|
|
482
|
-
| FastAPI + uvicorn |
|
|
490
|
+
| Framework | Req/sec |
|
|
491
|
+
|-----------|---------|
|
|
492
|
+
| Fastify + pg | 36,900 |
|
|
493
|
+
| **Whoosh + Falcon (fiber PG pool)** | **13,400** |
|
|
494
|
+
| Whoosh + Puma (Sequel) | 8,600 |
|
|
495
|
+
| Roda + Puma | 6,700 |
|
|
496
|
+
| Sinatra + Puma | 4,400 |
|
|
497
|
+
| FastAPI + uvicorn | 2,400 |
|
|
483
498
|
|
|
484
|
-
|
|
499
|
+
On realistic DB-bound work, Whoosh's fiber-aware PG pool closes a lot of the gap vs Fastify (~2.75×) and has a wide lead over FastAPI on CPython and over other Ruby frameworks.
|
|
485
500
|
|
|
486
|
-
|
|
487
|
-
|-----------|----------|---------|
|
|
488
|
-
| **Whoosh + Falcon (4 workers, fiber PG pool)** | Ruby 3.4 +YJIT | **45,900** |
|
|
489
|
-
| Fastify (single thread) | Node.js 22 | 36,900 |
|
|
501
|
+
**Multi-worker (sizing reference):**
|
|
490
502
|
|
|
491
|
-
|
|
503
|
+
| Framework | Req/sec |
|
|
504
|
+
|-----------|---------|
|
|
505
|
+
| Whoosh + Falcon (4 workers, fiber PG pool) | 45,900 |
|
|
506
|
+
| Fastify (single thread) | 36,900 |
|
|
492
507
|
|
|
493
508
|
### Micro-benchmarks
|
|
494
509
|
|
data/lib/whoosh/app.rb
CHANGED
|
@@ -493,8 +493,9 @@ module Whoosh
|
|
|
493
493
|
internal_paths = %w[/openapi.json /docs /redoc /metrics /healthz]
|
|
494
494
|
|
|
495
495
|
@router.routes.each do |route|
|
|
496
|
-
#
|
|
497
|
-
|
|
496
|
+
# Routes are exposed as MCP tools only when opted in with mcp: true.
|
|
497
|
+
# Why: default-expose leaks internal/admin endpoints as callable tools.
|
|
498
|
+
next unless route[:metadata] && route[:metadata][:mcp] == true
|
|
498
499
|
next if internal_paths.include?(route[:path])
|
|
499
500
|
|
|
500
501
|
tool_name = "#{route[:method]} #{route[:path]}"
|
data/lib/whoosh/version.rb
CHANGED