# grange — a document database your agent can pay for grange is an agent-first document DB: one static binary (MFL/machin), crash-safe WAL, secondary indexes with O(1) count/sum/avg registers, sorted range indexes. Faster than SQLite on every indexed workload at 100k docs (see the README bench). OSS: https://github.com/javimosch/grange · This hosted instance: https://grange.intrane.fr READ REPLICA: https://read.grange.intrane.fr — same tokens, GET routes only (find/count/agg/get/watch/export); point read-heavy agents here, writes go to the primary ## Get a namespace (self-serve; a peage wallet is the only credential) 1. mint a wallet: curl -s -X POST https://peage.intrane.fr/v1/wallets -d '{}' (save the pw_ token; docs: https://peage.intrane.fr/llms.txt) 2. fund it: curl -s -X POST https://peage.intrane.fr/v1/topup -H 'Authorization: Bearer pw_...' -d '{"amount_cents":500}' -> a Stripe URL your human pays once 3. sign up here: curl -s -X POST https://grange.intrane.fr/tenants -H 'X-Peage-Wallet: pw_...' -d '{"name":"my agent"}' -> {"tenant":"t...","token":"gt_..."} (save the gt_ token — it is your database) ## Pricing (pay-as-you-go, no subscription) Storage: 15 cents/GB/month, first 50 MB free. Accrued continuously (price/30 per GB-day), charged to your wallet via peage when >= 5 cents. Queries and writes are free under FAIR USE: 300 requests/min and 5 concurrent watchers per tenant; beyond that you get 429 {type:rate-limited, retry_after_seconds} — back off and retry. Throttling is recorded; your GET /usage shows throttled_requests_total. ## The API (Authorization: Bearer gt_... on every call; ?coll= picks a collection, default 'default') PUT curl -s -X POST $G/put -H "$A" -d '{"coll":"leads","id":"l1","doc":{"co":"acme","score":9}}' # id optional (auto) GET curl -s "$G/get?coll=leads&id=l1" -H "$A" DEL curl -s -X POST $G/del -H "$A" -d '{"coll":"leads","id":"l1"}' FIND curl -s "$G/find?coll=leads&where=score%3E%3D5&limit=50" -H "$A" # where: f=v,f2>v2 (AND; = > < >= <= ~=contains; f=a|b = ANY of; dotted paths reach nested) ORDER curl -s "$G/find?coll=events&order=ts&desc=1&limit=5" -H "$A" # THE LATEST N. Needs a range index on the field (INDEX with kind:"range"); without an order, a limit returns an ARBITRARY subset. Ties break by id, so the order is total. PAGE curl -s "$G/find?coll=events&order=ts&desc=1&limit=5&after=" -H "$A" # KEYSET paging: the response carries next; pass it back. Every page costs the same, and rows added/removed elsewhere cannot make one repeat or be skipped. next is omitted on a short page = stop. (A row MODIFIED between pages moves in the order and can be seen twice or missed.) COUNT curl -s "$G/count?coll=leads&where=co%3Dacme" -H "$A" # O(1) if the field is indexed INDEX curl -s -X POST $G/index -H "$A" -d '{"coll":"leads","field":"co","sums":"score"}' # kind:"range" for > < >= <= AGG curl -s "$G/agg?coll=leads&group-by=co&sum=score" -H "$A" # count/sum/avg O(1) via registers; minmax= scans BULK curl -s -X POST "$G/bulk?coll=leads" -H "$A" --data-binary @ops.ndjson # one line per op: '{...}' put auto-id · 'id{...}' put · '-id' del; ONE commit, all-or-nothing; 263k docs/s measured; on a COLD collection a large batch is written straight to its own run, so ingest never stages through memory COLLS curl -s $G/collections -H "$A" DBS curl -s $G/dbs -H "$A" # tenants have MULTIPLE databases: pass db= on any call (default 'default') STATS curl -s "$G/stats?coll=leads" -H "$A" COMPACT curl -s -X POST $G/compact -H "$A" -d '{"coll":"leads"}' # fold WAL chunks; run when stats.chunks_replayed grows COLD curl -s -X POST "$G/cold?coll=archive" -H "$A" # DISK-RESIDENT mode: data lives in page files, not RAM; point gets read one page; scans stream; SECONDARY INDEXES supported (index --field, 15x faster selective lookups); no TTL on cold; irreversible per collection WATCH curl -s "$G/watch?coll=jobs&since=0&timeout=25" -H "$A" # LONG-POLL: blocks until the collection changes past since -> {seq,changes:[{seq,op,id}]}; poll again with the returned seq EXPORT curl -s "$G/export?coll=leads&format=lines" -H "$A" # NDJSON 'iddoc' lines — pipe straight into /bulk of another grange VERIFY curl -s "$G/verify?coll=leads" -H "$A" # integrity check: every file's checksum + record stream, cold manifests vs their pages, declared indexes present and agreeing with the data -> {intact,files_checked,issues[]} USAGE curl -s $G/usage -H "$A" HEALTH curl -s $G/health # open, no auth Docs are single-line minified JSON. Errors: {"ok":false,"error":{type,message}} with proper HTTP codes. ## Every answer tells you what it cost Query responses carry the plan and its cost: {"mode":"cold-index-multi","scanned":0,"pages":30,...}. mode starting with `scan` means a missing index, not a slow database — declare one and it becomes `indexed`/`range`/`cold-index`. Measured on 200k docs: an indexed lookup reads 2 pages where the same question as a scan reads 256 and examines 50,000 documents. A query that would scan too far is refused with an error naming the field to index, rather than holding the server for everyone. ## Client SDKs (same surface everywhere: db(...).coll(...) scoping + put/get/find/count/agg/index/put_many) Python pip install grange-db from grange_db import Grange leads = Grange(url, token).db('crm').coll('leads') leads.put({'co':'acme'}); leads.count('co=acme'); leads.put_many([{...},...]) Node npm install grange-db (>=18, zero deps) const { Grange } = require('grange-db') const leads = new Grange({url, token}).db('crm').coll('leads') await leads.put({co:'acme'}); await leads.count('co=acme'); await leads.putMany([...]) Go go get github.com/javimosch/grange/sdk/go leads := grange.New(url, token).DB("crm").Coll("leads") id, _ := leads.Put("", map[string]any{"co":"acme"}); n, _ := leads.Count("co=acme") machin compose sdk/machin/grange_client.src (https://github.com/javimosch/grange) g := grange_client(url, token) g = grange_use(g, "crm", "leads") id, ok := grange_put(g, "", doc) n := grange_count(g, "co=acme") Durability: every write request is one immutable checksummed WAL chunk, FSYNCED before the write is acknowledged — the chunk's content, then the directory entry that makes it exist. So an acknowledged write survives a power cut, not merely a process crash. (Not provable from here: that the underlying DEVICE honours fsync.) A read on the replica after an acknowledged write sees that write. Your data is exportable at any time and REPLICABLE: `grange follow --from https://grange.intrane.fr --rtoken gt_... --db ./local` pulls a live local replica over the /watch feed (resync via export, then deltas). The engine is OSS — self-host the same binary if you outgrow the hosted rail.