Most frameworks treat an application as an object: a thing bound to a runtime, wired to HTTP, to a server, to one place it can live. Change the place and you rewrite the thing. Stone.js starts from the opposite idea. An application is not an object. It is an act.

An application is an act

An act has a shape: Application = Domain × Context → Resolution. Your domain is what your software means: its rules, its entities, its use cases. The context is everything the domain does not control: the runtime, the protocol, the shape of the input and the output. The resolution is the concrete response produced when the two meet, once per event.

The mistake the industry made was welding the domain to one context. Stone.js keeps them apart: you write the domain once, and the context applies to it, not the other way around.

Your domainwritten onceHTTPnode-httpServerlessaws-lambdaEdgefetch adapterCLInode-cliAgentmcp adapterBrowserbrowser adapter
Application = Domain × Context → Resolution. You write the domain once; every context applies to it at run time, one resolution per event.

Write the domain once

A handler in Stone.js names no runtime. It does not know whether the request came over HTTP, from the CLI, from the browser, or from an AI agent. It reads a normalised intention and returns a value.

app/TaskController.tsts
import { EventHandler, Get, Post } from '@stone-js/router'

@EventHandler('/tasks')
export class TaskController {
  constructor ({ tasks }) { this.tasks = tasks }

  @Get('/')
  list () { return this.tasks.all() }

  @Post('/')
  create (event) { return this.tasks.add(event.get('title')) }
}

There is no req, no res, no framework object tying this to a server. It is your domain, and nothing else.

The context collapses at run time

You add adapters to the manifest, one per platform, and they coexist: server, edge, browser, agents, stacked on the same domain. Nothing is chosen yet. When the app runs, Stone.js resolves exactly one, the contextual collapse. Deploy the same artifact to Node and it collapses to the Node adapter; deploy it to Lambda and it collapses to the Lambda one. The domain never changes.

That is what "your app exists in every runtime, until you run it" means when it is mechanical rather than marketing: one codebase, every runtime the industry has, and the ones it invents next arrive as a package someone writes, not a release you wait for.

Where to start

Read the documentation, which doubles as a short course in architecture, or scaffold a working app in one command from the starters. This blog will keep showing real cloud-native problems solved end to end, each with a diagram, the modules that carry it, and a starter you can run today.