@volter/world 3.0.146 → 3.0.148

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.
@@ -13,7 +13,7 @@ You need Node 22.6+, npm and registry access during installation. The browser pa
13
13
  Copy this starter into a new folder:
14
14
 
15
15
  ```console
16
- $ npx @volter/world@3.0.146 example customer-onboarding --dir onboarding
16
+ $ npx @volter/world@3.0.148 example customer-onboarding --dir onboarding
17
17
  $ cd onboarding
18
18
  $ npm install
19
19
  ```
@@ -11,10 +11,10 @@
11
11
  "pg": "8.16.3"
12
12
  },
13
13
  "devDependencies": {
14
- "@volter/world": "3.0.146",
15
- "@volter/world-core": "3.0.146",
16
- "@volter/world-runtime": "3.0.146",
17
- "@volter/world-console": "3.0.141",
14
+ "@volter/world": "3.0.148",
15
+ "@volter/world-core": "3.0.147",
16
+ "@volter/world-runtime": "3.0.147",
17
+ "@volter/world-console": "3.0.142",
18
18
  "@volter/twin-stripe": "3.0.2",
19
19
  "@volter/twin-resend": "1.0.1"
20
20
  }
@@ -13,7 +13,7 @@ Give two workers the same Stripe baseline, let them change different existing cu
13
13
  Use an empty parent folder. Create the baseline starter there:
14
14
 
15
15
  ```console
16
- $ npx @volter/world@3.0.146 example integration-branches --dir baseline
16
+ $ npx @volter/world@3.0.148 example integration-branches --dir baseline
17
17
  $ cd baseline
18
18
  $ npm install
19
19
  $ ./node_modules/.bin/volter world init --name baseline --twins stripe
@@ -38,9 +38,9 @@ The stopped baseline retains its history and can supply local path remotes. Defa
38
38
  Create three more copies in the same parent folder:
39
39
 
40
40
  ```console
41
- $ npx @volter/world@3.0.146 example integration-branches --dir integration
42
- $ npx @volter/world@3.0.146 example integration-branches --dir worker-a
43
- $ npx @volter/world@3.0.146 example integration-branches --dir worker-b
41
+ $ npx @volter/world@3.0.148 example integration-branches --dir integration
42
+ $ npx @volter/world@3.0.148 example integration-branches --dir worker-a
43
+ $ npx @volter/world@3.0.148 example integration-branches --dir worker-b
44
44
  ```
45
45
 
46
46
  In `integration`, then `worker-a`, then `worker-b`, install dependencies and initialize with the folder's name. Before a first boot or app write, link and pull the baseline:
@@ -9,10 +9,10 @@
9
9
  "stripe": "17.7.0"
10
10
  },
11
11
  "devDependencies": {
12
- "@volter/world": "3.0.146",
13
- "@volter/world-core": "3.0.146",
14
- "@volter/world-runtime": "3.0.146",
15
- "@volter/world-console": "3.0.141",
12
+ "@volter/world": "3.0.148",
13
+ "@volter/world-core": "3.0.147",
14
+ "@volter/world-runtime": "3.0.147",
15
+ "@volter/world-console": "3.0.142",
16
16
  "@volter/twin-stripe": "3.0.2"
17
17
  }
18
18
  }
@@ -13,7 +13,7 @@ You need Node 22.6+, npm and registry access during installation. No platform ac
13
13
  ## Prepare an ordinary workplace
14
14
 
15
15
  ```console
16
- $ npx @volter/world@3.0.146 example workplace-agent --dir workplace
16
+ $ npx @volter/world@3.0.148 example workplace-agent --dir workplace
17
17
  $ cd workplace
18
18
  $ npm install
19
19
  $ ./node_modules/.bin/volter world init --name workplace --twins github,linear,slack
@@ -11,10 +11,10 @@
11
11
  "@slack/web-api": "7.19.0"
12
12
  },
13
13
  "devDependencies": {
14
- "@volter/world": "3.0.146",
15
- "@volter/world-core": "3.0.146",
16
- "@volter/world-runtime": "3.0.146",
17
- "@volter/world-console": "3.0.141",
14
+ "@volter/world": "3.0.148",
15
+ "@volter/world-core": "3.0.147",
16
+ "@volter/world-runtime": "3.0.147",
17
+ "@volter/world-console": "3.0.142",
18
18
  "@volter/twin-github": "3.0.6",
19
19
  "@volter/twin-linear": "3.0.4",
20
20
  "@volter/twin-slack": "3.0.5"
@@ -105,6 +105,8 @@ export declare class World {
105
105
  seed?: boolean;
106
106
  cwd?: string;
107
107
  }): Promise<WorldInstance>;
108
+ /** Product startup completes its seed sequence; the runtime owns only the boot. */
109
+ private finishStartup;
108
110
  /** Stop the twins; the branch's state stays (`up` resumes). `purge` forgets it. */
109
111
  down(opts?: {
110
112
  purge?: boolean;
package/dist/src/world.js CHANGED
@@ -125,12 +125,17 @@ export class World {
125
125
  ...(opts.mode ? { mode: opts.mode } : existing ? { mode: existing.mode } : {}),
126
126
  ...(existing ? { keepState: true } : {}),
127
127
  });
128
+ await this.finishStartup(instance, existing === null, opts);
129
+ return instance;
130
+ }
131
+ /** Product startup completes its seed sequence; the runtime owns only the boot. */
132
+ async finishStartup(instance, fresh, opts = {}) {
128
133
  try {
129
- if (!existing && opts.seed !== false && existsSync(worldSeedPath(this.root))) {
134
+ if (fresh && opts.seed !== false && existsSync(worldSeedPath(this.root))) {
130
135
  // a story that fails is a loud failure: the world is up, but not the world the app expects
131
136
  const seeded = await seedWorld(this.name, { root: this.root, cwd: opts.cwd ?? this.root });
132
137
  if (seeded.exitCode !== 0)
133
- throw new Error(`the story failed (${seeded.entry} exited ${seeded.exitCode}); the world is up without it — fix the seed and \`volter world seed\``);
138
+ throw new Error(`the story failed (${seeded.entry} exited ${seeded.exitCode}); state may be partially seeded — fix the seed, start this World and run \`volter world seed\``);
134
139
  }
135
140
  }
136
141
  finally {
@@ -138,7 +143,6 @@ export class World {
138
143
  // fresh boot runs simulated), where the deploy reads it — also when the seed failed
139
144
  materializeRoots(loadWorldConfig(this.configRef(), this.root).config, instance.dirs.data, this.root);
140
145
  }
141
- return instance;
142
146
  }
143
147
  /** Stop the twins; the branch's state stays (`up` resumes). `purge` forgets it. */
144
148
  down(opts = {}) { return downWorld(this.name, this.root, opts); }
@@ -150,12 +154,9 @@ export class World {
150
154
  shell() { return shellWorld(this.name, this.root); }
151
155
  /** The kernel's instance record, or null before the first `up`. */
152
156
  instance() {
153
- try {
154
- return statusWorld(this.name, this.root);
155
- }
156
- catch {
157
+ if (!existsSync(join(instanceDir(this.root, this.name), 'instance.json')))
157
158
  return null;
158
- }
159
+ return statusWorld(this.name, this.root);
159
160
  }
160
161
  /** The world, the branch, the origin, what is unpushed, what is running. */
161
162
  status() {
@@ -379,7 +380,35 @@ export class World {
379
380
  /** Serve this world on a port under `/<org>/<world>/`; returns when listening. */
380
381
  serve(opts = {}) { return serveWorld(this.name, { root: this.root, ...opts }); }
381
382
  /** `volter world view`: this World, its twins' screens, its branches and the console on one origin (docs/contributing/architecture.md, "Viewing a World"). */
382
- view(opts = {}) { const person = platformPerson(); return serveWorldView(this.name, { root: this.root, ...(person ? { localPerson: person } : {}), ...opts }); }
383
+ async view(opts = {}) {
384
+ const fresh = this.instance() === null;
385
+ const person = platformPerson();
386
+ const announced = [];
387
+ let ready = false;
388
+ const view = await serveWorldView(this.name, { root: this.root, ...(person ? { localPerson: person } : {}), ...opts, announce: line => { if (ready)
389
+ opts.announce?.(line);
390
+ else
391
+ announced.push(line); } });
392
+ try {
393
+ const instance = this.instance();
394
+ if (!instance)
395
+ throw new Error('the viewer started without a recorded World instance');
396
+ await this.finishStartup(instance, fresh);
397
+ for (const line of announced)
398
+ opts.announce?.(line);
399
+ ready = true;
400
+ return view;
401
+ }
402
+ catch (error) {
403
+ try {
404
+ await view.stop();
405
+ }
406
+ catch (stopError) {
407
+ throw new AggregateError([error, stopError], 'World viewer startup failed and teardown was not confirmed; retained lifecycle evidence needs inspection');
408
+ }
409
+ throw error;
410
+ }
411
+ }
383
412
  // ── remotes ─────────────────────────────────────────────────────────────────────────────
384
413
  /** The vendors this world has a twin of, as world.json names them (a twin service's package, else its id). */
385
414
  vendors() {
@@ -13,7 +13,7 @@ You need Node 22.6+, npm and registry access during installation. The browser pa
13
13
  Copy this starter into a new folder:
14
14
 
15
15
  ```console
16
- $ npx @volter/world@3.0.146 example customer-onboarding --dir onboarding
16
+ $ npx @volter/world@3.0.148 example customer-onboarding --dir onboarding
17
17
  $ cd onboarding
18
18
  $ npm install
19
19
  ```
@@ -11,10 +11,10 @@
11
11
  "pg": "8.16.3"
12
12
  },
13
13
  "devDependencies": {
14
- "@volter/world": "3.0.146",
15
- "@volter/world-core": "3.0.146",
16
- "@volter/world-runtime": "3.0.146",
17
- "@volter/world-console": "3.0.141",
14
+ "@volter/world": "3.0.148",
15
+ "@volter/world-core": "3.0.147",
16
+ "@volter/world-runtime": "3.0.147",
17
+ "@volter/world-console": "3.0.142",
18
18
  "@volter/twin-stripe": "3.0.2",
19
19
  "@volter/twin-resend": "1.0.1"
20
20
  }
@@ -13,7 +13,7 @@ Give two workers the same Stripe baseline, let them change different existing cu
13
13
  Use an empty parent folder. Create the baseline starter there:
14
14
 
15
15
  ```console
16
- $ npx @volter/world@3.0.146 example integration-branches --dir baseline
16
+ $ npx @volter/world@3.0.148 example integration-branches --dir baseline
17
17
  $ cd baseline
18
18
  $ npm install
19
19
  $ ./node_modules/.bin/volter world init --name baseline --twins stripe
@@ -38,9 +38,9 @@ The stopped baseline retains its history and can supply local path remotes. Defa
38
38
  Create three more copies in the same parent folder:
39
39
 
40
40
  ```console
41
- $ npx @volter/world@3.0.146 example integration-branches --dir integration
42
- $ npx @volter/world@3.0.146 example integration-branches --dir worker-a
43
- $ npx @volter/world@3.0.146 example integration-branches --dir worker-b
41
+ $ npx @volter/world@3.0.148 example integration-branches --dir integration
42
+ $ npx @volter/world@3.0.148 example integration-branches --dir worker-a
43
+ $ npx @volter/world@3.0.148 example integration-branches --dir worker-b
44
44
  ```
45
45
 
46
46
  In `integration`, then `worker-a`, then `worker-b`, install dependencies and initialize with the folder's name. Before a first boot or app write, link and pull the baseline:
@@ -9,10 +9,10 @@
9
9
  "stripe": "17.7.0"
10
10
  },
11
11
  "devDependencies": {
12
- "@volter/world": "3.0.146",
13
- "@volter/world-core": "3.0.146",
14
- "@volter/world-runtime": "3.0.146",
15
- "@volter/world-console": "3.0.141",
12
+ "@volter/world": "3.0.148",
13
+ "@volter/world-core": "3.0.147",
14
+ "@volter/world-runtime": "3.0.147",
15
+ "@volter/world-console": "3.0.142",
16
16
  "@volter/twin-stripe": "3.0.2"
17
17
  }
18
18
  }
@@ -13,7 +13,7 @@ You need Node 22.6+, npm and registry access during installation. No platform ac
13
13
  ## Prepare an ordinary workplace
14
14
 
15
15
  ```console
16
- $ npx @volter/world@3.0.146 example workplace-agent --dir workplace
16
+ $ npx @volter/world@3.0.148 example workplace-agent --dir workplace
17
17
  $ cd workplace
18
18
  $ npm install
19
19
  $ ./node_modules/.bin/volter world init --name workplace --twins github,linear,slack
@@ -11,10 +11,10 @@
11
11
  "@slack/web-api": "7.19.0"
12
12
  },
13
13
  "devDependencies": {
14
- "@volter/world": "3.0.146",
15
- "@volter/world-core": "3.0.146",
16
- "@volter/world-runtime": "3.0.146",
17
- "@volter/world-console": "3.0.141",
14
+ "@volter/world": "3.0.148",
15
+ "@volter/world-core": "3.0.147",
16
+ "@volter/world-runtime": "3.0.147",
17
+ "@volter/world-console": "3.0.142",
18
18
  "@volter/twin-github": "3.0.6",
19
19
  "@volter/twin-linear": "3.0.4",
20
20
  "@volter/twin-slack": "3.0.5"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world",
3
- "version": "3.0.146",
3
+ "version": "3.0.148",
4
4
  "description": "Run your app against a world of twins. `World` and `Repo` — one method per verb: init, up, run, log, diff, reset, branch, checkout, clone, fetch, changeset, push — and the `volter` command, one client of them.",
5
5
  "keywords": [
6
6
  "volter",
@@ -31,12 +31,12 @@
31
31
  ],
32
32
  "dependencies": {
33
33
  "@modelcontextprotocol/sdk": "^1.30.1",
34
- "@volter/world-core": "3.0.146",
35
- "@volter/world-runtime": "3.0.146",
34
+ "@volter/world-core": "3.0.147",
35
+ "@volter/world-runtime": "3.0.147",
36
36
  "zod": "^4.6.5"
37
37
  },
38
38
  "peerDependencies": {
39
- "@volter/world-console": "3.0.141"
39
+ "@volter/world-console": "3.0.142"
40
40
  },
41
41
  "peerDependenciesMeta": {
42
42
  "@volter/world-console": {
@@ -44,7 +44,7 @@
44
44
  }
45
45
  },
46
46
  "devDependencies": {
47
- "@volter/world-console": "3.0.141"
47
+ "@volter/world-console": "3.0.142"
48
48
  },
49
49
  "bin": {
50
50
  "volter": "dist/src/cli.js"
package/src/world.ts CHANGED
@@ -138,18 +138,22 @@ export class World {
138
138
  ...(opts.mode ? { mode: opts.mode } : existing ? { mode: existing.mode } : {}),
139
139
  ...(existing ? { keepState: true } : {}),
140
140
  });
141
+ await this.finishStartup(instance, existing === null, opts);
142
+ return instance;
143
+ }
144
+ /** Product startup completes its seed sequence; the runtime owns only the boot. */
145
+ private async finishStartup(instance: WorldInstance, fresh: boolean, opts: { seed?: boolean; cwd?: string } = {}): Promise<void> {
141
146
  try {
142
- if (!existing && opts.seed !== false && existsSync(worldSeedPath(this.root))) {
147
+ if (fresh && opts.seed !== false && existsSync(worldSeedPath(this.root))) {
143
148
  // a story that fails is a loud failure: the world is up, but not the world the app expects
144
149
  const seeded = await seedWorld(this.name, { root: this.root, cwd: opts.cwd ?? this.root });
145
- if (seeded.exitCode !== 0) throw new Error(`the story failed (${seeded.entry} exited ${seeded.exitCode}); the world is up without it — fix the seed and \`volter world seed\``);
150
+ if (seeded.exitCode !== 0) throw new Error(`the story failed (${seeded.entry} exited ${seeded.exitCode}); state may be partially seeded — fix the seed, start this World and run \`volter world seed\``);
146
151
  }
147
152
  } finally {
148
153
  // a root set while the world was stopped reaches the twins' state after the default data (which a
149
154
  // fresh boot runs simulated), where the deploy reads it — also when the seed failed
150
155
  materializeRoots(loadWorldConfig(this.configRef(), this.root).config, instance.dirs.data, this.root);
151
156
  }
152
- return instance;
153
157
  }
154
158
  /** Stop the twins; the branch's state stays (`up` resumes). `purge` forgets it. */
155
159
  down(opts: { purge?: boolean } = {}): ReturnType<typeof downWorld> { return downWorld(this.name, this.root, opts); }
@@ -162,7 +166,8 @@ export class World {
162
166
 
163
167
  /** The kernel's instance record, or null before the first `up`. */
164
168
  instance(): ReturnType<typeof statusWorld> | null {
165
- try { return statusWorld(this.name, this.root); } catch { return null; }
169
+ if (!existsSync(join(instanceDir(this.root, this.name), 'instance.json'))) return null;
170
+ return statusWorld(this.name, this.root);
166
171
  }
167
172
  /** The world, the branch, the origin, what is unpushed, what is running. */
168
173
  status(): WorldStatus {
@@ -351,7 +356,25 @@ export class World {
351
356
  /** Serve this world on a port under `/<org>/<world>/`; returns when listening. */
352
357
  serve(opts: { port?: number; host?: string; consolePort?: number; announce?: (info: { name: string; base: string; token: string; readToken: string; console: string | null }) => void } = {}): Promise<ServedWorld> { return serveWorld(this.name, { root: this.root, ...opts }); }
353
358
  /** `volter world view`: this World, its twins' screens, its branches and the console on one origin (docs/contributing/architecture.md, "Viewing a World"). */
354
- view(opts: { port?: number; host?: string; console?: ViewConsole; announce?: (line: string) => void; origins?: boolean; origin?: string } = {}): Promise<WorldView> { const person = platformPerson(); return serveWorldView(this.name, { root: this.root, ...(person ? { localPerson: person } : {}), ...opts }); }
359
+ async view(opts: { port?: number; host?: string; console?: ViewConsole; announce?: (line: string) => void; origins?: boolean; origin?: string } = {}): Promise<WorldView> {
360
+ const fresh = this.instance() === null;
361
+ const person = platformPerson();
362
+ const announced: string[] = [];
363
+ let ready = false;
364
+ const view = await serveWorldView(this.name, { root: this.root, ...(person ? { localPerson: person } : {}), ...opts, announce: line => { if (ready) opts.announce?.(line); else announced.push(line); } });
365
+ try {
366
+ const instance = this.instance();
367
+ if (!instance) throw new Error('the viewer started without a recorded World instance');
368
+ await this.finishStartup(instance, fresh);
369
+ for (const line of announced) opts.announce?.(line);
370
+ ready = true;
371
+ return view;
372
+ } catch (error) {
373
+ try { await view.stop(); }
374
+ catch (stopError) { throw new AggregateError([error, stopError], 'World viewer startup failed and teardown was not confirmed; retained lifecycle evidence needs inspection'); }
375
+ throw error;
376
+ }
377
+ }
355
378
 
356
379
  // ── remotes ─────────────────────────────────────────────────────────────────────────────
357
380
  /** The vendors this world has a twin of, as world.json names them (a twin service's package, else its id). */