Guide

A tool is a folder.

Index.html, its styles and scripts, and one small xevol.json. Upload it, and it runs inside the Workspace that installed it, with the records it keeps and the calls its owner approved. Nothing else reaches it: no network, no session of its own, no server of yours.

The folder

A tool is a folder of static files. index.html is its front door, and a tool that moves between its own addresses gets index.html for every path, so a single-page app works as it is.

my-tool/
  index.html
  app.js
  style.css
  xevol.json

No build step is needed: what you upload is what runs. Everything the page loads must be in the folder. A library fetched from a CDN at run time will not load, because a tool has no network of its own.

What it asks for

xevol.json is read when the folder is imported. It names the records the tool keeps and the Xevol actions it calls. Both are optional, and neither is ever widened by a later version without the Workspace's owner approving it again.

{
  "grants": ["leads.read"],
  "data": {
    "notes": {
      "fields": { "body": "text", "pinned": "bool?" }
    }
  }
}

Fields

  • grants: the actions it calls, from the list Xevol publishes. A tool that keeps only its own records needs none.
  • data: the collections it keeps, each with its fields. A field's type is text, number, bool, date or json; a ? makes it optional.
  • mine: true on a collection keeps each person's records to themselves; without it the Workspace shares them.

Calling Xevol

index.html loads the bridge script the code host serves. After that, xevol.call is the only way the page reaches its Workspace: it asks Xevol's page, which checks the approval and runs the call as the person using the tool.

<script src="/__xevol/bridge.js"></script>
<script>
  // What this installed copy may call, so the page can hide what is not there.
  const actions = await xevol.catalog();

  // One call. It rejects with the plain reason when the owner has not approved it.
  const leads = await xevol.call("leads.list", {});
</script>

A call that spends credits, sends something outside Xevol, or only a person may make asks the person first: Xevol shows a dialog naming what the call will do, and runs it only if they agree. Nothing the tool can do skips that.

Records

Records live in Xevol, not in the browser, so they are the same on every device and for every person in the Workspace. Five calls, all named in the collection's own shape: data.list, data.get, data.create, data.update and data.delete.

// Create: the fields the collection declares, and nothing else.
await xevol.call("data.create", {
  collection: "notes",
  data: { body: "The boiler is booked for Tuesday." },
});

// Update: name the revision you read, so two people never overwrite each other.
const notes = await xevol.call("data.list", { collection: "notes" });
const first = notes.records[0];
await xevol.call("data.update", {
  collection: "notes",
  id: first.id,
  revision: first.revision,
  data: { pinned: true },
});

// Delete: its own call, never a side effect of an update.
await xevol.call("data.delete", { collection: "notes", id: first.id });

A Workspace holds up to 10,000 records per collection and 32 KB per record, with ten collections per tool. Reading after the last page takes the cursor data.list answers with.

Live updates

xevol.onChange calls your function when the tool's records change, from any device and any person, so a list stays current without polling. The event names the collection, or null when everything should be read again.

xevol.onChange(({ collection }) => {
  if (collection === null || collection === "notes") void load();
});
// It returns the unsubscribe.

Check before you upload

The Xevol CLI reads the folder and names anything a tool cannot do, before a byte is sent. It makes no network calls, so it is safe on any folder.

npm install -g xevol
xevol login
xevol structify . --check --name my-tool --license NOASSERTION

It refuses a script, stylesheet, image or font loaded from another site, a fetch, WebSocket or EventSource that reaches one, and code imported straight from a URL such as esm.sh or unpkg. It warns about what is closed inside a tool, such as a form that submits. Copy the file into the folder instead, and it passes.

Upload and publish

In Xevol, a Workspace's owner adds an app by dropping its folder on Add an app. It is checked as it is read, uploaded, and published at an address of its own, which is fixed from then on. The owner approves what it asks for as they import it, because the list is on the screen.

xevol structify ./my-tool --name my-tool --license NOASSERTION --publish \
  --workspace-id <the Workspace's id>

Running it again with a changed folder makes a new version and publishes it. A version that asks for nothing new, no new action and no new collection, keeps working everywhere it is installed. One that asks for more is held until each Workspace's owner approves it again.

List it in the store

In Settings, on the Store page, List an app offers the Workspace's own tools. Fill in a name, a summary and an address, and it is sent to Xevol for review. An approved listing shows in the store, free to install, and an install asks the installing owner to approve the same list of actions and collections.

Publishing an app does not list it: anyone with its address can open it, signed in or not. The list is what a person goes through to find it, and what makes it installable in one step.

What a tool cannot do

By design

  • It has no network of its own, and no session: calls run as the person using it.
  • A form never submits inside a tool's frame; send the data with xevol.call instead.
  • It reads no other tool's records, and nothing outside the Workspace that installed it.
  • A call it was not approved for is refused, with a plain reason the page can show.

A tool that reads private records and sends out in one go cannot be approved from a tool's page. Split it into two tools.

Shipping a new version

Every version is sealed and kept

  • A new field may be added to a collection; an existing field's type never changes.
  • The address stays the same, and so does everything saved under it.
  • Going back to an earlier version is a publish of it, not an undo of anyone's records.

The Notes example in Xevol's repository is a working tool of about 150 lines: the page, its script and xevol.json, using all five record calls and the live updates.