Install
Full setup for every language, the complete standard library, and worked end-to-end examples: stof.dev
Quickstart
Every JSON document you have is already a Stof document. Start with the data you have, add logic where you need it, and export back to JSON - or YAML, or TOML - any time.
import { stofAsync } from '@formata/stof';
const doc = await stofAsync`
{
"name": "Stof"
"hello": ():str => \`Hello, \${self.name}!\`
}`;
console.log(await doc.call('hello')); // Hello, Stof!
import { StofDoc } from '@formata/stof';
const doc = await StofDoc.parse({
first: 'John',
last: 'Doe',
domain: 'example.com'
});
doc.parse(`
fn fill(domain: str = 'example.com') {
self.domain = domain;
self.email = \`\${self.first.lower()}.\${self.last.lower()}@\${self.domain}\`;
}`);
await doc.call('fill', 'stof.dev');
console.log(doc.record());
/*
{
first: 'John',
last: 'Doe',
domain: 'stof.dev',
email: 'john.doe@stof.dev',
}
*/
What Stof Is
Stof is a data runtime — not a data format, not a programming language, not a database.
The same way a JavaScript runtime executes JavaScript, a data runtime executes data. A Stof document doesn't just describe something; it validates itself, transforms itself, and acts, wherever the runtime lands.
Concretely: valid JSON is already valid Stof. You're not migrating to a new format — you're handing your existing data a place to keep the logic that used to live somewhere else.
Why it's different
- Data and logic belong together. Splitting them across a data file and a separate codebase creates schema drift, versioning hell, and is a limitation for modern distributed systems. Stof documents carry both, as one unit.
- Portable means portable. The same document runs native, in WebAssembly, or embedded in another host, with the same behavior every time. If it doesn't run everywhere, it isn't portable — it's just convenient sometimes.
- Sandboxed by default. A document can only see and manipulate itself unless you explicitly hand it more. Logic that arrives over the wire has to be safe to run without a review cycle first.
- Documents should be able to grow. A running document parsing new fields, new types, even new functions into itself isn't an edge case — it's the model working as intended. Data that can only be replaced, never extended, is still just a snapshot.
- Interop with all data formats. A superset of JSON that is compatible with every other data format, even ones that don't exist yet — binary, images, PDFs, DOCX, YAML, TOML, JSON, etc.
Where This Came From
Stof comes out of a decade spent building parametric and geometry formats for CAD and graphics systems. That's where the core architectural insights come from: represent everything as a flat graph of nodes and data components (ECS + DAG), connected by pointers rather than nested copies, and moving or transforming part of the document stops being expensive. Everything — fields, functions, PDFs, images, binaries — become data components on a graph of nodes. Allow function components to manipulate the graph, and you have Stof.
Stof started as a solo project while trying to send wasm over the wire, and it's been running in production since — including as the policy engine underneath Limitr, where every plan, credit limit, and validation rule is a live Stof document.
Learn More
- stof.dev — install docs, the full standard library, and examples
- Discord — talk to the team and community
- GitHub Issues — bugs and feature requests
License
Apache 2.0. See LICENSE for details.