Volter World

Read the vendor through a shared world

Keep a copy of the account in the team's world: refreshed on demand or on a schedule, read by every clone without a vendor call, and rebased when the vendor moves.

This page is executed as written by packages/cli/src/journeys/tutorials.test.ts; the recording is made from the same run.

The page, recorded

A root's log is the account's history as observed. A refresh asks the vendor what it holds and appends to that log only what changed; the twin says how often the vendor can be asked, and the world can say otherwise. Everything downstream reads the held copy: a clone answers from its tree, so a rate-limited API is read as often as you like. When the vendor moves under a changeset, rebase names the conflict by record and field.

Three worlds

GitHub itself is a world here, so the whole chain runs on one machine; the commands are the same when the root is https://api.github.com.

{ "name": "acme-web", "private": true, "type": "module", "dependencies": { "@octokit/rest": "^21" } }
import { Octokit } from '@octokit/rest';
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const { data: issues } = await octokit.issues.listForRepo({ owner: 'acme', repo: 'web', state: 'all' });
for (const issue of issues) console.log(`#${issue.number} ${issue.title}`);
import { Octokit } from '@octokit/rest';
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
await octokit.issues.update({ owner: 'acme', repo: 'web', issue_number: 1, title: process.argv[2] });
console.log('retitled #1');
npm install
npm install -g @volter/world
npm install -D @volter/twin-github
mkdir ../reality && cd ../reality && volter world init --bare acme/reality --twins github
volter world serve --port 4400 &
mkdir ../team && cd ../team && volter world init --bare acme/team --twins github
volter world serve --port 4300 &
cd ../acme-web

Wait for both worlds to announce that they are serving before continuing.

serving  acme/reality  http://127.0.0.1:4400/acme/reality
serving  acme/team  http://127.0.0.1:4300/acme/team
for i in $(seq 1 60); do [ -f ../reality/.volter/token ] && [ -f ../team/.volter/token ] && break; sleep 1; done; sleep 3
curl -s -X POST http://127.0.0.1:4400/acme/reality/github/repos/acme/web/issues -H "authorization: Bearer $(cat ../reality/.volter/token)" -H 'content-type: application/json' -d '{"title":"Launch checklist"}' | grep -o '"number":[0-9]*'
"number":1

Set the root, and how often it may be asked

--deploy hold keeps landed entries waiting until someone deploys; --at-most 30s is this world's word on how often the twin may be refreshed on demand (the twin has its own default).

cd ../team
volter twin github root http://127.0.0.1:4400/acme/reality/github --scope repos/acme/web --deploy hold --at-most 30s
cat ../reality/.volter/token | volter twin github credential
volter twin github refresh
volter twin github refresh
cd ../acme-web
github  refreshed
github  not refreshed: refreshed at ISO; the github twin refreshes at most every 30s (--force to refresh now)

A clone reads the copy

The app's world clones the team's, and its tree holds what the team observed. Stop the vendor, and the app still reads the issue: nothing here calls the vendor.

volter world init
volter world up --no-seed
volter world clone http://127.0.0.1:4300/acme/team --token "$(cat ../team/.volter/token)"
kill %1
volter world run -- node list-issues.mjs
#1 Launch checklist

When the vendor moves

Bring the vendor back and change the issue there. The team's world observes it on its next refresh. Meanwhile the app changed the same title, cut a changeset and pushes: the push refuses, because the team's world moved under it, and says what to do.

cd ../reality
volter world serve --port 4400 &

Wait for the restarted world to announce that it is serving:

serving  acme/reality  http://127.0.0.1:4400/acme/reality
cd ../acme-web
for i in $(seq 1 60); do curl -s http://127.0.0.1:4400/-/ping >/dev/null && break; sleep 1; done; sleep 2
curl -s -X PATCH http://127.0.0.1:4400/acme/reality/github/repos/acme/web/issues/1 -H "authorization: Bearer $(cat ../reality/.volter/token)" -H 'content-type: application/json' -d '{"title":"Launch checklist, final"}' | grep -o '"title":"[^"]*"'
cd ../team
volter twin github refresh --force
cd ../acme-web
volter world run -- node retitle.mjs "Launch checklist, draft"
volter world changeset -m "Retitle the checklist"
volter world push
"title":"Launch checklist, final"
github  refreshed  1 changed
retitled #1
changeset  retitle-the-checklist  1 change
origin moved: 1 change on github since your last fetch

fetch brings the moved base, and rebase replays the changeset over it, naming the record and the field where the two disagree. The rebased changeset has a new hash; a reviewer sees the conflict on it.

volter world fetch
volter world rebase retitle-the-checklist
volter world push
conflicts:
github issue:acme/web#issue:1 title set
pushed  retitle-the-checklist  1 change → origin

The entry waits on the team's world: the root says hold. Someone deploys it, and the vendor holds the app's title.

cd ../team
volter world deploy
volter world log --receipts
cd ../acme-web
curl -s http://127.0.0.1:4400/acme/reality/github/repos/acme/web/issues/1 -H "authorization: Bearer $(cat ../reality/.volter/token)" | grep -o '"title":"[^"]*"'
deployed  github  1 change
"title":"Launch checklist, draft"

Clean up

volter world down
kill $(jobs -p)

Playback

Each command above, as the recording shows it.

npm install

step 1

npm install -g @volter/world

step 2

npm install -D @volter/twin-github

step 3

mkdir ../reality && cd ../reality && volter world init --bare acme/reality --twins github

step 4

volter world serve --port 4400 &

step 5

mkdir ../team && cd ../team && volter world init --bare acme/team --twins github

step 6

volter world serve --port 4300 &

step 7

cd ../acme-web

step 8

for i in $(seq 1 60); do [ -f ../reality/.volter/token ] && [ -f ../team/.volter/token ] && break; sleep 1; done; sleep 3

step 9

curl -s -X POST http://127.0.0.1:4400/acme/reality/github/repos/acme/web/issues -H "authorization: Bearer $(cat ../reality/.volter/token)" -H 'content-type: application/json' -d '{"title":"Launch checklist"}' | grep -o '"number":[0-9]*'

step 10

cd ../team

step 11

volter twin github root http://127.0.0.1:4400/acme/reality/github --scope repos/acme/web --deploy hold --at-most 30s

step 12

cat ../reality/.volter/token | volter twin github credential

step 13

volter twin github refresh

step 14

volter twin github refresh

step 15

cd ../acme-web

step 16

volter world init

step 17

volter world up --no-seed

step 18

volter world clone http://127.0.0.1:4300/acme/team --token "$(cat ../team/.volter/token)"

step 19

kill %1

step 20

volter world run -- node list-issues.mjs

step 21

cd ../reality

step 22

volter world serve --port 4400 &

step 23

cd ../acme-web

step 24

for i in $(seq 1 60); do curl -s http://127.0.0.1:4400/-/ping >/dev/null && break; sleep 1; done; sleep 2

step 25

curl -s -X PATCH http://127.0.0.1:4400/acme/reality/github/repos/acme/web/issues/1 -H "authorization: Bearer $(cat ../reality/.volter/token)" -H 'content-type: application/json' -d '{"title":"Launch checklist, final"}' | grep -o '"title":"[^"]*"'

step 26

cd ../team

step 27

volter twin github refresh --force

step 28

cd ../acme-web

step 29

volter world run -- node retitle.mjs "Launch checklist, draft"

step 30

volter world changeset -m "Retitle the checklist"

step 31

volter world push

step 32

volter world fetch

step 33

volter world rebase retitle-the-checklist

step 34

volter world push

step 35

cd ../team

step 36

volter world deploy

step 37

volter world log --receipts

step 38

cd ../acme-web

step 39

curl -s http://127.0.0.1:4400/acme/reality/github/repos/acme/web/issues/1 -H "authorization: Bearer $(cat ../reality/.volter/token)" | grep -o '"title":"[^"]*"'

step 40

volter world down

step 41

kill $(jobs -p)

step 42