@volter/world 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +669 -0
- package/dist/src/credentials.d.ts +12 -0
- package/dist/src/credentials.js +64 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +6 -0
- package/dist/src/locate.d.ts +18 -0
- package/dist/src/locate.js +60 -0
- package/dist/src/serve-shutdown.d.ts +6 -0
- package/dist/src/serve-shutdown.js +27 -0
- package/dist/src/world.d.ts +299 -0
- package/dist/src/world.js +491 -0
- package/package.json +37 -0
- package/src/cli.ts +486 -0
- package/src/credentials.ts +56 -0
- package/src/handlers-in-repo.test.ts +45 -0
- package/src/index.ts +6 -0
- package/src/journeys/kit.ts +12 -0
- package/src/journeys/tutorial-runner.test.ts +180 -0
- package/src/journeys/tutorial.ts +345 -0
- package/src/journeys/tutorials.test.ts +39 -0
- package/src/locate.ts +61 -0
- package/src/sdk.test.ts +92 -0
- package/src/serve-shutdown.test.ts +40 -0
- package/src/serve-shutdown.ts +26 -0
- package/src/world.ts +448 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** `$XDG_CONFIG_HOME/volter/credentials.json`, else `~/.config/volter/credentials.json`. */
|
|
2
|
+
export declare function credentialsPath(): string;
|
|
3
|
+
/** The token stored for an origin, or undefined. */
|
|
4
|
+
export declare function tokenFor(origin: string, path?: string): string | undefined;
|
|
5
|
+
/** Store a token for an origin (0600). */
|
|
6
|
+
export declare function storeToken(origin: string, token: string, path?: string): void;
|
|
7
|
+
/** `volter login`: the hosted platform a person signed the CLI into — its origin, beside the credentials. */
|
|
8
|
+
export declare function platformPath(): string;
|
|
9
|
+
export declare function storePlatform(origin: string, path?: string): void;
|
|
10
|
+
export declare function platformOrigin(path?: string): string | undefined;
|
|
11
|
+
/** The token a verb uses: an explicit one for this call (CI), else the stored one, else a clear error. */
|
|
12
|
+
export declare function requireToken(origin: string, explicit?: string, path?: string): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Tokens, not keys (docs/concepts/worlds.md#the-config-and-the-running-world): the remote's read and admin
|
|
2
|
+
// credentials are TOKENS, kept in the user's config directory keyed by origin URL — written by
|
|
3
|
+
// `clone --token`, read by `fetch` and `push`. Nothing in the repo and nothing in the world holds
|
|
4
|
+
// one; "key" means only the vendor's real API key, which lives in the remote's custody.
|
|
5
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
/** `$XDG_CONFIG_HOME/volter/credentials.json`, else `~/.config/volter/credentials.json`. */
|
|
9
|
+
export function credentialsPath() {
|
|
10
|
+
const base = process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME !== '' ? process.env.XDG_CONFIG_HOME : join(homedir(), '.config');
|
|
11
|
+
return join(base, 'volter', 'credentials.json');
|
|
12
|
+
}
|
|
13
|
+
function normalizeOrigin(url) { return url.replace(/\/+$/, ''); }
|
|
14
|
+
function readStore(path) {
|
|
15
|
+
if (!existsSync(path))
|
|
16
|
+
return {};
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new Error(`${path} is not readable as JSON — fix or delete it`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** The token stored for an origin, or undefined. */
|
|
25
|
+
export function tokenFor(origin, path = credentialsPath()) {
|
|
26
|
+
return readStore(path)[normalizeOrigin(origin)]?.token;
|
|
27
|
+
}
|
|
28
|
+
/** Store a token for an origin (0600). */
|
|
29
|
+
export function storeToken(origin, token, path = credentialsPath()) {
|
|
30
|
+
if (token === '')
|
|
31
|
+
throw new Error('an empty token cannot be stored');
|
|
32
|
+
const store = readStore(path);
|
|
33
|
+
store[normalizeOrigin(origin)] = { token, savedAt: new Date().toISOString() };
|
|
34
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
35
|
+
writeFileSync(path, `${JSON.stringify(store, null, 2)}\n`, { mode: 0o600 });
|
|
36
|
+
chmodSync(path, 0o600);
|
|
37
|
+
}
|
|
38
|
+
/** `volter login`: the hosted platform a person signed the CLI into — its origin, beside the credentials. */
|
|
39
|
+
export function platformPath() { return join(dirname(credentialsPath()), 'platform.json'); }
|
|
40
|
+
export function storePlatform(origin, path = platformPath()) {
|
|
41
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
42
|
+
writeFileSync(path, `${JSON.stringify({ url: normalizeOrigin(origin), savedAt: new Date().toISOString() }, null, 2)}\n`, { mode: 0o600 });
|
|
43
|
+
}
|
|
44
|
+
export function platformOrigin(path = platformPath()) {
|
|
45
|
+
if (!existsSync(path))
|
|
46
|
+
return undefined;
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(readFileSync(path, 'utf8')).url;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** The token a verb uses: an explicit one for this call (CI), else the stored one, else a clear error. */
|
|
55
|
+
export function requireToken(origin, explicit, path = credentialsPath()) {
|
|
56
|
+
if (explicit !== undefined && explicit !== '')
|
|
57
|
+
return explicit;
|
|
58
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(origin))
|
|
59
|
+
return ''; // a world at a path needs no token
|
|
60
|
+
const stored = tokenFor(origin, path);
|
|
61
|
+
if (stored === undefined)
|
|
62
|
+
throw new Error(`No token stored for ${normalizeOrigin(origin)} — \`volter world clone <url> --token <token>\` stores one, or pass --token for this call`);
|
|
63
|
+
return stored;
|
|
64
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { World, parseOriginUrl, worldNameFor, type LogEntry, type WorldMode, type WorldRef, type WorldStatus } from './world.js';
|
|
2
|
+
export { findWorldRoot, requireWorldRoot, currentBranch, setCurrentBranch, mainBranch, worldConfigPath, worldEnvPath, worldSeedPath, WORLD_FILE } from './locate.js';
|
|
3
|
+
export { credentialsPath, requireToken, storeToken, tokenFor } from './credentials.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @volter/world — the product surface (docs/concepts/the-model.md, docs/concepts/worlds.md#the-config-and-the-running-world):
|
|
2
|
+
// World and Repo, one method per verb in the user's words, over core and the kernel, with no
|
|
3
|
+
// mechanism of its own. The `volter` command is one client of this package.
|
|
4
|
+
export { World, parseOriginUrl, worldNameFor } from "./world.js";
|
|
5
|
+
export { findWorldRoot, requireWorldRoot, currentBranch, setCurrentBranch, mainBranch, worldConfigPath, worldEnvPath, worldSeedPath, WORLD_FILE } from "./locate.js";
|
|
6
|
+
export { credentialsPath, requireToken, storeToken, tokenFor } from "./credentials.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const WORLD_FILE = "world.json";
|
|
2
|
+
/** `<root>/.volter` */
|
|
3
|
+
export declare function stateDir(root: string): string;
|
|
4
|
+
/** `<root>/.volter/world.json` — the config, relative to the root as `up` takes it. */
|
|
5
|
+
export declare function worldConfigRelative(): string;
|
|
6
|
+
export declare function worldConfigPath(root: string): string;
|
|
7
|
+
/** `<root>/.volter/world.env` — where `up` writes the live env (gitignored by init). */
|
|
8
|
+
export declare function worldEnvPath(root: string): string;
|
|
9
|
+
/** `<root>/.volter/seed.ts` — the default data, when the world has any. */
|
|
10
|
+
export declare function worldSeedPath(root: string): string;
|
|
11
|
+
/** The world root above `from`, or null: the nearest directory holding `.volter/world.json`. */
|
|
12
|
+
export declare function findWorldRoot(from?: string): string | null;
|
|
13
|
+
export declare function requireWorldRoot(from?: string): string;
|
|
14
|
+
/** The main branch: the config's `id`. */
|
|
15
|
+
export declare function mainBranch(root: string): string;
|
|
16
|
+
/** The checked-out branch: `.volter/current`, else main. */
|
|
17
|
+
export declare function currentBranch(root: string): string;
|
|
18
|
+
export declare function setCurrentBranch(root: string, name: string): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Where the world is (docs/concepts/worlds.md#the-config-and-the-running-world): the nearest `.volter/world.json`
|
|
2
|
+
// above the cwd names the world root — the app repo — and `.volter/current` names the branch that
|
|
3
|
+
// is checked out (absent: the main branch, the config's `id`). Every `volter` verb and
|
|
4
|
+
// `World.open()` start here, the way `git` starts from the nearest `.git`.
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { dirname, join, resolve } from 'node:path';
|
|
7
|
+
import { stateDirName } from '@volter/world-core';
|
|
8
|
+
import { loadWorldConfig } from '@volter/world-runtime';
|
|
9
|
+
export const WORLD_FILE = 'world.json';
|
|
10
|
+
/** `<root>/.volter` */
|
|
11
|
+
export function stateDir(root) { return join(root, stateDirName()); }
|
|
12
|
+
/** `<root>/.volter/world.json` — the config, relative to the root as `up` takes it. */
|
|
13
|
+
export function worldConfigRelative() { return join(stateDirName(), WORLD_FILE); }
|
|
14
|
+
export function worldConfigPath(root) { return join(stateDir(root), WORLD_FILE); }
|
|
15
|
+
/** `<root>/.volter/world.env` — where `up` writes the live env (gitignored by init). */
|
|
16
|
+
export function worldEnvPath(root) { return join(stateDir(root), 'world.env'); }
|
|
17
|
+
/** `<root>/.volter/seed.ts` — the default data, when the world has any. */
|
|
18
|
+
export function worldSeedPath(root) { return join(stateDir(root), 'seed.ts'); }
|
|
19
|
+
/** The world root above `from`, or null: the nearest directory holding `.volter/world.json`. */
|
|
20
|
+
export function findWorldRoot(from = process.cwd()) {
|
|
21
|
+
let dir = resolve(from);
|
|
22
|
+
for (;;) {
|
|
23
|
+
if (existsSync(worldConfigPath(dir)))
|
|
24
|
+
return dir;
|
|
25
|
+
const up = dirname(dir);
|
|
26
|
+
if (up === dir)
|
|
27
|
+
return null;
|
|
28
|
+
dir = up;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function requireWorldRoot(from = process.cwd()) {
|
|
32
|
+
const root = findWorldRoot(from);
|
|
33
|
+
if (root === null) {
|
|
34
|
+
throw new Error(`Not in a world: no ${worldConfigRelative()} here or above ${resolve(from)}. Run \`volter world init\` in your app, or pass --world <dir>.`);
|
|
35
|
+
}
|
|
36
|
+
return root;
|
|
37
|
+
}
|
|
38
|
+
/** The main branch: the config's `id`. */
|
|
39
|
+
export function mainBranch(root) {
|
|
40
|
+
return loadWorldConfig(worldConfigPath(root), root).config.id;
|
|
41
|
+
}
|
|
42
|
+
const CURRENT = 'current';
|
|
43
|
+
/** The checked-out branch: `.volter/current`, else main. */
|
|
44
|
+
export function currentBranch(root) {
|
|
45
|
+
const file = join(stateDir(root), CURRENT);
|
|
46
|
+
if (!existsSync(file))
|
|
47
|
+
return mainBranch(root);
|
|
48
|
+
const name = readFileSync(file, 'utf8').trim();
|
|
49
|
+
return name === '' ? mainBranch(root) : name;
|
|
50
|
+
}
|
|
51
|
+
export function setCurrentBranch(root, name) {
|
|
52
|
+
mkdirSync(stateDir(root), { recursive: true });
|
|
53
|
+
const file = join(stateDir(root), CURRENT);
|
|
54
|
+
if (name === mainBranch(root)) {
|
|
55
|
+
if (existsSync(file))
|
|
56
|
+
rmSync(file);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
writeFileSync(file, `${name}\n`);
|
|
60
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { EventEmitter } from 'node:events';
|
|
2
|
+
/** Signals request one shutdown; repeated signals never start a competing World teardown. */
|
|
3
|
+
export declare function waitForServeShutdown(stop: () => Promise<void>, options?: {
|
|
4
|
+
signals?: Pick<EventEmitter, 'on' | 'off'>;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Signals request one shutdown; repeated signals never start a competing World teardown. */
|
|
2
|
+
export async function waitForServeShutdown(stop, options = {}) {
|
|
3
|
+
const signals = options.signals ?? process;
|
|
4
|
+
const names = ['SIGTERM', 'SIGINT', 'SIGHUP'];
|
|
5
|
+
let started = false;
|
|
6
|
+
let timer;
|
|
7
|
+
let request;
|
|
8
|
+
try {
|
|
9
|
+
await new Promise((resolve, reject) => {
|
|
10
|
+
request = () => {
|
|
11
|
+
if (started)
|
|
12
|
+
return;
|
|
13
|
+
started = true;
|
|
14
|
+
timer = setTimeout(() => reject(new Error('World serve shutdown timed out; cleanup incomplete, instance and reservation retained for explicit down')), options.timeoutMs ?? 8000);
|
|
15
|
+
Promise.resolve().then(stop).then(resolve, reject);
|
|
16
|
+
};
|
|
17
|
+
for (const name of names)
|
|
18
|
+
signals.on(name, request);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
if (timer)
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
for (const name of names)
|
|
25
|
+
signals.off(name, request);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { rebaseChangeset, worldBootMarker, type Changeset, type LedgerDelta, type TwinAction, type WorldMarker, type ApplyReceipt, applyTwinWrite, type TwinResource } from '@volter/world-core';
|
|
2
|
+
import { approveWorldChangeset, createWorldChangeset, downWorld, fetchFromOrigin, pushWorldChangeset, rebaseWorldChangeset, replayWorldChangeset, resetWorld, seedWorld, statusWorldChangeset, verifyWorldChangeset, type ServedWorld, type DeployTwinOutcome, type ChangesetLocation, type InitOptions, type InitResult, type WorldInstance } from '@volter/world-runtime';
|
|
3
|
+
import { rebaseBranch, type Receipt, type RootConfig } from '@volter/world-core';
|
|
4
|
+
export type WorldRef = {
|
|
5
|
+
name?: string;
|
|
6
|
+
root?: string;
|
|
7
|
+
};
|
|
8
|
+
export type WorldMode = 'local' | 'share' | 'sealed';
|
|
9
|
+
/** One change as `log` shows it: the write, and the receipt the vendor answered with once pushed. */
|
|
10
|
+
/** A twin's branch log as the world reads it: the twin's name, the state service it records under, its control root. */
|
|
11
|
+
export declare class TwinLog {
|
|
12
|
+
readonly service: string;
|
|
13
|
+
readonly stateService: string;
|
|
14
|
+
readonly root: string;
|
|
15
|
+
constructor(service: string, stateService: string, root: string);
|
|
16
|
+
/** this branch's own entries, bookkeeping aside */
|
|
17
|
+
log(): TwinAction[];
|
|
18
|
+
/** entries the parent does not hold */
|
|
19
|
+
unpushed(opts?: {
|
|
20
|
+
pushable?: boolean;
|
|
21
|
+
}): TwinAction[];
|
|
22
|
+
/** the tree: this twin's resources as a read sees them */
|
|
23
|
+
state(): TwinResource[];
|
|
24
|
+
/** one write through the kernel's write path (the head performs it when this twin's root says so) */
|
|
25
|
+
change(write: Parameters<typeof applyTwinWrite>[1]): ReturnType<typeof applyTwinWrite>;
|
|
26
|
+
}
|
|
27
|
+
export type LogEntry = TwinAction & {
|
|
28
|
+
receipt?: ApplyReceipt;
|
|
29
|
+
changeset?: string; /** v2: the receipt on the landed copy — deployed, refused, failed, landed */
|
|
30
|
+
landed?: Receipt; /** the entry's POSITION in its twin's whole log (contract "Just like Neon", 2): the parent view then the branch, counted from one */
|
|
31
|
+
position?: number;
|
|
32
|
+
};
|
|
33
|
+
export type WorldStatus = {
|
|
34
|
+
world: string;
|
|
35
|
+
root: string;
|
|
36
|
+
branch: string;
|
|
37
|
+
branches: string[];
|
|
38
|
+
running: boolean;
|
|
39
|
+
/** the URL this world is served at, when a serve process is alive (contract "Just like Neon", 4) */
|
|
40
|
+
served: {
|
|
41
|
+
base: string;
|
|
42
|
+
pid: number;
|
|
43
|
+
startedAt: string;
|
|
44
|
+
} | null;
|
|
45
|
+
/** the origin this world clones from and pushes to, or null: the default data is its only origin */
|
|
46
|
+
origin: {
|
|
47
|
+
url: string;
|
|
48
|
+
namespace: string;
|
|
49
|
+
fetchedAt?: string;
|
|
50
|
+
} | null;
|
|
51
|
+
unpushed: number;
|
|
52
|
+
changesets: {
|
|
53
|
+
total: number;
|
|
54
|
+
pushed: number;
|
|
55
|
+
};
|
|
56
|
+
services: Record<string, {
|
|
57
|
+
url?: string;
|
|
58
|
+
running: boolean; /** the pack's protocol major and its standing (protocol 1 is deprecated: served under a warning, its vendor half throws) */
|
|
59
|
+
protocol?: {
|
|
60
|
+
major: number;
|
|
61
|
+
standing: string;
|
|
62
|
+
};
|
|
63
|
+
}>;
|
|
64
|
+
envFile?: string;
|
|
65
|
+
};
|
|
66
|
+
/** `https://host/org/world` → the remote's base URL and the namespace it addresses. */
|
|
67
|
+
export declare function parseOriginUrl(url: string): {
|
|
68
|
+
url: string;
|
|
69
|
+
namespace: string;
|
|
70
|
+
};
|
|
71
|
+
export declare class World {
|
|
72
|
+
/** The branch this handle is on — the instance name in the kernel. */
|
|
73
|
+
readonly name: string;
|
|
74
|
+
/** The world root: the app repo. */
|
|
75
|
+
readonly root: string;
|
|
76
|
+
private constructor();
|
|
77
|
+
/** The world of the cwd (or of `ref.root`), on its checked-out branch (or `ref.name`). */
|
|
78
|
+
/** The config `loadWorldConfig` resolves for this world: the in-repo world.json, else the world's name. */
|
|
79
|
+
private configRef;
|
|
80
|
+
static open(ref?: WorldRef): World;
|
|
81
|
+
/** Whether `from` (default: the cwd) is inside a world. */
|
|
82
|
+
static find(from?: string): World | null;
|
|
83
|
+
/** `volter world init --bare <org>/<world> --twins a,b`: a world with no app — a package.json naming the
|
|
84
|
+
* twins (installed with bun), then the world over them, served as `/<org>/<world>/`. */
|
|
85
|
+
static initBare(dir: string, served: string, twins: string[], opts?: {
|
|
86
|
+
install?: boolean;
|
|
87
|
+
force?: boolean;
|
|
88
|
+
}): {
|
|
89
|
+
world: World;
|
|
90
|
+
result: InitResult;
|
|
91
|
+
};
|
|
92
|
+
/** `volter world init`: detect the app's vendors and write `.volter/world.json` beside the code. */
|
|
93
|
+
static init(app?: string, opts?: Omit<InitOptions, 'root'> & {
|
|
94
|
+
name?: string;
|
|
95
|
+
root?: string;
|
|
96
|
+
}): {
|
|
97
|
+
world: World;
|
|
98
|
+
result: InitResult;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Start the twins on this branch. A branch that has run before comes back with its state (down
|
|
102
|
+
* stops compute, up resumes it — `reset` is the way back to the default data); a fresh branch
|
|
103
|
+
* boots clean and loads the default data unless `seed: false`.
|
|
104
|
+
*/
|
|
105
|
+
up(opts?: {
|
|
106
|
+
mode?: WorldMode;
|
|
107
|
+
seed?: boolean;
|
|
108
|
+
cwd?: string;
|
|
109
|
+
}): Promise<WorldInstance>;
|
|
110
|
+
/** Stop the twins; the branch's state stays (`up` resumes). `purge` forgets it. */
|
|
111
|
+
down(opts?: {
|
|
112
|
+
purge?: boolean;
|
|
113
|
+
}): ReturnType<typeof downWorld>;
|
|
114
|
+
/** Run a command inside the world: the app or its tests, with the twins' URLs and fake credentials in its env. */
|
|
115
|
+
run(command: string[], opts?: {
|
|
116
|
+
cwd?: string;
|
|
117
|
+
verbose?: boolean;
|
|
118
|
+
}): Promise<number>;
|
|
119
|
+
/** The shell script that activates the world in the current shell: `eval "$(volter world activate)"` — vendor CLIs and curl reach the twins. */
|
|
120
|
+
activateScript(): string;
|
|
121
|
+
/** A subshell with the world active; resolves to its exit code. */
|
|
122
|
+
shell(): Promise<number>;
|
|
123
|
+
/** The kernel's instance record, or null before the first `up`. */
|
|
124
|
+
instance(): (WorldInstance & {
|
|
125
|
+
running: boolean;
|
|
126
|
+
livePids: number[];
|
|
127
|
+
}) | null;
|
|
128
|
+
/** The world, the branch, the origin, what is unpushed, what is running. */
|
|
129
|
+
status(): WorldStatus;
|
|
130
|
+
/** One twin log per twin the world runs: its branch entries and what is unpushed. */
|
|
131
|
+
repos(): TwinLog[];
|
|
132
|
+
repo(service: string): TwinLog;
|
|
133
|
+
/** Every write the app made, across the twins, oldest first, with the receipt against each pushed one. */
|
|
134
|
+
log(): LogEntry[];
|
|
135
|
+
/** Changes not yet pushed (`log origin..HEAD`), across the twins. */
|
|
136
|
+
unpushed(): TwinAction[];
|
|
137
|
+
diff(base?: string): LedgerDelta;
|
|
138
|
+
/** Load the default data: the seed runs with every twin recording what it creates as data that was already there. */
|
|
139
|
+
seed(opts?: {
|
|
140
|
+
entry?: string;
|
|
141
|
+
cwd?: string;
|
|
142
|
+
}): ReturnType<typeof seedWorld>;
|
|
143
|
+
/** Back to the default data: forget this branch's state, boot, seed. */
|
|
144
|
+
reset(opts?: {
|
|
145
|
+
entry?: string;
|
|
146
|
+
cwd?: string;
|
|
147
|
+
}): ReturnType<typeof resetWorld>;
|
|
148
|
+
/**
|
|
149
|
+
* A new branch from here — its own log over this branch's mirrors — checked out. One branch runs
|
|
150
|
+
* at a time in a world (the app's env names one set of twins), so this branch stops as the new
|
|
151
|
+
* one starts; `checkout` brings it back with its state.
|
|
152
|
+
*/
|
|
153
|
+
branch(name: string, opts?: {
|
|
154
|
+
at?: {
|
|
155
|
+
instant?: string;
|
|
156
|
+
positions?: Record<string, number>;
|
|
157
|
+
views?: Record<string, string>;
|
|
158
|
+
};
|
|
159
|
+
}): Promise<World>;
|
|
160
|
+
/** Switch to a branch: its twins come back with their state; this branch's stop. */
|
|
161
|
+
checkout(name: string): Promise<World>;
|
|
162
|
+
branches(): string[];
|
|
163
|
+
/** Is this the main branch? */
|
|
164
|
+
isMain(): boolean;
|
|
165
|
+
/** What this branch's changes are measured from, as `diff` says it: the branch point, origin, or the story. */
|
|
166
|
+
diffBase(): string;
|
|
167
|
+
/** The world's clock: the frozen instant every twin stamps from, or the wall clock when unset. */
|
|
168
|
+
clock(): {
|
|
169
|
+
at: string;
|
|
170
|
+
frozen: boolean;
|
|
171
|
+
};
|
|
172
|
+
/** Set the world's clock to an instant; every twin stamps from it until it moves. */
|
|
173
|
+
setClock(iso: string): string;
|
|
174
|
+
/** Move a set clock forward: `30d`, `12h`, `5m`, `90s`. */
|
|
175
|
+
advanceClock(by: string): string;
|
|
176
|
+
/** Rebase this branch onto its base's current position, every twin; conflicts named per subject and field. */
|
|
177
|
+
rebaseBranch(): Array<{
|
|
178
|
+
service: string;
|
|
179
|
+
} & ReturnType<typeof rebaseBranch>>;
|
|
180
|
+
/** Replay a changeset's changes into another branch's twins, in order, with the same ids. */
|
|
181
|
+
replay(name: string, into: string): ReturnType<typeof replayWorldChangeset>;
|
|
182
|
+
/** Serve this world on a port under `/<org>/<world>/`; returns when listening. */
|
|
183
|
+
serve(opts?: {
|
|
184
|
+
port?: number;
|
|
185
|
+
host?: string;
|
|
186
|
+
consolePort?: number;
|
|
187
|
+
announce?: (info: {
|
|
188
|
+
name: string;
|
|
189
|
+
base: string;
|
|
190
|
+
token: string;
|
|
191
|
+
readToken: string;
|
|
192
|
+
console: string | null;
|
|
193
|
+
}) => void;
|
|
194
|
+
}): Promise<ServedWorld>;
|
|
195
|
+
/** The remotes named in world.json (`volter remote add`), name → url or path. */
|
|
196
|
+
remotes(): Record<string, string>;
|
|
197
|
+
/** Name a remote; `origin` is the one fetch and push use by default. A token given is remembered for it. */
|
|
198
|
+
addRemote(name: string, target: string, opts?: {
|
|
199
|
+
token?: string;
|
|
200
|
+
}): void;
|
|
201
|
+
removeRemote(name: string): void;
|
|
202
|
+
/** The remote a verb uses: the named one, else origin from world.json, else the instance's recorded origin. */
|
|
203
|
+
remote(name?: string): {
|
|
204
|
+
url: string;
|
|
205
|
+
namespace: string;
|
|
206
|
+
} | null;
|
|
207
|
+
/** The origin this world clones from and pushes to, or null: the default data is its only origin. */
|
|
208
|
+
origin(): WorldInstance['origin'] | null;
|
|
209
|
+
/**
|
|
210
|
+
* Clone a remote's canonical history into this world: record the origin, remember the token,
|
|
211
|
+
* fetch everything. With the world's token (namespace or read), the history arrives through the
|
|
212
|
+
* mirror, from the beginning; with `adminToken` as well, the remote's whole tree is copied in one
|
|
213
|
+
* move, which is what an admin can do and a reader cannot. Either way one token is remembered:
|
|
214
|
+
* the world's, which is what `fetch` and `push` use afterwards.
|
|
215
|
+
*/
|
|
216
|
+
clone(url: string, opts?: {
|
|
217
|
+
token?: string;
|
|
218
|
+
adminToken?: string;
|
|
219
|
+
}): Promise<ReturnType<typeof fetchFromOrigin>>;
|
|
220
|
+
/** Fetch, then move this branch onto what came in: git's pull. */
|
|
221
|
+
pull(opts?: {
|
|
222
|
+
token?: string;
|
|
223
|
+
services?: string[];
|
|
224
|
+
}): Promise<{
|
|
225
|
+
fetched: Awaited<ReturnType<typeof fetchFromOrigin>>;
|
|
226
|
+
rebased: ReturnType<World['rebaseBranch']>;
|
|
227
|
+
}>;
|
|
228
|
+
/** Fetch what the origin observed since the last fetch. */
|
|
229
|
+
fetch(opts?: {
|
|
230
|
+
token?: string;
|
|
231
|
+
services?: string[];
|
|
232
|
+
}): ReturnType<typeof fetchFromOrigin>;
|
|
233
|
+
/** Cut a changeset from the unpushed changes: the reviewable unit, with the author's message. */
|
|
234
|
+
changeset(opts?: {
|
|
235
|
+
name?: string;
|
|
236
|
+
message?: string;
|
|
237
|
+
base?: string;
|
|
238
|
+
verifiers?: Parameters<typeof createWorldChangeset>[2] extends infer O ? (O extends {
|
|
239
|
+
verifiers?: infer V;
|
|
240
|
+
} ? V : never) : never;
|
|
241
|
+
overwrite?: boolean;
|
|
242
|
+
}): Changeset;
|
|
243
|
+
changesets(): ChangesetLocation[];
|
|
244
|
+
/**
|
|
245
|
+
* Push to the origin, which deploys to the vendor with its own keys and answers with receipts:
|
|
246
|
+
* the named changeset, or every changeset not yet pushed, oldest first. A local world never
|
|
247
|
+
* holds a vendor credential.
|
|
248
|
+
*/
|
|
249
|
+
push(opts?: {
|
|
250
|
+
name?: string;
|
|
251
|
+
token?: string;
|
|
252
|
+
force?: boolean;
|
|
253
|
+
}): Promise<Array<Awaited<ReturnType<typeof pushWorldChangeset>>>>;
|
|
254
|
+
mark(id?: string, now?: Date): WorldMarker;
|
|
255
|
+
marks(): WorldMarker[];
|
|
256
|
+
verify(name: string, opts?: {
|
|
257
|
+
into?: string;
|
|
258
|
+
ephemeral?: boolean;
|
|
259
|
+
}): ReturnType<typeof verifyWorldChangeset>;
|
|
260
|
+
approve(name: string, principal: string, note?: string): ReturnType<typeof approveWorldChangeset>;
|
|
261
|
+
readiness(name: string): ReturnType<typeof statusWorldChangeset>;
|
|
262
|
+
rebase(name: string): ReturnType<typeof rebaseWorldChangeset>;
|
|
263
|
+
/** The twin's root and credential, as `volter twin <vendor>` prints them. */
|
|
264
|
+
twin(vendor: string): {
|
|
265
|
+
vendor: string;
|
|
266
|
+
url?: string;
|
|
267
|
+
root: RootConfig | null;
|
|
268
|
+
credential: {
|
|
269
|
+
placedAt: string;
|
|
270
|
+
fingerprint: string;
|
|
271
|
+
} | null;
|
|
272
|
+
};
|
|
273
|
+
/** Set (or clear) the twin's root: the vendor's API and the deploy policy, in world.json. Takes effect at the next `up` or `serve`, or now when the world runs. */
|
|
274
|
+
setTwinRoot(vendor: string, root: RootConfig | null): void;
|
|
275
|
+
/** Seal the vendor's credential beside the world (a bare token, or a JSON payload). Never readable back. */
|
|
276
|
+
sealTwinCredential(vendor: string, input: string): Promise<{
|
|
277
|
+
placedAt: string;
|
|
278
|
+
fingerprint: string;
|
|
279
|
+
}>;
|
|
280
|
+
/** Refresh one twin from its root now. */
|
|
281
|
+
refreshTwin(vendor: string, opts?: {
|
|
282
|
+
force?: boolean;
|
|
283
|
+
}): Promise<{
|
|
284
|
+
refreshed: boolean;
|
|
285
|
+
reason?: string;
|
|
286
|
+
report?: {
|
|
287
|
+
observed: number;
|
|
288
|
+
appended: number;
|
|
289
|
+
unchanged: number;
|
|
290
|
+
removed: number;
|
|
291
|
+
};
|
|
292
|
+
}>;
|
|
293
|
+
private rootOf;
|
|
294
|
+
/** DEPLOY: perform landed entries against each root twin's vendor, by policy (runtime `deployWorld`). */
|
|
295
|
+
deploy(name?: string): Promise<DeployTwinOutcome[]>;
|
|
296
|
+
}
|
|
297
|
+
/** The world's name when none is given: the app directory's basename, made safe. */
|
|
298
|
+
export declare function worldNameFor(app: string): string;
|
|
299
|
+
export { rebaseChangeset, worldBootMarker };
|