Alpha Version: You are viewing the ALPHA documentation. This is an experimental version and may contain breaking changes.
Skip to main content

Run and deploy your application

The same application code runs in a single local process and on AWS. Develop against the local platform — it needs no cloud account, starts in seconds, and behaves like production — then deploy the identical plugins.

The entry point

A platform root is short. It picks a platform, builds the plugins over it, and starts the servers:

module Platform = ReventlessLocal.Platform.Make()

module Catalog = CatalogPlugin.Plugin.Make(Platform)
module Ordering = OrderingPlugin.Plugin.Make(Platform)

Platform.makePlatform(
~version=Reventless.PackageVersion.fromCwd(),
~plugins=[module(Catalog), module(Ordering)],
)

Platform.startServers()

Swapping ReventlessLocal.Platform for ReventlessAws.Platform is what moves the same plugins to AWS — see Deploying to AWS below.

Running it

pnpm run build      # compile; re-run after source changes
pnpm run serve # start the platform

Add GRAPHQL_DEBUG=1 and MCP_DEBUG=1 (or use a dev script that sets them) to log every request. For a watch loop, run the ReScript compiler in watch mode alongside the server so a saved file restarts it.

Ports

The local platform serves the domain API and the platform/admin API separately by default:

ServiceDefault port
GraphQL — domain (your plugins)4000
GraphQL — platform/admin4001
MCP — domain3001
MCP — platform/admin3002

Override with REVENTLESS_DOMAIN_PORT, REVENTLESS_PLATFORM_PORT, REVENTLESS_DOMAIN_MCP_PORT, and REVENTLESS_PLATFORM_MCP_PORT.

Split versus unified API

Splitting the two APIs keeps administrative operations (activating plugins, cloning) off the endpoint your application clients talk to. It is worth having because it gives you a boundary to restrict later, and because an AI assistant pointed at the domain endpoint sees only domain tools rather than platform administration.

To serve everything from one endpoint instead:

module Platform = ReventlessLocal.Platform.MakeWithConfig({
let silent = false
let splitApi = false
})

In unified mode all schema is served from port 4000, and all MCP tools from 3001. On AWS, split mode is the default and provisions a dedicated admin API.

Storage backends

The local platform keeps state in memory by default, which is what you want for tests — every run starts clean. For development, a SQLite file lets data survive restarts:

REVENTLESS_LOCAL_BACKEND=memory                          # nothing persisted
REVENTLESS_LOCAL_BACKEND=sqlite:./.reventless/local.db # persistent
REVENTLESS_LOCAL_BACKEND='sqlite:./.reventless/local.db?reset' # wiped on start

or pick it in code with Backend.Memory / Backend.Sqlite({path, resetOnStart}) via MakeWithConfig. The platform logs which backend it chose at startup. See local persistence for the on-disk format — it is an ordinary SQLite file you can open with sqlite3 and inspect.

Committing a small local.db as a fixture is a reasonable way to share a deterministic demo dataset with collaborators.

Users and authorization locally

Reventless is authenticated by default: commands and queries expect a caller. Locally, users come from a YAML file at .reventless/users.yaml — gitignored, and seeded from a committed users.example.yaml template:

- username: admin
password: admin
groups: [Admin, Shopper]
userId: local-admin

- username: shopper
password: shopper
groups: [Shopper]
userId: local-shopper

Each entry needs username, password, and groups (an empty list means unprivileged); userId defaults to the username. The file is read relative to the process working directory at startup, so restart the server after editing it.

pnpm run seed reads the same file — it offers these accounts as the login to seed as, in the order they appear with the first as the default, and takes the password from the entry. A deployed platform keeps a .reventless/users.yaml too, recording the accounts created in its Cognito pool, and the seed offers those the same way.

Sign in through the shell's login page to exercise group-based rules. Give yourself one account per role you have defined — the useful test is not "does admin work" but "does each role see exactly its own surfaces".

Two shortcuts exist for convenience, and it is worth knowing which is which:

  • A request with no X-User header falls back to an unprivileged defaultUser, so casual local browsing works without logging in.
  • A request with an X-User: admin header is treated as that user, which is what makes curl testing practical.

Neither exists on AWS, where Cognito issues the identity. To test what an administrator sees, log in as one rather than granting the fallback user extra groups — otherwise you are testing a configuration that will never be deployed.

Exploring the API

Open http://localhost:4000/graphql in a browser for the GraphiQL explorer: it lists every command as a mutation, every view as a query and a subscription, with fields prefixed per plugin (Catalog_…, Ordering_…). It is the fastest way to discover the exact field names your specs produced.

From the command line:

curl http://localhost:4000/graphql \
-H 'content-type: application/json' \
-H 'X-User: admin' \
-d '{"query":"mutation { Catalog_AddCategory(categoryId: \"books\", name: \"Books\") { __typename } }"}'

Command mutations return a result union, so they need a selection set; view queries return a Relay-style connection (edges { node { … } }). See the GraphQL API guide for the full shape.

Your application also exposes the same commands and views over MCP, so an AI assistant can drive the running application through the same authorization rules a person gets.

Seeding data

Rather than clicking through the UI after every reset, drive your own seed through the public command API — the same door a client uses, so seeded data goes through the same validation and produces the same events. See the seeding guide.

Deploying to AWS

When you are ready for the cloud, the application code does not change — only the platform module and a deployment package around it: