@volter/world-runtime 3.0.25 → 3.0.26
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/served-world.js +26 -1
- package/package.json +4 -4
- package/src/served-world.ts +23 -1
package/dist/src/served-world.js
CHANGED
|
@@ -19,6 +19,7 @@ import { getActiveWorldStore, withHistoryLocks, captureHistory, historyAtInstant
|
|
|
19
19
|
// GET/PUT clock, POST clock/advance the frozen instant every twin stamps from, forward only
|
|
20
20
|
// GET/POST branches, DELETE branches/<org>/<world> the host's branches of this World ("as of" views)
|
|
21
21
|
// GET history?at=<instant> each twin's history cut at an instant (where such a branch starts)
|
|
22
|
+
// GET/POST marks where the World's changes count from; POST { note? } marks it now
|
|
22
23
|
// GET /<org>/<world>/<vendor>/<path> the vendor's own screens, the pack's workspaces (its `GET /twin` lists them)
|
|
23
24
|
// Tokens ride `x-volter-token`: the token opens everything, the read token only reads — on the vendor
|
|
24
25
|
// wire, a twin that enforces it (`requestScopes: ['read']`) is handed the read token's other requests
|
|
@@ -33,7 +34,7 @@ import { grantOf, presentedSecrets, sameSecret, spentPasses, verifyPass } from '
|
|
|
33
34
|
import { createHash } from 'node:crypto';
|
|
34
35
|
import { dirname, join, resolve } from 'node:path';
|
|
35
36
|
import { appendActionIfAbsent, branchEntries, branchLogPath, branchMetaPath, changesetContentHash, changesetReadiness, confirmAction, parentLogPath, readTree, stateDirName, wholeLog, worldNow, parentEntries, readBranchMeta, rebaseBranch, } from '@volter/world-core/runtime';
|
|
36
|
-
import { approveWorldChangeset, createWorldChangeset, diffWorld, findWorldChangeset, listWorldChangesets, pushWorldChangeset, verifyWorldChangeset, worldChangesetsDir } from "./changeset.js";
|
|
37
|
+
import { approveWorldChangeset, createWorldChangeset, diffWorld, findWorldChangeset, listWorldChangesets, listWorldMarks, markWorld, MARK_NOTE_MAX, pushWorldChangeset, verifyWorldChangeset, worldChangesetsDir } from "./changeset.js";
|
|
37
38
|
import { fetchFromOrigin } from "./origin.js";
|
|
38
39
|
import { CONSOLE_BASE, consoleRedirect, serveConsoleApart } from "./console-apart.js";
|
|
39
40
|
import { loadWorldConfig, worldChangesetRules, writeWorldConfig } from "./configs.js";
|
|
@@ -1543,6 +1544,30 @@ export class WorldDoors {
|
|
|
1543
1544
|
views[vendor] = historyAtInstant(this.stateOf(vendor), instant, this.controlRoot(vendor)); });
|
|
1544
1545
|
return Response.json({ world: this.served, at: new Date(Date.parse(instant)).toISOString(), views });
|
|
1545
1546
|
}
|
|
1547
|
+
// THE WORLD'S MARKS (`GET marks`, `POST marks { note? }`): where its changes count from. A World seeded through its
|
|
1548
|
+
// doors (a hosted one has no seed run to land its starting data as already there) is marked once it is set up, so
|
|
1549
|
+
// what it starts with is not counted as its changes; the latest mark is the base of the diff, the board's Changes
|
|
1550
|
+
// and a new changeset, as `volter-world mark <world>` makes it on this machine
|
|
1551
|
+
if (kind === 'marks' && !name && request.method === 'GET')
|
|
1552
|
+
return Response.json({ world: this.served, marks: listWorldMarks(this.name, this.worldRoot) });
|
|
1553
|
+
if (kind === 'marks' && !name && request.method === 'POST') {
|
|
1554
|
+
const body = (await request.json().catch(() => ({})));
|
|
1555
|
+
if (!body || typeof body !== 'object' || Array.isArray(body))
|
|
1556
|
+
return Response.json({ error: 'a mark is sent as a JSON object' }, { status: 400 });
|
|
1557
|
+
if (body.note !== undefined && (typeof body.note !== 'string' || body.note.length > MARK_NOTE_MAX))
|
|
1558
|
+
return Response.json({ error: `a mark's note is a string of at most ${MARK_NOTE_MAX} characters` }, { status: 400 });
|
|
1559
|
+
const vendors = Object.keys(this.up().services);
|
|
1560
|
+
// taken while no twin writes, so it stands at one position of every log
|
|
1561
|
+
const { mark, passed } = withHistoryLocks(vendors.map((vendor) => worldPaths(this.stateOf(vendor), this.controlRoot(vendor)).dir), () => {
|
|
1562
|
+
const delta = diffWorld(this.name, { root: this.worldRoot });
|
|
1563
|
+
// what another World pushed here is in its changeset already: never this World's uncut changes
|
|
1564
|
+
const received = new Set(listWorldChangesets({ root: this.worldRoot, world: this.name }).filter((l) => l.changeset.pushedBy).flatMap((l) => l.changeset.actions.map((a) => a.action.id)));
|
|
1565
|
+
const passed = delta.actions.filter((a) => !received.has(a.action.id)).length;
|
|
1566
|
+
const mark = markWorld(this.name, { root: this.worldRoot, ...(typeof body.note === 'string' && body.note.trim() ? { note: body.note } : {}) });
|
|
1567
|
+
return { mark, passed };
|
|
1568
|
+
});
|
|
1569
|
+
return Response.json({ world: this.served, mark, passed }, { status: 201 });
|
|
1570
|
+
}
|
|
1546
1571
|
if (kind === 'map' && !name && request.method === 'GET')
|
|
1547
1572
|
return Response.json(await this.map());
|
|
1548
1573
|
if (kind === 'board' && !name && request.method === 'GET')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-runtime",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.26",
|
|
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.26",
|
|
72
|
+
"@volter/world-console": "3.0.21"
|
|
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.26",
|
|
79
79
|
"pg-gateway": "0.3.0-beta.4",
|
|
80
80
|
"smol-toml": "^1.8.0",
|
|
81
81
|
"yaml": "2.9.0"
|
package/src/served-world.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { getActiveWorldStore, withHistoryLocks, captureHistory, historyAtInstant
|
|
|
19
19
|
// GET/PUT clock, POST clock/advance the frozen instant every twin stamps from, forward only
|
|
20
20
|
// GET/POST branches, DELETE branches/<org>/<world> the host's branches of this World ("as of" views)
|
|
21
21
|
// GET history?at=<instant> each twin's history cut at an instant (where such a branch starts)
|
|
22
|
+
// GET/POST marks where the World's changes count from; POST { note? } marks it now
|
|
22
23
|
// GET /<org>/<world>/<vendor>/<path> the vendor's own screens, the pack's workspaces (its `GET /twin` lists them)
|
|
23
24
|
// Tokens ride `x-volter-token`: the token opens everything, the read token only reads — on the vendor
|
|
24
25
|
// wire, a twin that enforces it (`requestScopes: ['read']`) is handed the read token's other requests
|
|
@@ -36,7 +37,7 @@ import {
|
|
|
36
37
|
appendActionIfAbsent, branchEntries, branchLogPath, branchMetaPath, changesetContentHash, changesetReadiness, confirmAction, parentLogPath, readTree, stateDirName, wholeLog, worldNow,
|
|
37
38
|
parentEntries, readBranchMeta, rebaseBranch, type Changeset, type Entry, type Receipt, type RootConfig,
|
|
38
39
|
} from '@volter/world-core/runtime';
|
|
39
|
-
import { approveWorldChangeset, createWorldChangeset, diffWorld, findWorldChangeset, listWorldChangesets, pushWorldChangeset, verifyWorldChangeset, worldChangesetsDir } from './changeset.ts';
|
|
40
|
+
import { approveWorldChangeset, createWorldChangeset, diffWorld, findWorldChangeset, listWorldChangesets, listWorldMarks, markWorld, MARK_NOTE_MAX, pushWorldChangeset, verifyWorldChangeset, worldChangesetsDir } from './changeset.ts';
|
|
40
41
|
import { fetchFromOrigin } from './origin.ts';
|
|
41
42
|
import { CONSOLE_BASE, consoleRedirect, serveConsoleApart, type ConsoleMount } from './console-apart.ts';
|
|
42
43
|
import { loadWorldConfig, worldChangesetRules, writeWorldConfig } from './configs.ts';
|
|
@@ -1340,6 +1341,27 @@ export class WorldDoors {
|
|
|
1340
1341
|
withHistoryLocks(vendors.map((vendor) => worldPaths(this.stateOf(vendor), this.controlRoot(vendor)).dir), () => { for (const vendor of vendors) views[vendor] = historyAtInstant(this.stateOf(vendor), instant, this.controlRoot(vendor)); });
|
|
1341
1342
|
return Response.json({ world: this.served, at: new Date(Date.parse(instant)).toISOString(), views });
|
|
1342
1343
|
}
|
|
1344
|
+
// THE WORLD'S MARKS (`GET marks`, `POST marks { note? }`): where its changes count from. A World seeded through its
|
|
1345
|
+
// doors (a hosted one has no seed run to land its starting data as already there) is marked once it is set up, so
|
|
1346
|
+
// what it starts with is not counted as its changes; the latest mark is the base of the diff, the board's Changes
|
|
1347
|
+
// and a new changeset, as `volter-world mark <world>` makes it on this machine
|
|
1348
|
+
if (kind === 'marks' && !name && request.method === 'GET') return Response.json({ world: this.served, marks: listWorldMarks(this.name, this.worldRoot) });
|
|
1349
|
+
if (kind === 'marks' && !name && request.method === 'POST') {
|
|
1350
|
+
const body = (await request.json().catch(() => ({}))) as { note?: unknown };
|
|
1351
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return Response.json({ error: 'a mark is sent as a JSON object' }, { status: 400 });
|
|
1352
|
+
if (body.note !== undefined && (typeof body.note !== 'string' || body.note.length > MARK_NOTE_MAX)) return Response.json({ error: `a mark's note is a string of at most ${MARK_NOTE_MAX} characters` }, { status: 400 });
|
|
1353
|
+
const vendors = Object.keys(this.up().services);
|
|
1354
|
+
// taken while no twin writes, so it stands at one position of every log
|
|
1355
|
+
const { mark, passed } = withHistoryLocks(vendors.map((vendor) => worldPaths(this.stateOf(vendor), this.controlRoot(vendor)).dir), () => {
|
|
1356
|
+
const delta = diffWorld(this.name, { root: this.worldRoot });
|
|
1357
|
+
// what another World pushed here is in its changeset already: never this World's uncut changes
|
|
1358
|
+
const received = new Set(listWorldChangesets({ root: this.worldRoot, world: this.name }).filter((l) => l.changeset.pushedBy).flatMap((l) => l.changeset.actions.map((a) => a.action.id)));
|
|
1359
|
+
const passed = delta.actions.filter((a) => !received.has(a.action.id)).length;
|
|
1360
|
+
const mark = markWorld(this.name, { root: this.worldRoot, ...(typeof body.note === 'string' && body.note.trim() ? { note: body.note } : {}) });
|
|
1361
|
+
return { mark, passed };
|
|
1362
|
+
});
|
|
1363
|
+
return Response.json({ world: this.served, mark, passed }, { status: 201 });
|
|
1364
|
+
}
|
|
1343
1365
|
if (kind === 'map' && !name && request.method === 'GET') return Response.json(await this.map());
|
|
1344
1366
|
if (kind === 'board' && !name && request.method === 'GET') return Response.json(await this.board(url));
|
|
1345
1367
|
if (kind === 'board' && name === 'changes' && !verb && request.method === 'GET') return await this.boardChanges(url);
|