@volter/world-runtime 3.0.162 → 3.0.164
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/ancestry.js +2 -2
- package/dist/src/app-url.d.ts +5 -1
- package/dist/src/app-url.js +14 -1
- package/dist/src/branch.js +28 -8
- package/dist/src/browser-files.d.ts +5 -0
- package/dist/src/browser-files.js +14 -0
- package/dist/src/browsers.d.ts +2 -3
- package/dist/src/browsers.js +4 -4
- package/dist/src/catalog.d.ts +2 -1
- package/dist/src/catalog.js +3 -2
- package/dist/src/changeset.js +1 -1
- package/dist/src/cli-commands.js +71 -18
- package/dist/src/cli-load-clock.d.ts +1 -0
- package/dist/src/cli-load-clock.js +2 -0
- package/dist/src/cli.js +5 -1
- package/dist/src/clickhouse-backing.d.ts +2 -0
- package/dist/src/clickhouse-backing.js +145 -0
- package/dist/src/command-lifetime.d.mts +1 -1
- package/dist/src/command-lifetime.mjs +5 -2
- package/dist/src/configs.js +1 -1
- package/dist/src/consumers.js +1 -1
- package/dist/src/covers.d.ts +4 -2
- package/dist/src/covers.js +8 -4
- package/dist/src/go-mysql-binding.d.ts +19 -0
- package/dist/src/go-mysql-binding.js +51 -0
- package/dist/src/guest-processes.d.mts +1 -1
- package/dist/src/guest-processes.mjs +22 -14
- package/dist/src/host-cli.js +8 -1
- package/dist/src/host-ready.d.ts +16 -2
- package/dist/src/host-ready.js +66 -39
- package/dist/src/host-worker.js +7 -1
- package/dist/src/host.d.ts +26 -0
- package/dist/src/host.js +93 -3
- package/dist/src/infra-definition.d.ts +35 -0
- package/dist/src/infra-definition.js +146 -0
- package/dist/src/infra.d.ts +3 -0
- package/dist/src/infra.js +375 -105
- package/dist/src/init.d.ts +46 -0
- package/dist/src/init.js +445 -18
- package/dist/src/lifecycle-record.d.ts +5 -1
- package/dist/src/lifecycle-record.js +27 -4
- package/dist/src/local-branches.js +1 -1
- package/dist/src/mariadb-bootstrap.d.ts +32 -0
- package/dist/src/mariadb-bootstrap.js +90 -0
- package/dist/src/mariadb-supplier.d.ts +23 -0
- package/dist/src/mariadb-supplier.js +215 -0
- package/dist/src/mongodb-backing.d.ts +4 -0
- package/dist/src/mongodb-backing.js +71 -1
- package/dist/src/native-mariadb-backing.d.ts +13 -0
- package/dist/src/native-mariadb-backing.js +512 -0
- package/dist/src/native-postgres-backing.d.ts +4 -10
- package/dist/src/native-postgres-backing.js +428 -43
- package/dist/src/native-server-inputs.d.ts +16 -0
- package/dist/src/native-server-inputs.js +204 -0
- package/dist/src/origin.js +3 -3
- package/dist/src/original-inputs.d.ts +14 -0
- package/dist/src/original-inputs.js +84 -0
- package/dist/src/pack-facts.d.ts +9 -1
- package/dist/src/pglite-backing.d.ts +41 -17
- package/dist/src/pglite-backing.js +213 -79
- package/dist/src/pglite-host.mjs +482 -10
- package/dist/src/process-groups.d.mts +6 -1
- package/dist/src/process-groups.d.ts +2 -1
- package/dist/src/process-groups.js +1 -1
- package/dist/src/process-groups.mjs +82 -15
- package/dist/src/process-signal.d.mts +2 -2
- package/dist/src/process-signal.mjs +29 -9
- package/dist/src/project-inspect.d.ts +12 -0
- package/dist/src/project-inspect.js +41 -4
- package/dist/src/proxy-env.d.ts +34 -0
- package/dist/src/proxy-env.js +114 -0
- package/dist/src/redirect-proxy.d.ts +1 -31
- package/dist/src/redirect-proxy.js +3 -112
- package/dist/src/redis-backing.d.ts +6 -4
- package/dist/src/redis-backing.js +20 -168
- package/dist/src/runtime.d.ts +1 -0
- package/dist/src/runtime.js +503 -218
- package/dist/src/schema.d.ts +1 -0
- package/dist/src/schema.js +5 -3
- package/dist/src/served-world.js +15 -10
- package/dist/src/service-exit.d.ts +3 -2
- package/dist/src/service-exit.js +7 -5
- package/dist/src/twin-backing.d.ts +47 -0
- package/dist/src/twin-backing.js +343 -0
- package/dist/src/up-cli.js +56 -3
- package/package.json +4 -4
- package/src/ancestry.ts +2 -2
- package/src/app-url.ts +9 -2
- package/src/branch.ts +26 -8
- package/src/browser-files.ts +15 -0
- package/src/browsers.ts +4 -4
- package/src/catalog.ts +4 -3
- package/src/changeset.ts +1 -1
- package/src/cli-commands.ts +85 -18
- package/src/cli-load-clock.ts +2 -0
- package/src/cli.ts +5 -1
- package/src/clickhouse-backing.ts +70 -0
- package/src/command-lifetime.d.mts +1 -1
- package/src/command-lifetime.mjs +5 -2
- package/src/configs.ts +1 -1
- package/src/consumers.ts +1 -1
- package/src/covers.ts +9 -4
- package/src/go-mysql-binding.ts +49 -0
- package/src/guest-processes.d.mts +1 -1
- package/src/guest-processes.mjs +22 -14
- package/src/host-cli.ts +7 -1
- package/src/host-ready.ts +62 -31
- package/src/host-worker.ts +6 -2
- package/src/host.ts +88 -6
- package/src/infra-definition.ts +135 -0
- package/src/infra.ts +250 -45
- package/src/init.ts +416 -17
- package/src/lifecycle-record.ts +18 -4
- package/src/local-branches.ts +1 -1
- package/src/mariadb-bootstrap.ts +107 -0
- package/src/mariadb-supplier.ts +199 -0
- package/src/mongodb-backing.ts +59 -1
- package/src/native-mariadb-backing.ts +418 -0
- package/src/native-postgres-backing.ts +353 -41
- package/src/native-server-inputs.ts +169 -0
- package/src/origin.ts +3 -3
- package/src/original-inputs.ts +72 -0
- package/src/pack-facts.ts +1 -1
- package/src/pglite-backing.ts +192 -80
- package/src/pglite-host.mjs +482 -10
- package/src/process-groups.d.mts +6 -1
- package/src/process-groups.mjs +82 -15
- package/src/process-groups.ts +2 -1
- package/src/process-signal.d.mts +2 -2
- package/src/process-signal.mjs +29 -9
- package/src/project-inspect.ts +37 -4
- package/src/proxy-env.ts +128 -0
- package/src/redirect-proxy.ts +3 -125
- package/src/redis-backing.ts +19 -136
- package/src/runtime.ts +342 -107
- package/src/schema.ts +5 -4
- package/src/served-world.ts +14 -10
- package/src/service-exit.ts +5 -3
- package/src/twin-backing.ts +300 -0
- package/src/up-cli.ts +52 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-runtime",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.164",
|
|
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",
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"node": ">=22.3"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@volter/world-core": "3.0.
|
|
74
|
-
"@volter/world-console": "3.0.
|
|
73
|
+
"@volter/world-core": "3.0.156",
|
|
74
|
+
"@volter/world-console": "3.0.151"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
77
|
"@electric-sql/pglite": "0.5.8",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"@volter/browser-runtime": "0.1.104",
|
|
81
81
|
"@volter/browser-sandboxes": "0.1.84",
|
|
82
82
|
"@volter/world-access": "3.0.1",
|
|
83
|
-
"@volter/world-core": "3.0.
|
|
83
|
+
"@volter/world-core": "3.0.156",
|
|
84
84
|
"modern-screenshot": "4.7.0",
|
|
85
85
|
"mongodb": "~6.20.0",
|
|
86
86
|
"pg-gateway": "0.3.0-beta.4",
|
package/src/ancestry.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// carry parent-side pins, including clones across checkouts, without a global World registry.
|
|
3
3
|
import { existsSync, readdirSync } from 'node:fs';
|
|
4
4
|
import { join, relative, resolve, sep } from 'node:path';
|
|
5
|
-
import { assertStateRemovable, readBranchMeta, stateDirName, withAncestryLock, withStateRemoval, getActiveWorldStore, worldPaths, worldStateRoot } from '@volter/world-core/runtime';
|
|
5
|
+
import { assertStateRemovable, readBranchMeta, stateDirName, withAncestryLock, withStateRemoval, getActiveWorldStore, worldPaths, worldStateRoot } from '@volter/world-core/runtime-state';
|
|
6
6
|
|
|
7
7
|
function checkWorldAncestry(root: string, path: string): void {
|
|
8
8
|
const base = join(root, stateDirName(), 'worlds');
|
|
@@ -28,7 +28,7 @@ function checkWorldAncestry(root: string, path: string): void {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export function assertWorldStateRemovable(root: string, path: string): void {
|
|
31
|
-
withAncestryLock(() => { checkWorldAncestry(root, path); assertStateRemovable(path); });
|
|
31
|
+
withAncestryLock(() => { checkWorldAncestry(root, path); assertStateRemovable(path); }, { operation: 'removal preflight' });
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export function removeWorldState<T>(root: string, path: string, remove: () => T): T {
|
package/src/app-url.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// instance it described.
|
|
22
22
|
import { errorMessage } from './error-message.ts';
|
|
23
23
|
import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
24
|
-
import { withFileLock } from '@volter/world-core/runtime';
|
|
24
|
+
import { withFileLock } from '@volter/world-core/runtime-state';
|
|
25
25
|
import { join } from 'node:path';
|
|
26
26
|
import { spawnSync } from 'node:child_process';
|
|
27
27
|
import net from 'node:net';
|
|
@@ -113,11 +113,18 @@ function assertHttpUrl(url: string): URL {
|
|
|
113
113
|
/** Record `url` as the app endpoint of the RUNNING/booted instance `name`. The instance must
|
|
114
114
|
* exist (a record for a world that was never upped would describe nothing); the app itself is
|
|
115
115
|
* deliberately NOT probed — the booter declaring "this is where I put the app" is the truth. */
|
|
116
|
-
export function setAppUrl(name: string, url: string, options: { root?: string; via?: Exclude<AppUrlSource, 'service'>; detail?: string; app?: string } = {}): AppUrlRecord {
|
|
116
|
+
export function setAppUrl(name: string, url: string, options: { root?: string; via?: Exclude<AppUrlSource, 'service'>; detail?: string; app?: string; /** An instrument: filled with how long the World's status and the record's write each took. */ timings?: { statusMs?: number; writeMs?: number } } = {}): AppUrlRecord {
|
|
117
117
|
const root = options.root ?? process.cwd();
|
|
118
118
|
const app = appName(options.app);
|
|
119
|
+
const began = Date.now();
|
|
119
120
|
const status = statusWorld(name, root); // throws "World instance not found" — the loud path
|
|
121
|
+
const statusRead = Date.now();
|
|
122
|
+
if (options.timings) options.timings.statusMs = statusRead - began;
|
|
120
123
|
assertHttpUrl(url);
|
|
124
|
+
try { return setAppUrlEntry(root, name, app, url, status, options); }
|
|
125
|
+
finally { if (options.timings) options.timings.writeMs = Date.now() - statusRead; }
|
|
126
|
+
}
|
|
127
|
+
function setAppUrlEntry(root: string, name: string, app: string, url: string, status: ReturnType<typeof statusWorld>, options: { via?: Exclude<AppUrlSource, 'service'>; detail?: string }): AppUrlRecord {
|
|
121
128
|
return writeEntry(root, name, app, (file) => {
|
|
122
129
|
// a new URL keeps the hosts recorded for the app (they name the app, not where it listens)
|
|
123
130
|
const hosts = entryOf(file, app)?.hosts;
|
package/src/branch.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// base running at once share the database port its definition names; run one at a time (the SDK does). `checkout <name>`
|
|
8
8
|
// brings a stopped world back up from its own instance record, state intact.
|
|
9
9
|
import { runInfrastructure } from './infra.ts';
|
|
10
|
-
import { cpSync, existsSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
10
|
+
import { cpSync, existsSync, lstatSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
11
11
|
import { dirname, join, resolve } from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
import { captureHistory, historyAtInstant, forkTwin, stateGeneration, withHistoryLocks, worldPaths, worldStateRoot } from '@volter/world-core/runtime';
|
|
@@ -49,7 +49,7 @@ export async function branchWorld(base: string, name: string, options: BranchOpt
|
|
|
49
49
|
const position = options.at?.positions?.[service] ?? options.at?.positions?.[state] ?? timed?.position;
|
|
50
50
|
return { state, view, position, generation: stateGeneration(worldPaths(state, fromRoot).dir) };
|
|
51
51
|
}) };
|
|
52
|
-
}));
|
|
52
|
+
}), { operation: 'capture' });
|
|
53
53
|
// the base's managed databases (checked before the branch boots: its database would share the base's port)
|
|
54
54
|
const databases = managedDatabases(from);
|
|
55
55
|
if (databases.length && (options.at?.instant || options.at?.positions || options.at?.views)) {
|
|
@@ -83,23 +83,28 @@ export async function branchWorld(base: string, name: string, options: BranchOpt
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/** The base's containerless managed databases: each PGlite `pglite-<kind>-data`, MongoDB's
|
|
86
|
-
* `mongod-mongodb-data` (pglite-backing.ts), the
|
|
86
|
+
* `mongod-mongodb-data` (pglite-backing.ts), the declared Redis/ClickHouse twin trees, and native PostgreSQL. A stopped World's
|
|
87
87
|
* hosts leave no pid file; a docker-backed World keeps its data under the container's volume, which a branch does not
|
|
88
88
|
* carry. */
|
|
89
89
|
function managedDatabases(from: WorldInstance): string[] {
|
|
90
90
|
const data = from.dirs.data;
|
|
91
91
|
if (!existsSync(data)) return [];
|
|
92
|
-
const known = (name: string) => /^pglite-[a-z0-9_-]+-data$/.test(name) || name === 'mongod-mongodb-data' ||
|
|
92
|
+
const known = (name: string) => /^pglite-[a-z0-9_-]+-data$/.test(name) || name === 'mongod-mongodb-data' || ['native-postgres-data', 'native-mariadb-data'].includes(name);
|
|
93
93
|
if (readdirSync(data).some(name => name.endsWith('-data') && !known(name) && !from.services[name])) {
|
|
94
94
|
throw new Error('volter-world branch: unrecognised storage; retain the original World');
|
|
95
95
|
}
|
|
96
|
-
return readdirSync(data).filter((n) => /^pglite-[a-z0-9_-]+-data$/.test(n) || n === 'mongod-mongodb-data' ||
|
|
96
|
+
return readdirSync(data).filter((n) => /^pglite-[a-z0-9_-]+-data$/.test(n) || n === 'mongod-mongodb-data' || ['redis-twin', 'clickhouse-twin', 'native-postgres-data', 'native-mariadb-data'].includes(n));
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
/** The pid files of the hosts serving one managed database directory. */
|
|
100
100
|
function hostPidFiles(data: string, db: string): string[] {
|
|
101
|
+
if (db === 'native-mariadb-data') return readdirSync(data).filter(name => /^native-mariadb\.(?:owner|starting)\.json(?:\.spawn\.json)?$/u.test(name)
|
|
102
|
+
|| /^native-mariadb-tool-.*\.json(?:\.spawn\.json)?$/u.test(name)).map(name => join(data, name));
|
|
101
103
|
if (db === 'native-postgres-data') return [join(data, 'native-postgres.owner.json')];
|
|
102
104
|
if (db === 'redis-twin') return readdirSync(data).filter((n) => /^redis-twin-\d+\.pid$/.test(n)).map((n) => join(data, n));
|
|
105
|
+
if (db === 'clickhouse-twin') return [...new Set(readdirSync(data)
|
|
106
|
+
.filter(n => new RegExp(`^${db}-\\d+\\.pid(?:\\.spawn\\.json)?$`).test(n))
|
|
107
|
+
.map(n => join(data, n.replace(/\.spawn\.json$/, ''))))];
|
|
103
108
|
return [join(data, `${db.slice(0, -'-data'.length)}.pid`)];
|
|
104
109
|
}
|
|
105
110
|
|
|
@@ -107,7 +112,19 @@ function hostPidFiles(data: string, db: string): string[] {
|
|
|
107
112
|
* port: the base must be stopped first (`volter world branch` stops it, as a branch replaces its base). */
|
|
108
113
|
function refuseLiveDatabases(from: WorldInstance, dbs: string[]): void {
|
|
109
114
|
for (const db of dbs) {
|
|
115
|
+
if (db === 'native-mariadb-data' && (existsSync(join(from.dirs.data, db, 'server.pid')) || existsSync(join(from.dirs.data, db, 'server.sock'))))
|
|
116
|
+
throw new Error(`volter-world branch: ${from.name}'s managed MariaDB retains PID/socket files; verified down is required`);
|
|
117
|
+
if (db === 'native-mariadb-data') {
|
|
118
|
+
const stamp = join(from.dirs.data, db, '.world-policy.json');
|
|
119
|
+
if (!existsSync(stamp) || !lstatSync(stamp).isFile() || existsSync(join(from.dirs.data, db, '.world-initializing.json')))
|
|
120
|
+
throw new Error(`volter-world branch: ${from.name}'s MariaDB has incomplete initialization; original data retained`);
|
|
121
|
+
}
|
|
110
122
|
for (const pidFile of hostPidFiles(from.dirs.data, db)) {
|
|
123
|
+
// Successful managed-twin down removes both receipts. A partial publication or an
|
|
124
|
+
// uncertain retirement must finish there before branch copies this store.
|
|
125
|
+
if (db === 'clickhouse-twin' || db === 'native-mariadb-data') {
|
|
126
|
+
throw new Error(`volter-world branch: ${from.name}'s managed ${db} retains ownership receipts; stop ${from.name} before copying its store`);
|
|
127
|
+
}
|
|
111
128
|
const recorded = existsSync(pidFile) ? readFileSync(pidFile, 'utf8').trim() : '';
|
|
112
129
|
const pid = db === 'native-postgres-data' && recorded ? JSON.parse(recorded).pid : Number(recorded);
|
|
113
130
|
let live = false;
|
|
@@ -121,6 +138,7 @@ function refuseLiveDatabases(from: WorldInstance, dbs: string[]): void {
|
|
|
121
138
|
function requireMatchingBackings(source: string[], child: string[]): void {
|
|
122
139
|
const kinds = {
|
|
123
140
|
postgres: ['native-postgres-data', 'pglite-postgres-data'],
|
|
141
|
+
mysql: ['native-mariadb-data'],
|
|
124
142
|
mongodb: ['mongod-mongodb-data'],
|
|
125
143
|
};
|
|
126
144
|
for (const [kind, formats] of Object.entries(kinds)) {
|
|
@@ -158,15 +176,15 @@ async function branchManagedDatabases(from: WorldInstance, instance: WorldInstan
|
|
|
158
176
|
const infrastructure = config.services.find((service) => service.type === 'external' && (service.external?.up[0] === 'volter-world-infra' || service.id === 'infrastructure'));
|
|
159
177
|
if (!infrastructure) throw new Error(`volter-world branch: ${instance.name}'s managed databases have no declared infrastructure service`);
|
|
160
178
|
const launch = { env: externalServiceEnv(infrastructure, instance.dirs, instance.env, config.stripEnv) };
|
|
161
|
-
if (dbs.
|
|
179
|
+
if (dbs.some(directory => ['native-postgres-data', 'native-mariadb-data', 'clickhouse-twin'].includes(directory))) {
|
|
162
180
|
const io = { out: (text: string) => process.stdout.write(text), err: (text: string) => process.stderr.write(text) };
|
|
163
|
-
if (await runInfrastructure('down', launch.env, io) !== 0) throw new Error('managed
|
|
181
|
+
if (await runInfrastructure('down', launch.env, io) !== 0) throw new Error('managed database branch could not stop its new backing');
|
|
164
182
|
for (const db of dbs) {
|
|
165
183
|
const to = join(instance.dirs.data, db);
|
|
166
184
|
rmSync(to, { recursive: true, force: true });
|
|
167
185
|
cpSync(join(from.dirs.data, db), to, { recursive: true });
|
|
168
186
|
}
|
|
169
|
-
if (await runInfrastructure('up', launch.env, io) !== 0) throw new Error('managed
|
|
187
|
+
if (await runInfrastructure('up', launch.env, io) !== 0) throw new Error('managed database branch could not resume its copied backing');
|
|
170
188
|
return;
|
|
171
189
|
}
|
|
172
190
|
const redisCli = dbs.includes('redis-twin') && redis.length ? redisTwinCli([dirname(fileURLToPath(import.meta.url)), dirname(instance.configPath)]) : undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getActiveWorldStore, stateDirName } from '@volter/world-core/runtime-state';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/** Instance bookkeeping, alongside the clock; retained at down, discarded at reset. */
|
|
5
|
+
export const browsersDir = (data: string): string => join(dirname(data), 'browser-records');
|
|
6
|
+
|
|
7
|
+
/** Empty Worlds need no browser validators or cookie/load machinery. A prior
|
|
8
|
+
* projection still loads the ordinary materializer to remove stale files. */
|
|
9
|
+
export async function materializeWorldBrowserFiles(data: string, root: string, name: string, main = name): Promise<void> {
|
|
10
|
+
const store = getActiveWorldStore();
|
|
11
|
+
if (!store.list(browsersDir(data)).some(file => file.endsWith('.json'))
|
|
12
|
+
&& store.read(join(root, stateDirName(), 'browsers', '.source.json')) === null) return;
|
|
13
|
+
const { materializeBrowserFiles } = await import('./browsers.ts');
|
|
14
|
+
materializeBrowserFiles(data, root, name, main);
|
|
15
|
+
}
|
package/src/browsers.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { assertBrowserName, validateBrowserState, type BrowserState } from '@volter/world-core';
|
|
2
|
-
import { getActiveWorldStore, stateDirName, type WorldStore } from '@volter/world-core/runtime';
|
|
1
|
+
import { assertBrowserName, validateBrowserState, type BrowserState } from '@volter/world-core/browser-state';
|
|
2
|
+
import { getActiveWorldStore, stateDirName, type WorldStore } from '@volter/world-core/runtime-state';
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
|
|
6
6
|
export type WorldBrowser = { name: string; state: BrowserState; version: string };
|
|
7
|
-
|
|
8
|
-
export
|
|
7
|
+
import { browsersDir } from './browser-files.ts';
|
|
8
|
+
export { browsersDir } from './browser-files.ts';
|
|
9
9
|
const path = (data: string, name: string): string => { assertBrowserName(name); return join(browsersDir(data), `${name}.json`); };
|
|
10
10
|
const records = new WeakMap<WorldStore, Map<string, { version: string; text: string; record: WorldBrowser }>>();
|
|
11
11
|
export function worldBrowser(data: string, name: string): WorldBrowser | null {
|
package/src/catalog.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
|
9
9
|
import { spawnSync } from 'node:child_process';
|
|
10
10
|
import { join, resolve } from 'node:path';
|
|
11
11
|
import { overlayPackFacts, type PackFacts } from './pack-facts.ts';
|
|
12
|
-
import { installedPackageDir, installedVendorPacks, isDerivedPack, packRepository } from '@volter/world-core/runtime';
|
|
12
|
+
import { installedPackageDir, installedVendorPacks, isDerivedPack, packRepository } from '@volter/world-core/runtime-state';
|
|
13
13
|
|
|
14
14
|
export const TWIN_PACKAGE_PREFIX = '@volter/twin-';
|
|
15
15
|
|
|
@@ -156,7 +156,7 @@ export function packFactsFile(dir: string): string | undefined {
|
|
|
156
156
|
|
|
157
157
|
/** The catalog for a World root: its pack repository (or the one beside its platform checkout), merged with installed
|
|
158
158
|
* packages. Only the selected packs' own facts are overlaid on the kernel's. */
|
|
159
|
-
export function resolveCatalog(root: string, options: { sources?: Readonly<Record<string, string>>; vendors?: readonly string[] } = {}): Catalog {
|
|
159
|
+
export function resolveCatalog(root: string, options: { sources?: Readonly<Record<string, string>>; vendors?: readonly string[]; mayBeEmpty?: boolean } = {}): Catalog {
|
|
160
160
|
const resolved = resolve(root);
|
|
161
161
|
const installed = installedCatalog(resolved, options);
|
|
162
162
|
const repo = packRepository(resolved);
|
|
@@ -165,7 +165,8 @@ export function resolveCatalog(root: string, options: { sources?: Readonly<Recor
|
|
|
165
165
|
const file = packFactsFile(catalog.packDir(vendor)!);
|
|
166
166
|
if (file) overlayPackFactsFile(file);
|
|
167
167
|
}
|
|
168
|
-
|
|
168
|
+
// A World whose application runs beside infrastructure alone has no twin to find (`init --needs`, architecture).
|
|
169
|
+
if (catalog.vendors().length === 0 && !options.mayBeEmpty) {
|
|
169
170
|
throw new Error(
|
|
170
171
|
`No twins found for ${resolved}: no pack repository and no \`@volter/twin-<vendor>\` package installed above it. `
|
|
171
172
|
+ `Install the twins your app needs (e.g. \`bun add -d @volter/twin-stripe\`) and run init again.`,
|
package/src/changeset.ts
CHANGED
|
@@ -641,7 +641,7 @@ export function rebaseWorldChangeset(name: string, options: WorldRootOptions & {
|
|
|
641
641
|
const moved = [...new Set(located.changeset.actions.map((e) => e.service))].map((service) => ({ st: located.changeset.actions.find((e) => e.service === service)?.stateService ?? service, controlRoot: join(inst.dirs.data, service) }));
|
|
642
642
|
withHistoryLocks(moved.map(({ st, controlRoot }) => worldPaths(st, controlRoot).dir), () => {
|
|
643
643
|
for (const { st, controlRoot } of moved) rebaseBranch(st, controlRoot, { origin: Boolean(readBranchMeta(st, controlRoot)?.origin) });
|
|
644
|
-
});
|
|
644
|
+
}, { operation: 'branch-pointer adjustment' });
|
|
645
645
|
if (!changesetHashMatches(located.changeset)) throw new Error(`Refusing to rebase changeset "${name}": its stored contentHash does not match its body — the object drifted after authoring`);
|
|
646
646
|
const instance = requireWorld(located.world, root);
|
|
647
647
|
const targets: RebaseTarget[] = [...new Set(located.changeset.actions.map((entry) => entry.service))].map((service) => ({ service, stateService: located.changeset.actions.find((entry) => entry.service === service)!.stateService, controlRoot: join(instance.dirs.data, service) }));
|