@volter/world-runtime 3.0.35 → 3.0.37
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/dist/src/browsers.d.ts +1 -1
- package/dist/src/browsers.js +1 -1
- package/dist/src/init.js +4 -4
- package/dist/src/local-branches.js +21 -3
- package/dist/src/served-world.js +7 -3
- package/package.json +4 -4
- package/src/browsers.ts +2 -2
- package/src/init.ts +4 -4
- package/src/local-branches.ts +19 -3
- package/src/served-world.ts +6 -3
package/dist/src/browsers.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export type WorldBrowser = {
|
|
|
8
8
|
export declare const browsersDir: (data: string) => string;
|
|
9
9
|
export declare function worldBrowser(data: string, name: string): WorldBrowser | null;
|
|
10
10
|
export declare function worldBrowsers(data: string): WorldBrowser[];
|
|
11
|
-
export declare function putWorldBrowser(data: string, name: string, state:
|
|
11
|
+
export declare function putWorldBrowser(data: string, name: string, state: unknown): WorldBrowser;
|
|
12
12
|
export declare function deleteWorldBrowser(data: string, name: string): void;
|
|
13
13
|
export declare function inheritWorldBrowsers(fromData: string, toData: string): void;
|
|
14
14
|
/** Derived files under the World state directory, written only by its current branch. */
|
package/dist/src/browsers.js
CHANGED
|
@@ -26,7 +26,7 @@ export function worldBrowser(data, name) {
|
|
|
26
26
|
held.text = text;
|
|
27
27
|
return held.record;
|
|
28
28
|
}
|
|
29
|
-
validateBrowserState(record.state);
|
|
29
|
+
record.state = validateBrowserState(record.state);
|
|
30
30
|
const freeze = (value) => {
|
|
31
31
|
if (!value || typeof value !== 'object')
|
|
32
32
|
return;
|
package/dist/src/init.js
CHANGED
|
@@ -1159,15 +1159,15 @@ export async function seed(): Promise<void> {
|
|
|
1159
1159
|
if (!door || !token) throw new Error('browser defaults run in a World seed with its browsers door and credential');
|
|
1160
1160
|
const held = await fetch(\`\${door}/\${encodeURIComponent(call.browser)}\`, { headers: { 'x-volter-token': token } });
|
|
1161
1161
|
if (!held.ok && held.status !== 404) throw new Error(\`browser \${call.browser}: \${held.status} \${await held.text()}\`);
|
|
1162
|
-
snapshots.set(call.browser, held.ok ? await held.json() as Awaited<ReturnType<SeedBrowser['state']>> : { cookies: [], origins: [] });
|
|
1162
|
+
snapshots.set(call.browser, held.ok ? await held.json() as Awaited<ReturnType<SeedBrowser['state']>> : { storageState: { cookies: [], origins: [] }, context: {} });
|
|
1163
1163
|
}
|
|
1164
1164
|
browserHelper ??= await import('@volter/world-core/browser').catch(error => {
|
|
1165
|
-
throw new Error('Install @volter/world-core 3.0.
|
|
1165
|
+
throw new Error('Install @volter/world-core 3.0.37 or later in this project to seed browsers.', { cause: error });
|
|
1166
1166
|
});
|
|
1167
1167
|
const initial = snapshots.get(call.browser)!;
|
|
1168
1168
|
const hostname = new URL(call.path).hostname;
|
|
1169
|
-
if (initial.cookies.some(cookie => browserHelper!.cookieMatchesHost(cookie, hostname))
|
|
1170
|
-
|| initial.origins.some(origin => new URL(origin.origin).hostname === hostname)) continue;
|
|
1169
|
+
if (initial.storageState.cookies.some(cookie => browserHelper!.cookieMatchesHost(cookie, hostname))
|
|
1170
|
+
|| initial.storageState.origins.some(origin => new URL(origin.origin).hostname === hostname)) continue;
|
|
1171
1171
|
}
|
|
1172
1172
|
const headers = { ...(call.body !== undefined ? { 'content-type': 'application/json' } : {}), ...(call.headers ?? {}) };
|
|
1173
1173
|
const form = /^application\\/x-www-form-urlencoded\\b/i.test(new Headers(headers).get('content-type') ?? '');
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// parent through its own doors (its history cut at an instant, when one is asked), its clock frozen at
|
|
4
4
|
// that instant so no twin's catch-up walks it forward, and removed when its time runs out. world-host
|
|
5
5
|
// and `volter world view` both make them with this; a hosted World's supervisor makes its own.
|
|
6
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { dirname, join } from 'node:path';
|
|
8
|
-
import { stateDirName, withStateRemoval } from '@volter/world-core/runtime';
|
|
8
|
+
import { readBranchMeta, stateDirName, withHistoryLock, withStateRemoval, worldPaths, worldStateRoot, writeBranchMeta } from '@volter/world-core/runtime';
|
|
9
9
|
import { inheritWorldBrowsers, materializeBrowserFiles } from "./browsers.js";
|
|
10
10
|
import { clockFile, statusWorld } from "./runtime.js";
|
|
11
11
|
import { loadWorldConfig } from "./configs.js";
|
|
@@ -169,7 +169,25 @@ export class LocalBranches {
|
|
|
169
169
|
writeFileSync(file, `${clock}\n`);
|
|
170
170
|
}
|
|
171
171
|
const child = statusWorld(id, root);
|
|
172
|
-
|
|
172
|
+
// The HTTP clone holds the history view here, but its URL parent holds no local resources.
|
|
173
|
+
// Retain the actual parent's state through the kernel's pointer/pin protocol: blob reads can
|
|
174
|
+
// follow it without copying uploads, while history keeps using the downloaded immutable view.
|
|
175
|
+
const base = statusWorld(world, from.root);
|
|
176
|
+
for (const service of Object.keys(child.services)) {
|
|
177
|
+
const control = join(child.dirs.data, service);
|
|
178
|
+
for (const state of readdirSync(worldStateRoot(control), { withFileTypes: true })) {
|
|
179
|
+
if (!state.isDirectory())
|
|
180
|
+
continue;
|
|
181
|
+
const directory = worldPaths(state.name, control).dir;
|
|
182
|
+
withHistoryLock(directory, () => {
|
|
183
|
+
const meta = readBranchMeta(state.name, control);
|
|
184
|
+
if (!meta?.parent)
|
|
185
|
+
throw new Error(`cloning ${parent}: ${service}/${state.name} has no parent`);
|
|
186
|
+
writeBranchMeta(state.name, { ...meta, parent: { ...meta.parent, at: join(base.dirs.data, service), viewDirectory: directory } }, control);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
inheritWorldBrowsers(base.dirs.data, child.dirs.data);
|
|
173
191
|
materializeBrowserFiles(child.dirs.data, root, id, child.config);
|
|
174
192
|
const createdAt = new Date().toISOString();
|
|
175
193
|
const expiresAt = ttlSeconds === null ? null : new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
package/dist/src/served-world.js
CHANGED
|
@@ -32,7 +32,7 @@ import { boardFrameKey, getActiveWorldStore, withHistoryLocks, captureHistory, h
|
|
|
32
32
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
33
33
|
import { CALLER_HEADER, callerForHeader, newTraceparent, parseTraceparent, traceparentForDelivery, readTwinRequestJournalFrom, assertSafeChangesetName, blobDigest, destinationOf, getActiveBlobStore, journalTwinRequest, loadCheck, twinRequestJournalFailures, openSealedAt, readTwinRequestJournal, serveHttp, twinRequestCredentials, TWIN_PREFIX_HEADER, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteUrlOf, ORIGINAL_PATH_HEADER, validateRemoteOrigin } from '@volter/world-core/runtime';
|
|
34
34
|
import { resolveTwinHost } from '@volter/world-core/host-routing';
|
|
35
|
-
import { assertBrowserName, validateBrowserState, cookieMatchesHost } from '@volter/world-core';
|
|
35
|
+
import { assertBrowserName, validateBrowserState, browserAcceptLanguage, cookieMatchesHost } from '@volter/world-core';
|
|
36
36
|
import { worldBrowser, worldBrowsers, putWorldBrowser, deleteWorldBrowser, materializeBrowserFiles, browserLoad, putBrowserLoad, browserLoadId } from "./browsers.js";
|
|
37
37
|
import { APP_READ_ENDPOINT_ENV } from "./init.js";
|
|
38
38
|
import { packFacts, packHostRules } from "./pack-facts.js";
|
|
@@ -1129,8 +1129,8 @@ export class WorldDoors {
|
|
|
1129
1129
|
return null;
|
|
1130
1130
|
if (request.method !== 'GET' || !['document', 'iframe'].includes(request.headers.get('sec-fetch-dest') ?? ''))
|
|
1131
1131
|
return null;
|
|
1132
|
-
const cookies = browser.state.cookies.filter(c => cookieMatchesHost(c, site));
|
|
1133
|
-
const storage = browser.state.origins.find(o => new URL(o.origin).hostname === site)?.localStorage ?? [];
|
|
1132
|
+
const cookies = browser.state.storageState.cookies.filter(c => cookieMatchesHost(c, site));
|
|
1133
|
+
const storage = browser.state.storageState.origins.find(o => new URL(o.origin).hostname === site)?.localStorage ?? [];
|
|
1134
1134
|
const headers = new Headers({ 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'referrer-policy': 'no-referrer' });
|
|
1135
1135
|
const protectedCookie = (key) => /^(?:__Host-)?volter_(?:world|pages|browser)$/.test(key);
|
|
1136
1136
|
const expired = new Map();
|
|
@@ -1240,6 +1240,10 @@ export class WorldDoors {
|
|
|
1240
1240
|
headers.set(name, v);
|
|
1241
1241
|
}
|
|
1242
1242
|
if (browser) {
|
|
1243
|
+
const profile = worldBrowser(this.up().dirs.data, browser)?.state;
|
|
1244
|
+
const language = profile ? browserAcceptLanguage(profile.context) : undefined;
|
|
1245
|
+
if (language !== undefined)
|
|
1246
|
+
headers.set('accept-language', language);
|
|
1243
1247
|
const cookies = (request.headers.get('cookie') ?? '').split(/;\s*/).filter(c => !/^(?:__Host-)?volter_(?:world|pages|browser)=/.test(c));
|
|
1244
1248
|
if (cookies.length)
|
|
1245
1249
|
headers.set('cookie', cookies.join('; '));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-runtime",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.37",
|
|
4
4
|
"description": "World configs for twins: boot named local runtimes, allocate ports, generate world.env/instance.json, and run apps against fake-key twin worlds.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twin",
|
|
@@ -68,14 +68,14 @@
|
|
|
68
68
|
"node": ">=22.3"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
|
-
"@volter/world-core": "3.0.
|
|
72
|
-
"@volter/world-console": "3.0.
|
|
71
|
+
"@volter/world-core": "3.0.37",
|
|
72
|
+
"@volter/world-console": "3.0.32"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"@electric-sql/pglite": "0.5.8",
|
|
76
76
|
"@electric-sql/pglite-pgvector": "0.0.9",
|
|
77
77
|
"@volter/world-access": "3.0.1",
|
|
78
|
-
"@volter/world-core": "3.0.
|
|
78
|
+
"@volter/world-core": "3.0.37",
|
|
79
79
|
"pg-gateway": "0.3.0-beta.4",
|
|
80
80
|
"smol-toml": "^1.8.0",
|
|
81
81
|
"yaml": "2.9.0"
|
package/src/browsers.ts
CHANGED
|
@@ -20,7 +20,7 @@ export function worldBrowser(data: string, name: string): WorldBrowser | null {
|
|
|
20
20
|
const record = JSON.parse(text) as WorldBrowser;
|
|
21
21
|
if (record.name !== name || typeof record.version !== 'string') throw new Error(`invalid browser record: ${name}`);
|
|
22
22
|
if (held?.version === record.version) { held.text = text; return held.record; }
|
|
23
|
-
validateBrowserState(record.state);
|
|
23
|
+
record.state = validateBrowserState(record.state);
|
|
24
24
|
const freeze = (value: unknown): void => {
|
|
25
25
|
if (!value || typeof value !== 'object') return;
|
|
26
26
|
for (const item of Object.values(value)) freeze(item);
|
|
@@ -33,7 +33,7 @@ export function worldBrowser(data: string, name: string): WorldBrowser | null {
|
|
|
33
33
|
export function worldBrowsers(data: string): WorldBrowser[] {
|
|
34
34
|
return getActiveWorldStore().list(browsersDir(data)).filter(f => f.endsWith('.json')).sort().map(f => worldBrowser(data, f.slice(0, -5))!);
|
|
35
35
|
}
|
|
36
|
-
export function putWorldBrowser(data: string, name: string, state:
|
|
36
|
+
export function putWorldBrowser(data: string, name: string, state: unknown): WorldBrowser {
|
|
37
37
|
const record = { name, state: validateBrowserState(state), version: randomUUID() };
|
|
38
38
|
getActiveWorldStore().writeAtomic(path(data, name), `${JSON.stringify(record)}\n`, { secret: true });
|
|
39
39
|
retireBrowserLoads(data, name, record);
|
package/src/init.ts
CHANGED
|
@@ -1346,15 +1346,15 @@ export async function seed(): Promise<void> {
|
|
|
1346
1346
|
if (!door || !token) throw new Error('browser defaults run in a World seed with its browsers door and credential');
|
|
1347
1347
|
const held = await fetch(\`\${door}/\${encodeURIComponent(call.browser)}\`, { headers: { 'x-volter-token': token } });
|
|
1348
1348
|
if (!held.ok && held.status !== 404) throw new Error(\`browser \${call.browser}: \${held.status} \${await held.text()}\`);
|
|
1349
|
-
snapshots.set(call.browser, held.ok ? await held.json() as Awaited<ReturnType<SeedBrowser['state']>> : { cookies: [], origins: [] });
|
|
1349
|
+
snapshots.set(call.browser, held.ok ? await held.json() as Awaited<ReturnType<SeedBrowser['state']>> : { storageState: { cookies: [], origins: [] }, context: {} });
|
|
1350
1350
|
}
|
|
1351
1351
|
browserHelper ??= await import('@volter/world-core/browser').catch(error => {
|
|
1352
|
-
throw new Error('Install @volter/world-core 3.0.
|
|
1352
|
+
throw new Error('Install @volter/world-core 3.0.37 or later in this project to seed browsers.', { cause: error });
|
|
1353
1353
|
});
|
|
1354
1354
|
const initial = snapshots.get(call.browser)!;
|
|
1355
1355
|
const hostname = new URL(call.path).hostname;
|
|
1356
|
-
if (initial.cookies.some(cookie => browserHelper!.cookieMatchesHost(cookie, hostname))
|
|
1357
|
-
|| initial.origins.some(origin => new URL(origin.origin).hostname === hostname)) continue;
|
|
1356
|
+
if (initial.storageState.cookies.some(cookie => browserHelper!.cookieMatchesHost(cookie, hostname))
|
|
1357
|
+
|| initial.storageState.origins.some(origin => new URL(origin.origin).hostname === hostname)) continue;
|
|
1358
1358
|
}
|
|
1359
1359
|
const headers = { ...(call.body !== undefined ? { 'content-type': 'application/json' } : {}), ...(call.headers ?? {}) };
|
|
1360
1360
|
const form = /^application\\/x-www-form-urlencoded\\b/i.test(new Headers(headers).get('content-type') ?? '');
|
package/src/local-branches.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// parent through its own doors (its history cut at an instant, when one is asked), its clock frozen at
|
|
4
4
|
// that instant so no twin's catch-up walks it forward, and removed when its time runs out. world-host
|
|
5
5
|
// and `volter world view` both make them with this; a hosted World's supervisor makes its own.
|
|
6
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { dirname, join } from 'node:path';
|
|
8
|
-
import { stateDirName, withStateRemoval } from '@volter/world-core/runtime';
|
|
8
|
+
import { readBranchMeta, stateDirName, withHistoryLock, withStateRemoval, worldPaths, worldStateRoot, writeBranchMeta } from '@volter/world-core/runtime';
|
|
9
9
|
import { inheritWorldBrowsers, materializeBrowserFiles } from './browsers.ts';
|
|
10
10
|
import { clockFile, statusWorld } from './runtime.ts';
|
|
11
11
|
import { loadWorldConfig } from './configs.ts';
|
|
@@ -162,7 +162,23 @@ export class LocalBranches {
|
|
|
162
162
|
const clock = at.instant ?? this.frozenClock(from) ?? (at.live ? null : new Date().toISOString());
|
|
163
163
|
if (clock) { const file = clockFile(root, id); mkdirSync(dirname(file), { recursive: true }); writeFileSync(file, `${clock}\n`); }
|
|
164
164
|
const child = statusWorld(id, root);
|
|
165
|
-
|
|
165
|
+
// The HTTP clone holds the history view here, but its URL parent holds no local resources.
|
|
166
|
+
// Retain the actual parent's state through the kernel's pointer/pin protocol: blob reads can
|
|
167
|
+
// follow it without copying uploads, while history keeps using the downloaded immutable view.
|
|
168
|
+
const base = statusWorld(world, from.root);
|
|
169
|
+
for (const service of Object.keys(child.services)) {
|
|
170
|
+
const control = join(child.dirs.data, service);
|
|
171
|
+
for (const state of readdirSync(worldStateRoot(control), { withFileTypes: true })) {
|
|
172
|
+
if (!state.isDirectory()) continue;
|
|
173
|
+
const directory = worldPaths(state.name, control).dir;
|
|
174
|
+
withHistoryLock(directory, () => {
|
|
175
|
+
const meta = readBranchMeta(state.name, control);
|
|
176
|
+
if (!meta?.parent) throw new Error(`cloning ${parent}: ${service}/${state.name} has no parent`);
|
|
177
|
+
writeBranchMeta(state.name, { ...meta, parent: { ...meta.parent, at: join(base.dirs.data, service), viewDirectory: directory } }, control);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
inheritWorldBrowsers(base.dirs.data, child.dirs.data);
|
|
166
182
|
materializeBrowserFiles(child.dirs.data, root, id, child.config);
|
|
167
183
|
const createdAt = new Date().toISOString();
|
|
168
184
|
const expiresAt = ttlSeconds === null ? null : new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
package/src/served-world.ts
CHANGED
|
@@ -32,7 +32,7 @@ import { boardFrameKey, getActiveWorldStore, withHistoryLocks, captureHistory, h
|
|
|
32
32
|
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync , rmSync} from 'node:fs';
|
|
33
33
|
import { CALLER_HEADER, callerForHeader, newTraceparent, parseTraceparent, traceparentForDelivery, readTwinRequestJournalFrom, type TwinRequestJournalCursor, assertSafeChangesetName, blobDigest, destinationOf, getActiveBlobStore, journalTwinRequest, loadCheck, twinRequestJournalFailures, openSealedAt, readTwinRequestJournal, serveHttp, twinRequestCredentials, TWIN_PREFIX_HEADER, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteLabel, siteUrlOf, ORIGINAL_PATH_HEADER, validateRemoteOrigin, type SealedCredential, type TwinStreamConnection, type TwinStreamSink, type BoardFrame } from '@volter/world-core/runtime';
|
|
34
34
|
import { resolveTwinHost, type HostPredicate } from '@volter/world-core/host-routing';
|
|
35
|
-
import { assertBrowserName, validateBrowserState, cookieMatchesHost } from '@volter/world-core';
|
|
35
|
+
import { assertBrowserName, validateBrowserState, browserAcceptLanguage, cookieMatchesHost } from '@volter/world-core';
|
|
36
36
|
import { worldBrowser, worldBrowsers, putWorldBrowser, deleteWorldBrowser, materializeBrowserFiles, browserLoad, putBrowserLoad, browserLoadId } from './browsers.ts';
|
|
37
37
|
import { APP_READ_ENDPOINT_ENV } from './init.ts';
|
|
38
38
|
import { packFacts, packHostRules } from './pack-facts.ts';
|
|
@@ -1024,8 +1024,8 @@ export class WorldDoors {
|
|
|
1024
1024
|
const carried = [current, previous].filter(load => load?.name === name && load.site === site);
|
|
1025
1025
|
if (id === loadId && current?.name === name && current.site === site && current.version === browser.version) return null;
|
|
1026
1026
|
if (request.method !== 'GET' || !['document', 'iframe'].includes(request.headers.get('sec-fetch-dest') ?? '')) return null;
|
|
1027
|
-
const cookies = browser.state.cookies.filter(c => cookieMatchesHost(c, site));
|
|
1028
|
-
const storage = browser.state.origins.find(o => new URL(o.origin).hostname === site)?.localStorage ?? [];
|
|
1027
|
+
const cookies = browser.state.storageState.cookies.filter(c => cookieMatchesHost(c, site));
|
|
1028
|
+
const storage = browser.state.storageState.origins.find(o => new URL(o.origin).hostname === site)?.localStorage ?? [];
|
|
1029
1029
|
const headers = new Headers({ 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'referrer-policy': 'no-referrer' });
|
|
1030
1030
|
const protectedCookie = (key: string): boolean => /^(?:__Host-)?volter_(?:world|pages|browser)$/.test(key);
|
|
1031
1031
|
const expired = new Map<string, { name: string; path: string }>();
|
|
@@ -1111,6 +1111,9 @@ export class WorldDoors {
|
|
|
1111
1111
|
for (const key of [TOKEN_HEADER, TWINS_KEY_HEADER, CALLER_HEADER, ORIGINAL_PATH_HEADER, READ_ONLY_HEADER, 'host', TWIN_PREFIX_HEADER, TWIN_SITES_HEADER, TWIN_SITE_HEADER]) headers.delete(key);
|
|
1112
1112
|
for (const name of ['accept', 'accept-language', 'if-none-match', 'range', 'user-agent']) { const v = request.headers.get(name); if (v !== null) headers.set(name, v); }
|
|
1113
1113
|
if (browser) {
|
|
1114
|
+
const profile = worldBrowser(this.up().dirs.data, browser)?.state;
|
|
1115
|
+
const language = profile ? browserAcceptLanguage(profile.context) : undefined;
|
|
1116
|
+
if (language !== undefined) headers.set('accept-language', language);
|
|
1114
1117
|
const cookies = (request.headers.get('cookie') ?? '').split(/;\s*/).filter(c => !/^(?:__Host-)?volter_(?:world|pages|browser)=/.test(c));
|
|
1115
1118
|
if (cookies.length) headers.set('cookie', cookies.join('; '));
|
|
1116
1119
|
headers.set('x-volter-world-browser', browser);
|