@volter/world-host 3.0.12 → 3.0.14

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.
@@ -17,7 +17,7 @@
17
17
  import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
18
18
  import { assertStateRemovable, serveHttp, stateDirName, withAncestryLock, withStateRemoval } from '@volter/world-core/runtime';
19
19
  import { dirname, join, resolve } from 'node:path';
20
- import { assertWorldStateRemovable, resolveCatalog, CONSOLE_BASE, consoleRedirect, initWorld, LocalBranches, loadWorldConfig, localWorldOrigin, mountWorld, pathOfWorld, worldOriginLabel, serveConsoleApart, siteOfHost, worldDoorUnderConsole, TOKEN_HEADER, worldOfHost } from '@volter/world-runtime';
20
+ import { assertWorldStateRemovable, resolveCatalog, CONSOLE_BASE, consoleRedirect, followParentPins, initWorld, LocalBranches, loadWorldConfig, localWorldOrigin, mountWorld, pagesOfHost, pagesOriginOf, pathOfWorld, worldOriginLabel, serveConsoleApart, siteOfHost, worldDoorUnderConsole, TOKEN_HEADER, worldOfHost } from '@volter/world-runtime';
21
21
  import { issuerKeys, trustableIssuer } from '@volter/world-access';
22
22
  export { CONSOLE_BASE };
23
23
  const NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
@@ -115,6 +115,10 @@ export async function createWorldHost(options) {
115
115
  : (served) => (port ? localWorldOrigin(served, port) : null);
116
116
  // sites are routed only at this machine's loopback origins (the front's siteOfHost)
117
117
  const sitesOrigin = origins && !originBase ? (served) => (port ? localWorldOrigin(served, port) : null) : undefined;
118
+ // each World's content plane beside its origin (`<world>--<org>--pages`): its twins' pages, apart from its doors
119
+ const pagesOrigin = !origins ? undefined : originBase
120
+ ? (served) => pagesOriginOf(served, advertised?.protocol ?? 'https:', originBase, advertised?.port || undefined)
121
+ : (served) => (port ? pagesOriginOf(served, 'http:', 'localhost', port) : null);
118
122
  // one key reader per trusted platform, shared by every World here (each caches its platform's key set)
119
123
  // https only, or http on this machine: keys read over plain http elsewhere could be swapped, and a swapped key forges passes
120
124
  const passIssuers = (options.trust ?? []).map((origin) => {
@@ -128,8 +132,22 @@ export async function createWorldHost(options) {
128
132
  const branches = new LocalBranches({ dir, origin: () => loopback, worlds: byServed, mount: (root) => mount({ name: '', root }), ...(options.announce ? { announce: options.announce } : {}) });
129
133
  const mount = async (w) => {
130
134
  const config = loadWorldConfig(join(w.root, '.volter', 'world.json'), w.root).config;
131
- return mountWorld(config.id, { root: w.root, branches: (served) => branches.doorsFor(served), ...(browserOrigin ? { browserOrigin } : {}), ...(sitesOrigin ? { sitesOrigin } : {}), ...(passIssuers.length ? { passIssuers } : {}) });
135
+ return mountWorld(config.id, { root: w.root, branches: (served) => branches.doorsFor(served), ...(browserOrigin ? { browserOrigin } : {}), ...(sitesOrigin ? { sitesOrigin } : {}), ...(pagesOrigin ? { pagesOrigin } : {}), ...(passIssuers.length ? { passIssuers } : {}) });
132
136
  };
137
+ // a branch beside its parent takes the parent's twin versions before it mounts (followParentPins), parents first: a
138
+ // branch of a branch follows its parent only once that parent has followed its own
139
+ const fromOf = new Map(found.map((w) => { try {
140
+ return [w.name, JSON.parse(readFileSync(join(w.root, stateDirName(), 'branch.json'), 'utf8')).from];
141
+ }
142
+ catch {
143
+ return [w.name, undefined];
144
+ } }));
145
+ const depth = (name, seen = new Set()) => { const up = fromOf.get(name); return up && fromOf.has(up) && !seen.has(up) ? 1 + depth(up, seen.add(name)) : 0; };
146
+ for (const w of [...found].sort((a, b) => depth(a.name) - depth(b.name))) {
147
+ const parent = found.find((p) => p.name === fromOf.get(w.name));
148
+ if (parent)
149
+ followParentPins(parent.root, w.root);
150
+ }
133
151
  for (const w of found)
134
152
  byServed.set(w.name, await mount(w));
135
153
  const host = options.host ?? '127.0.0.1';
@@ -281,6 +299,10 @@ export async function createWorldHost(options) {
281
299
  const site = origins && !originBase ? siteOfHost(at.hostname, byServed.keys()) : undefined;
282
300
  if (site)
283
301
  return byServed.get(site.world).site(request, site.site);
302
+ // a World's content plane serves that World's twins' pages and nothing else of this host's (no console, no admin)
303
+ const pagesFor = origins ? pagesOfHost(at.hostname, byServed.keys(), originBase) : undefined;
304
+ if (pagesFor)
305
+ return path === '/-/ping' ? json({ ok: true, worlds: [pagesFor] }) : byServed.get(pagesFor).handle(request);
284
306
  const own = origins ? worldOfHost(at.hostname, byServed.keys(), originBase) : undefined;
285
307
  if (own === null)
286
308
  return json({ error: `no world at ${at.host}` }, 404);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-host",
3
- "version": "3.0.12",
3
+ "version": "3.0.14",
4
4
  "description": "The hosted remote: worlds under one URL, each holding the canonical history of a real vendor account and its sealed credential — what a local world clones from and pushes to.",
5
5
  "keywords": [
6
6
  "twin",
@@ -27,11 +27,11 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@volter/world-access": "3.0.1",
30
- "@volter/world-core": "3.0.12",
31
- "@volter/world-runtime": "3.0.12"
30
+ "@volter/world-core": "3.0.14",
31
+ "@volter/world-runtime": "3.0.14"
32
32
  },
33
33
  "peerDependencies": {
34
- "@volter/world-console": "3.0.7"
34
+ "@volter/world-console": "3.0.9"
35
35
  },
36
36
  "peerDependenciesMeta": {
37
37
  "@volter/world-console": {
@@ -39,7 +39,7 @@
39
39
  }
40
40
  },
41
41
  "devDependencies": {
42
- "@volter/world-console": "3.0.7"
42
+ "@volter/world-console": "3.0.9"
43
43
  },
44
44
  "exports": {
45
45
  ".": {
package/src/twins-host.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
18
18
  import { assertStateRemovable, serveHttp, stateDirName, withAncestryLock, withStateRemoval } from '@volter/world-core/runtime';
19
19
  import { dirname, join, resolve } from 'node:path';
20
- import { assertWorldStateRemovable, resolveCatalog, CONSOLE_BASE, consoleRedirect, initWorld, LocalBranches, loadWorldConfig, localWorldOrigin, mountWorld, pathOfWorld, worldOriginLabel, serveConsoleApart, siteOfHost, worldDoorUnderConsole, TOKEN_HEADER, worldOfHost, type ConsoleMount, type MountedWorld } from '@volter/world-runtime';
20
+ import { assertWorldStateRemovable, resolveCatalog, CONSOLE_BASE, consoleRedirect, followParentPins, initWorld, LocalBranches, loadWorldConfig, localWorldOrigin, mountWorld, pagesOfHost, pagesOriginOf, pathOfWorld, worldOriginLabel, serveConsoleApart, siteOfHost, worldDoorUnderConsole, TOKEN_HEADER, worldOfHost, type ConsoleMount, type MountedWorld } from '@volter/world-runtime';
21
21
 
22
22
  import { issuerKeys, trustableIssuer, type TrustedIssuer } from '@volter/world-access';
23
23
 
@@ -124,6 +124,10 @@ export async function createWorldHost(options: WorldHostOptions): Promise<WorldH
124
124
  : (served: string) => (port ? localWorldOrigin(served, port) : null);
125
125
  // sites are routed only at this machine's loopback origins (the front's siteOfHost)
126
126
  const sitesOrigin = origins && !originBase ? (served: string) => (port ? localWorldOrigin(served, port) : null) : undefined;
127
+ // each World's content plane beside its origin (`<world>--<org>--pages`): its twins' pages, apart from its doors
128
+ const pagesOrigin = !origins ? undefined : originBase
129
+ ? (served: string) => pagesOriginOf(served, advertised?.protocol ?? 'https:', originBase, advertised?.port || undefined)
130
+ : (served: string) => (port ? pagesOriginOf(served, 'http:', 'localhost', port) : null);
127
131
  // one key reader per trusted platform, shared by every World here (each caches its platform's key set)
128
132
  // https only, or http on this machine: keys read over plain http elsewhere could be swapped, and a swapped key forges passes
129
133
  const passIssuers: TrustedIssuer[] = (options.trust ?? []).map((origin) => {
@@ -136,8 +140,16 @@ export async function createWorldHost(options: WorldHostOptions): Promise<WorldH
136
140
  const branches: LocalBranches = new LocalBranches({ dir, origin: () => loopback, worlds: byServed, mount: (root) => mount({ name: '', root }), ...(options.announce ? { announce: options.announce } : {}) });
137
141
  const mount = async (w: { name: string; root: string }): Promise<MountedWorld> => {
138
142
  const config = loadWorldConfig(join(w.root, '.volter', 'world.json'), w.root).config;
139
- return mountWorld(config.id, { root: w.root, branches: (served) => branches.doorsFor(served), ...(browserOrigin ? { browserOrigin } : {}), ...(sitesOrigin ? { sitesOrigin } : {}), ...(passIssuers.length ? { passIssuers } : {}) });
143
+ return mountWorld(config.id, { root: w.root, branches: (served) => branches.doorsFor(served), ...(browserOrigin ? { browserOrigin } : {}), ...(sitesOrigin ? { sitesOrigin } : {}), ...(pagesOrigin ? { pagesOrigin } : {}), ...(passIssuers.length ? { passIssuers } : {}) });
140
144
  };
145
+ // a branch beside its parent takes the parent's twin versions before it mounts (followParentPins), parents first: a
146
+ // branch of a branch follows its parent only once that parent has followed its own
147
+ const fromOf = new Map(found.map((w) => { try { return [w.name, (JSON.parse(readFileSync(join(w.root, stateDirName(), 'branch.json'), 'utf8')) as { from?: string }).from] as const; } catch { return [w.name, undefined] as const; } }));
148
+ const depth = (name: string, seen = new Set<string>()): number => { const up = fromOf.get(name); return up && fromOf.has(up) && !seen.has(up) ? 1 + depth(up, seen.add(name)) : 0; };
149
+ for (const w of [...found].sort((a, b) => depth(a.name) - depth(b.name))) {
150
+ const parent = found.find((p) => p.name === fromOf.get(w.name));
151
+ if (parent) followParentPins(parent.root, w.root);
152
+ }
141
153
  for (const w of found) byServed.set(w.name, await mount(w));
142
154
  const host = options.host ?? '127.0.0.1';
143
155
  let url = '';
@@ -245,6 +257,9 @@ export async function createWorldHost(options: WorldHostOptions): Promise<WorldH
245
257
  // this machine's loopback origins, where the World's own page needs no token either
246
258
  const site = origins && !originBase ? siteOfHost(at.hostname, byServed.keys()) : undefined;
247
259
  if (site) return byServed.get(site.world)!.site(request, site.site);
260
+ // a World's content plane serves that World's twins' pages and nothing else of this host's (no console, no admin)
261
+ const pagesFor = origins ? pagesOfHost(at.hostname, byServed.keys(), originBase) : undefined;
262
+ if (pagesFor) return path === '/-/ping' ? json({ ok: true, worlds: [pagesFor] }) : byServed.get(pagesFor)!.handle(request);
248
263
  const own = origins ? worldOfHost(at.hostname, byServed.keys(), originBase) : undefined;
249
264
  if (own === null) return json({ error: `no world at ${at.host}` }, 404);
250
265
  if (own && !pathOfWorld(path, own, CONSOLE_BASE)) return json({ error: `this origin serves ${own} only` }, 404);