@volter/world-runtime 3.0.63 → 3.0.64
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/catalog.d.ts +8 -2
- package/dist/src/catalog.js +4 -4
- package/dist/src/init.d.ts +3 -2
- package/dist/src/init.js +6 -3
- package/dist/src/pack-facts.d.ts +2 -0
- package/dist/src/runtime.js +12 -6
- package/dist/src/schema.d.ts +7 -0
- package/dist/src/schema.js +2 -2
- package/dist/src/served-world.d.ts +6 -0
- package/dist/src/served-world.js +5 -1
- package/package.json +4 -4
- package/src/catalog.ts +4 -4
- package/src/init.ts +9 -5
- package/src/pack-facts.ts +2 -0
- package/src/runtime.ts +9 -5
- package/src/schema.ts +6 -2
- package/src/served-world.ts +6 -2
package/dist/src/catalog.d.ts
CHANGED
|
@@ -27,7 +27,10 @@ export declare function packCli(dir: string): string | undefined;
|
|
|
27
27
|
/** The nearest `node_modules/<name>` above `from` that holds a package.json — node's own walk. */
|
|
28
28
|
export declare const findInstalledPackage: typeof installedPackageDir;
|
|
29
29
|
export declare function checkoutCatalog(root: string): Catalog;
|
|
30
|
-
export declare function installedCatalog(root: string
|
|
30
|
+
export declare function installedCatalog(root: string, options?: {
|
|
31
|
+
sources?: Readonly<Record<string, string>>;
|
|
32
|
+
vendors?: readonly string[];
|
|
33
|
+
}): Catalog;
|
|
31
34
|
/**
|
|
32
35
|
* One catalog over several sources (docs/contributing/architecture.md, "The catalog: where twins come from"): each
|
|
33
36
|
* vendor is served by the pack of the higher major; on a tie the earlier source keeps it. `kind` is the first source's.
|
|
@@ -39,6 +42,9 @@ export declare function overlayPackFactsFile(file: string): void;
|
|
|
39
42
|
export declare function packFactsFile(dir: string): string | undefined;
|
|
40
43
|
/** The catalog for a World root: its pack repository (or the one beside its platform checkout), merged with installed
|
|
41
44
|
* packages. Only the selected packs' own facts are overlaid on the kernel's. */
|
|
42
|
-
export declare function resolveCatalog(root: string
|
|
45
|
+
export declare function resolveCatalog(root: string, options?: {
|
|
46
|
+
sources?: Readonly<Record<string, string>>;
|
|
47
|
+
vendors?: readonly string[];
|
|
48
|
+
}): Catalog;
|
|
43
49
|
/** The pack directory a service boots from: its `package` resolved from the world root at boot. */
|
|
44
50
|
export declare function resolveServicePackage(root: string, pkg: string): string;
|
package/dist/src/catalog.js
CHANGED
|
@@ -84,8 +84,8 @@ export function checkoutCatalog(root) {
|
|
|
84
84
|
},
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
-
export function installedCatalog(root) {
|
|
88
|
-
const twins = new Map([...installedVendorPacks(root)].filter(([, dir]) => packCli(dir) !== undefined));
|
|
87
|
+
export function installedCatalog(root, options = {}) {
|
|
88
|
+
const twins = new Map([...installedVendorPacks(root, options)].filter(([, dir]) => packCli(dir) !== undefined));
|
|
89
89
|
return {
|
|
90
90
|
kind: 'installed',
|
|
91
91
|
root: resolve(root),
|
|
@@ -149,9 +149,9 @@ export function packFactsFile(dir) {
|
|
|
149
149
|
}
|
|
150
150
|
/** The catalog for a World root: its pack repository (or the one beside its platform checkout), merged with installed
|
|
151
151
|
* packages. Only the selected packs' own facts are overlaid on the kernel's. */
|
|
152
|
-
export function resolveCatalog(root) {
|
|
152
|
+
export function resolveCatalog(root, options = {}) {
|
|
153
153
|
const resolved = resolve(root);
|
|
154
|
-
const installed = installedCatalog(resolved);
|
|
154
|
+
const installed = installedCatalog(resolved, options);
|
|
155
155
|
const repo = packRepository(resolved);
|
|
156
156
|
const catalog = repo === undefined ? installed : mergedCatalog([checkoutCatalog(repo), installed]);
|
|
157
157
|
for (const vendor of catalog.vendors()) {
|
package/dist/src/init.d.ts
CHANGED
|
@@ -183,9 +183,10 @@ export type InitOptions = {
|
|
|
183
183
|
force?: boolean;
|
|
184
184
|
allowUnknown?: boolean;
|
|
185
185
|
acknowledge?: Record<string, string>;
|
|
186
|
-
/** The twins
|
|
187
|
-
* <org>/<world> --twins a,b`). */
|
|
186
|
+
/** The twins deliberately selected instead of including every detected vendor. */
|
|
188
187
|
vendors?: string[];
|
|
188
|
+
/** Installed package choices by vendor, before discovery resolves competing publishers. */
|
|
189
|
+
sources?: Record<string, string>;
|
|
189
190
|
/** A world with no app: served as `/<org>/<world>/`. */
|
|
190
191
|
bare?: string;
|
|
191
192
|
};
|
package/dist/src/init.js
CHANGED
|
@@ -518,7 +518,7 @@ export function planWorldInit(name, repoPath, options = {}) {
|
|
|
518
518
|
/** a path the config carries: relative to the world root when the config lives under it
|
|
519
519
|
* (portable between teammates), absolute otherwise (a pilot emitted away from its root) */
|
|
520
520
|
const carried = (path) => (isInside(root, path) ? relative(root, path) : path);
|
|
521
|
-
const catalogSource = resolveCatalog(root);
|
|
521
|
+
const catalogSource = resolveCatalog(root, { sources: options.sources, vendors: options.vendors });
|
|
522
522
|
const catalog = new Set(catalogSource.vendors());
|
|
523
523
|
// the catalog index's defaults (the installed `@volter/twin-catalog`), which name the twin to install for a vendor
|
|
524
524
|
// this catalog lacks
|
|
@@ -604,9 +604,12 @@ export function planWorldInit(name, repoPath, options = {}) {
|
|
|
604
604
|
return null;
|
|
605
605
|
}
|
|
606
606
|
})();
|
|
607
|
-
const
|
|
607
|
+
const handlersPath = ownedPath(out, `handlers/${vendor}.json`);
|
|
608
|
+
// A published generative pack need not ship its author's journeys. Its scenario file is
|
|
609
|
+
// still a World-owned input, including one the user writes after init.
|
|
610
|
+
const handlersTo = starterHandlers !== null || packFacts()[vendor]?.archetype === 'generative' || existsSync(handlersPath) ? handlersPath : null;
|
|
608
611
|
if (handlersTo !== null)
|
|
609
|
-
defaultsCopies.push({ vendor, kind: 'handlers', content: starterHandlers, to: handlersTo });
|
|
612
|
+
defaultsCopies.push({ vendor, kind: 'handlers', content: starterHandlers ?? `${JSON.stringify({ handlers: [] }, null, 2)}\n`, to: handlersTo });
|
|
610
613
|
const handlersArg = handlersTo === null ? null : carried(handlersTo);
|
|
611
614
|
// a pack's default data (src/semantics/seed.ts: calls to the vendor's own operations, as data) is copied beside a
|
|
612
615
|
// runner that makes each call against the twin, both the author's from then on
|
package/dist/src/pack-facts.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export type PackFacts = {
|
|
|
22
22
|
maxRequestBodySize?: number;
|
|
23
23
|
/** Existing descriptor transport, compiled into the same artifact as endpoint/adoption facts. */
|
|
24
24
|
transport?: string;
|
|
25
|
+
/** Descriptor serve family, compiled into the same artifact as transport and adoption facts. */
|
|
26
|
+
archetype?: string;
|
|
25
27
|
adoption?: PackAdoption;
|
|
26
28
|
hosts?: Array<{
|
|
27
29
|
host?: string;
|
package/dist/src/runtime.js
CHANGED
|
@@ -1450,6 +1450,7 @@ async function startService(config, service, worldEnv, paths, allocatedPorts, bo
|
|
|
1450
1450
|
url,
|
|
1451
1451
|
...sidesRecord(sides),
|
|
1452
1452
|
...(mounted.version !== undefined ? { resolvedVersion: mounted.version } : {}),
|
|
1453
|
+
...(mounted.name !== undefined ? { resolvedPackage: mounted.name } : {}),
|
|
1453
1454
|
...(mounted.protocol !== undefined ? { protocol: mounted.protocol } : {}),
|
|
1454
1455
|
pid: child.pid ?? 0,
|
|
1455
1456
|
log,
|
|
@@ -1471,9 +1472,11 @@ function mountedPackage(modulePath, serviceId) {
|
|
|
1471
1472
|
const p = join(dir, 'package.json');
|
|
1472
1473
|
if (existsSync(p)) {
|
|
1473
1474
|
try {
|
|
1474
|
-
const
|
|
1475
|
-
if (typeof
|
|
1476
|
-
out.
|
|
1475
|
+
const manifest = JSON.parse(readFileSync(p, 'utf8'));
|
|
1476
|
+
if (typeof manifest.name === 'string')
|
|
1477
|
+
out.name = manifest.name;
|
|
1478
|
+
if (typeof manifest.version === 'string') {
|
|
1479
|
+
out.version = manifest.version;
|
|
1477
1480
|
out.packagePath = p;
|
|
1478
1481
|
}
|
|
1479
1482
|
}
|
|
@@ -1503,7 +1506,7 @@ function lockfileCheck(service, modulePath) {
|
|
|
1503
1506
|
if (mounted.version === undefined)
|
|
1504
1507
|
throw new Error(`Twin "${service.id}" pins version ${service.version} but no package.json was found above ${modulePath} — nothing to hold the pin against`);
|
|
1505
1508
|
if (!versionSatisfies(mounted.version, service.version))
|
|
1506
|
-
throw new Error(`Twin "${service.id}" pins version ${service.version} and the mounted package is ${mounted.version} (${mounted.packagePath}) — install the version the World names (npm install -D
|
|
1509
|
+
throw new Error(`Twin "${service.id}" pins version ${service.version} and the mounted package is ${mounted.version} (${mounted.packagePath}) — install the version the World names (npm install -D --save-exact ${service.package ?? mounted.name ?? `@volter/twin-${service.id}`}@${service.version}), or trial an update in a separate app directory before changing source.version in .volter/world.json (volter-world outdated lists both)`);
|
|
1507
1510
|
}
|
|
1508
1511
|
if (mounted.protocol?.standing === 'refused')
|
|
1509
1512
|
throw new Error(`Twin "${service.id}" targets protocol major ${mounted.protocol.major}; this platform serves ${PROTOCOL_VERSION} only (runtime contract R16)`);
|
|
@@ -1846,6 +1849,7 @@ async function startColocatedServices(config, colocated, isolation, worldEnv, pa
|
|
|
1846
1849
|
url,
|
|
1847
1850
|
...sidesRecord(sides),
|
|
1848
1851
|
...(mounted.version !== undefined ? { resolvedVersion: mounted.version } : {}),
|
|
1852
|
+
...(mounted.name !== undefined ? { resolvedPackage: mounted.name } : {}),
|
|
1849
1853
|
...(mounted.protocol !== undefined ? { protocol: mounted.protocol } : {}),
|
|
1850
1854
|
pid: child.pid ?? 0,
|
|
1851
1855
|
log,
|
|
@@ -2140,8 +2144,10 @@ export async function upWorld(configId, options = {}, abort) {
|
|
|
2140
2144
|
try {
|
|
2141
2145
|
const services = {};
|
|
2142
2146
|
const recordService = (started) => {
|
|
2143
|
-
const
|
|
2144
|
-
services[started.id] = { ...started, branchState: branchState ?? 'supported'
|
|
2147
|
+
const selected = loaded.config.services.find(service => service.id === started.id);
|
|
2148
|
+
services[started.id] = { ...started, branchState: selected?.branchState ?? 'supported',
|
|
2149
|
+
...(selected?.package || selected?.version ? { pinnedSource: { ...(selected.package ? { package: selected.package } : {}), ...(selected.version ? { version: selected.version } : {}) } } : {}),
|
|
2150
|
+
};
|
|
2145
2151
|
};
|
|
2146
2152
|
const inheritedEnv = admittedEnv(loaded.config.stripEnv);
|
|
2147
2153
|
const env = {
|
package/dist/src/schema.d.ts
CHANGED
|
@@ -345,6 +345,13 @@ export type WorldServiceInstance = {
|
|
|
345
345
|
/** R17 — what the host actually mounted for a colocated twin: the package version read from
|
|
346
346
|
* the module's own package.json, and the protocol standing (R16) it declared. */
|
|
347
347
|
resolvedVersion?: string;
|
|
348
|
+
/** Package identity captured before boot, never inferred from a later installation. */
|
|
349
|
+
resolvedPackage?: string;
|
|
350
|
+
/** Configured selection at boot; distinct from the package actually mounted. */
|
|
351
|
+
pinnedSource?: {
|
|
352
|
+
package?: string;
|
|
353
|
+
version?: string;
|
|
354
|
+
};
|
|
348
355
|
protocol?: {
|
|
349
356
|
declared?: string;
|
|
350
357
|
major: number;
|
package/dist/src/schema.js
CHANGED
|
@@ -214,8 +214,8 @@ function assertLegacyWorldConfig(value, path) {
|
|
|
214
214
|
assertExternalServiceConfig(service, path);
|
|
215
215
|
}
|
|
216
216
|
else {
|
|
217
|
-
if (service.package !== undefined && !(typeof service.package === 'string' &&
|
|
218
|
-
throw new Error(`Service "${service.id}" package must
|
|
217
|
+
if (service.package !== undefined && !(typeof service.package === 'string' && /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(service.package))) {
|
|
218
|
+
throw new Error(`Service "${service.id}" package must be an npm package name; the installed descriptor establishes its vendor and protocol in ${path}`);
|
|
219
219
|
}
|
|
220
220
|
if (service.package === undefined && (!service.command || typeof service.command !== 'string')) {
|
|
221
221
|
throw new Error(`Service "${service.id}" must define command (or package) in ${path}`);
|
|
@@ -120,6 +120,12 @@ export type WorldLayout = {
|
|
|
120
120
|
services: Record<string, {
|
|
121
121
|
url?: string;
|
|
122
122
|
protocol?: unknown;
|
|
123
|
+
resolvedPackage?: string;
|
|
124
|
+
resolvedVersion?: string;
|
|
125
|
+
pinnedSource?: {
|
|
126
|
+
package?: string;
|
|
127
|
+
version?: string;
|
|
128
|
+
};
|
|
123
129
|
}>;
|
|
124
130
|
};
|
|
125
131
|
/** The host a world's doors run in: its layout, and how a request reaches a twin's wire — a local
|
package/dist/src/served-world.js
CHANGED
|
@@ -2352,7 +2352,11 @@ export class WorldDoors {
|
|
|
2352
2352
|
refresh = null;
|
|
2353
2353
|
}
|
|
2354
2354
|
const last = [...parentEntries(state, this.controlRoot(name))].reverse().find((e) => e.landsId && e.receipt);
|
|
2355
|
-
|
|
2355
|
+
const mounted = this.up().services[name];
|
|
2356
|
+
const origin = readBranchMeta(state, this.controlRoot(name))?.origin;
|
|
2357
|
+
const identity = { mounted: mounted?.resolvedPackage ? { package: mounted.resolvedPackage, version: mounted.resolvedVersion ?? null } : null, pinned: mounted?.pinnedSource ?? null };
|
|
2358
|
+
const dataOrigin = origin ? { kind: 'shared-history', url: origin.at } : refresh ? { kind: 'vendor-refresh', url: root?.url ?? null } : { kind: 'unrecorded', url: null };
|
|
2359
|
+
return Response.json({ twin: name, state, identity, dataOrigin, execution: { rootConnected: Boolean(root), deploy: root?.deploy ?? null }, protocol: mounted?.protocol ?? null, screens: await this.screensOf(name), root: root ?? null, credential: sealedCredentialInfo(this.worldRoot, name), refresh, position: wholeLog(state, this.controlRoot(name)).length, lastReceipt: last?.receipt ?? null });
|
|
2356
2360
|
}
|
|
2357
2361
|
if (verb === 'refresh' && request.method === 'POST') {
|
|
2358
2362
|
const root = rootForControlRoot(this.controlRoot(name), name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-runtime",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.64",
|
|
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",
|
|
@@ -69,14 +69,14 @@
|
|
|
69
69
|
"node": ">=22.3"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@volter/world-core": "3.0.
|
|
73
|
-
"@volter/world-console": "3.0.
|
|
72
|
+
"@volter/world-core": "3.0.64",
|
|
73
|
+
"@volter/world-console": "3.0.59"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@electric-sql/pglite": "0.5.8",
|
|
77
77
|
"@electric-sql/pglite-pgvector": "0.0.9",
|
|
78
78
|
"@volter/world-access": "3.0.1",
|
|
79
|
-
"@volter/world-core": "3.0.
|
|
79
|
+
"@volter/world-core": "3.0.64",
|
|
80
80
|
"mongodb": "~6.20.0",
|
|
81
81
|
"pg-gateway": "0.3.0-beta.4",
|
|
82
82
|
"smol-toml": "^1.8.0",
|
package/src/catalog.ts
CHANGED
|
@@ -98,8 +98,8 @@ export function checkoutCatalog(root: string): Catalog {
|
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
export function installedCatalog(root: string): Catalog {
|
|
102
|
-
const twins = new Map([...installedVendorPacks(root)].filter(([, dir]) => packCli(dir) !== undefined));
|
|
101
|
+
export function installedCatalog(root: string, options: { sources?: Readonly<Record<string, string>>; vendors?: readonly string[] } = {}): Catalog {
|
|
102
|
+
const twins = new Map([...installedVendorPacks(root, options)].filter(([, dir]) => packCli(dir) !== undefined));
|
|
103
103
|
return {
|
|
104
104
|
kind: 'installed',
|
|
105
105
|
root: resolve(root),
|
|
@@ -156,9 +156,9 @@ 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): Catalog {
|
|
159
|
+
export function resolveCatalog(root: string, options: { sources?: Readonly<Record<string, string>>; vendors?: readonly string[] } = {}): Catalog {
|
|
160
160
|
const resolved = resolve(root);
|
|
161
|
-
const installed = installedCatalog(resolved);
|
|
161
|
+
const installed = installedCatalog(resolved, options);
|
|
162
162
|
const repo = packRepository(resolved);
|
|
163
163
|
const catalog = repo === undefined ? installed : mergedCatalog([checkoutCatalog(repo), installed]);
|
|
164
164
|
for (const vendor of catalog.vendors()) {
|
package/src/init.ts
CHANGED
|
@@ -258,9 +258,10 @@ export type InitOptions = {
|
|
|
258
258
|
force?: boolean;
|
|
259
259
|
allowUnknown?: boolean;
|
|
260
260
|
acknowledge?: Record<string, string>;
|
|
261
|
-
/** The twins
|
|
262
|
-
* <org>/<world> --twins a,b`). */
|
|
261
|
+
/** The twins deliberately selected instead of including every detected vendor. */
|
|
263
262
|
vendors?: string[];
|
|
263
|
+
/** Installed package choices by vendor, before discovery resolves competing publishers. */
|
|
264
|
+
sources?: Record<string, string>;
|
|
264
265
|
/** A world with no app: served as `/<org>/<world>/`. */
|
|
265
266
|
bare?: string;
|
|
266
267
|
};
|
|
@@ -721,7 +722,7 @@ export function planWorldInit(name: string, repoPath: string, options: InitOptio
|
|
|
721
722
|
* (portable between teammates), absolute otherwise (a pilot emitted away from its root) */
|
|
722
723
|
const carried = (path: string): string => (isInside(root, path) ? relative(root, path) : path);
|
|
723
724
|
|
|
724
|
-
const catalogSource: Catalog = resolveCatalog(root);
|
|
725
|
+
const catalogSource: Catalog = resolveCatalog(root, { sources: options.sources, vendors: options.vendors });
|
|
725
726
|
const catalog = new Set(catalogSource.vendors());
|
|
726
727
|
// the catalog index's defaults (the installed `@volter/twin-catalog`), which name the twin to install for a vendor
|
|
727
728
|
// this catalog lacks
|
|
@@ -800,8 +801,11 @@ export function planWorldInit(name: string, repoPath: string, options: InitOptio
|
|
|
800
801
|
return answering.length ? `${JSON.stringify({ ...doc, handlers: answering }, null, 2)}\n` : null;
|
|
801
802
|
} catch { return null; }
|
|
802
803
|
})();
|
|
803
|
-
const
|
|
804
|
-
|
|
804
|
+
const handlersPath = ownedPath(out, `handlers/${vendor}.json`);
|
|
805
|
+
// A published generative pack need not ship its author's journeys. Its scenario file is
|
|
806
|
+
// still a World-owned input, including one the user writes after init.
|
|
807
|
+
const handlersTo = starterHandlers !== null || packFacts()[vendor]?.archetype === 'generative' || existsSync(handlersPath) ? handlersPath : null;
|
|
808
|
+
if (handlersTo !== null) defaultsCopies.push({ vendor, kind: 'handlers', content: starterHandlers ?? `${JSON.stringify({ handlers: [] }, null, 2)}\n`, to: handlersTo });
|
|
805
809
|
const handlersArg = handlersTo === null ? null : carried(handlersTo);
|
|
806
810
|
// a pack's default data (src/semantics/seed.ts: calls to the vendor's own operations, as data) is copied beside a
|
|
807
811
|
// runner that makes each call against the twin, both the author's from then on
|
package/src/pack-facts.ts
CHANGED
|
@@ -17,6 +17,8 @@ export type PackFacts = {
|
|
|
17
17
|
maxRequestBodySize?: number;
|
|
18
18
|
/** Existing descriptor transport, compiled into the same artifact as endpoint/adoption facts. */
|
|
19
19
|
transport?: string;
|
|
20
|
+
/** Descriptor serve family, compiled into the same artifact as transport and adoption facts. */
|
|
21
|
+
archetype?: string;
|
|
20
22
|
adoption?: PackAdoption;
|
|
21
23
|
hosts?: Array<{ host?: string; suffix?: string; hostPattern?: string; pathPattern?: string; key?: string; exclude?: true }>;
|
|
22
24
|
hostsNone?: string;
|
package/src/runtime.ts
CHANGED
|
@@ -1441,6 +1441,7 @@ async function startService(
|
|
|
1441
1441
|
url,
|
|
1442
1442
|
...sidesRecord(sides),
|
|
1443
1443
|
...(mounted.version !== undefined ? { resolvedVersion: mounted.version } : {}),
|
|
1444
|
+
...(mounted.name !== undefined ? { resolvedPackage: mounted.name } : {}),
|
|
1444
1445
|
...(mounted.protocol !== undefined ? { protocol: mounted.protocol } : {}),
|
|
1445
1446
|
pid: child.pid ?? 0,
|
|
1446
1447
|
log,
|
|
@@ -1452,7 +1453,7 @@ async function startService(
|
|
|
1452
1453
|
};
|
|
1453
1454
|
}
|
|
1454
1455
|
|
|
1455
|
-
type MountedPackage = { version?: string; packagePath?: string; protocol?: { declared?: string; major: number; standing: string } };
|
|
1456
|
+
type MountedPackage = { name?: string; version?: string; packagePath?: string; protocol?: { declared?: string; major: number; standing: string } };
|
|
1456
1457
|
/** What the host is about to mount, read WITHOUT importing the pack (the kernel never imports
|
|
1457
1458
|
* packs): the nearest package.json above the module for the version, pack-facts for the
|
|
1458
1459
|
* protocol standing the catalog recorded — keyed by the vendor dir the module sits in, else
|
|
@@ -1463,7 +1464,7 @@ function mountedPackage(modulePath: string, serviceId: string): MountedPackage {
|
|
|
1463
1464
|
for (let i = 0; i < 6; i++) {
|
|
1464
1465
|
const p = join(dir, 'package.json');
|
|
1465
1466
|
if (existsSync(p)) {
|
|
1466
|
-
try { const
|
|
1467
|
+
try { const manifest = JSON.parse(readFileSync(p, 'utf8')) as { name?: unknown; version?: unknown }; if (typeof manifest.name === 'string') out.name = manifest.name; if (typeof manifest.version === 'string') { out.version = manifest.version; out.packagePath = p; } } catch { /* unreadable: no version to hold a pin against */ }
|
|
1467
1468
|
break;
|
|
1468
1469
|
}
|
|
1469
1470
|
const up = dirname(dir); if (up === dir) break; dir = up;
|
|
@@ -1479,7 +1480,7 @@ function lockfileCheck(service: WorldServiceConfig, modulePath: string): Mounted
|
|
|
1479
1480
|
const mounted = mountedPackage(modulePath, service.id);
|
|
1480
1481
|
if (service.version !== undefined) {
|
|
1481
1482
|
if (mounted.version === undefined) throw new Error(`Twin "${service.id}" pins version ${service.version} but no package.json was found above ${modulePath} — nothing to hold the pin against`);
|
|
1482
|
-
if (!versionSatisfies(mounted.version, service.version)) throw new Error(`Twin "${service.id}" pins version ${service.version} and the mounted package is ${mounted.version} (${mounted.packagePath}) — install the version the World names (npm install -D
|
|
1483
|
+
if (!versionSatisfies(mounted.version, service.version)) throw new Error(`Twin "${service.id}" pins version ${service.version} and the mounted package is ${mounted.version} (${mounted.packagePath}) — install the version the World names (npm install -D --save-exact ${service.package ?? mounted.name ?? `@volter/twin-${service.id}`}@${service.version}), or trial an update in a separate app directory before changing source.version in .volter/world.json (volter-world outdated lists both)`);
|
|
1483
1484
|
}
|
|
1484
1485
|
if (mounted.protocol?.standing === 'refused') throw new Error(`Twin "${service.id}" targets protocol major ${mounted.protocol.major}; this platform serves ${PROTOCOL_VERSION} only (runtime contract R16)`);
|
|
1485
1486
|
return mounted;
|
|
@@ -1789,6 +1790,7 @@ async function startColocatedServices(
|
|
|
1789
1790
|
url,
|
|
1790
1791
|
...sidesRecord(sides),
|
|
1791
1792
|
...(mounted.version !== undefined ? { resolvedVersion: mounted.version } : {}),
|
|
1793
|
+
...(mounted.name !== undefined ? { resolvedPackage: mounted.name } : {}),
|
|
1792
1794
|
...(mounted.protocol !== undefined ? { protocol: mounted.protocol } : {}),
|
|
1793
1795
|
pid: child.pid ?? 0,
|
|
1794
1796
|
log,
|
|
@@ -2028,8 +2030,10 @@ export async function upWorld(configId: string, options: UpWorldOptions = {}, ab
|
|
|
2028
2030
|
try {
|
|
2029
2031
|
const services: Record<string, WorldServiceInstance> = {};
|
|
2030
2032
|
const recordService = (started: WorldServiceInstance) => {
|
|
2031
|
-
const
|
|
2032
|
-
services[started.id] = { ...started, branchState: branchState ?? 'supported'
|
|
2033
|
+
const selected = loaded.config.services.find(service => service.id === started.id);
|
|
2034
|
+
services[started.id] = { ...started, branchState: selected?.branchState ?? 'supported',
|
|
2035
|
+
...(selected?.package || selected?.version ? { pinnedSource: { ...(selected.package ? { package: selected.package } : {}), ...(selected.version ? { version: selected.version } : {}) } } : {}),
|
|
2036
|
+
};
|
|
2033
2037
|
};
|
|
2034
2038
|
const inheritedEnv = admittedEnv(loaded.config.stripEnv);
|
|
2035
2039
|
const env: Record<string, string> = {
|
package/src/schema.ts
CHANGED
|
@@ -308,6 +308,10 @@ export type WorldServiceInstance = {
|
|
|
308
308
|
/** R17 — what the host actually mounted for a colocated twin: the package version read from
|
|
309
309
|
* the module's own package.json, and the protocol standing (R16) it declared. */
|
|
310
310
|
resolvedVersion?: string;
|
|
311
|
+
/** Package identity captured before boot, never inferred from a later installation. */
|
|
312
|
+
resolvedPackage?: string;
|
|
313
|
+
/** Configured selection at boot; distinct from the package actually mounted. */
|
|
314
|
+
pinnedSource?: { package?: string; version?: string };
|
|
311
315
|
protocol?: { declared?: string; major: number; standing: string };
|
|
312
316
|
/** Absent for 'external' services — we do not assign them a port. */
|
|
313
317
|
port?: number;
|
|
@@ -607,8 +611,8 @@ function assertLegacyWorldConfig(value: unknown, path: string): WorldConfig {
|
|
|
607
611
|
if (service.type === 'external') {
|
|
608
612
|
assertExternalServiceConfig(service, path);
|
|
609
613
|
} else {
|
|
610
|
-
if (service.package !== undefined && !(typeof service.package === 'string' &&
|
|
611
|
-
throw new Error(`Service "${service.id}" package must
|
|
614
|
+
if (service.package !== undefined && !(typeof service.package === 'string' && /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(service.package))) {
|
|
615
|
+
throw new Error(`Service "${service.id}" package must be an npm package name; the installed descriptor establishes its vendor and protocol in ${path}`);
|
|
612
616
|
}
|
|
613
617
|
if (service.package === undefined && (!service.command || typeof service.command !== 'string')) {
|
|
614
618
|
throw new Error(`Service "${service.id}" must define command (or package) in ${path}`);
|
package/src/served-world.ts
CHANGED
|
@@ -351,7 +351,7 @@ function streamEnv(id: string, protocol: string): Record<string, string> {
|
|
|
351
351
|
|
|
352
352
|
/** What the doors need of a world: where each twin's state lives and which twins it has. A booted
|
|
353
353
|
* local instance is one; a hosted World describes itself. */
|
|
354
|
-
export type WorldLayout = { dirs: { data: string }; envFile?: string; services: Record<string, { url?: string; protocol?: unknown }> };
|
|
354
|
+
export type WorldLayout = { dirs: { data: string }; envFile?: string; services: Record<string, { url?: string; protocol?: unknown; resolvedPackage?: string; resolvedVersion?: string; pinnedSource?: { package?: string; version?: string } }> };
|
|
355
355
|
/** The host a world's doors run in: its layout, and how a request reaches a twin's wire — a local
|
|
356
356
|
* host forwards to the twin's own port, a hosted World calls the pack in-process. */
|
|
357
357
|
export type DoorHost = {
|
|
@@ -1961,7 +1961,11 @@ export class WorldDoors {
|
|
|
1961
1961
|
const marker = getActiveWorldStore().read(join(this.controlRoot(name), stateDirName(), 'world', name, 'refresh.json'));
|
|
1962
1962
|
let refresh: unknown = null; try { if (marker !== null) refresh = JSON.parse(marker); } catch { refresh = null; }
|
|
1963
1963
|
const last = [...parentEntries(state, this.controlRoot(name))].reverse().find((e) => e.landsId && e.receipt);
|
|
1964
|
-
|
|
1964
|
+
const mounted = this.up().services[name];
|
|
1965
|
+
const origin = readBranchMeta(state, this.controlRoot(name))?.origin;
|
|
1966
|
+
const identity = { mounted: mounted?.resolvedPackage ? { package: mounted.resolvedPackage, version: mounted.resolvedVersion ?? null } : null, pinned: mounted?.pinnedSource ?? null };
|
|
1967
|
+
const dataOrigin = origin ? { kind: 'shared-history', url: origin.at } : refresh ? { kind: 'vendor-refresh', url: root?.url ?? null } : { kind: 'unrecorded', url: null };
|
|
1968
|
+
return Response.json({ twin: name, state, identity, dataOrigin, execution: { rootConnected: Boolean(root), deploy: root?.deploy ?? null }, protocol: mounted?.protocol ?? null, screens: await this.screensOf(name), root: root ?? null, credential: sealedCredentialInfo(this.worldRoot, name), refresh, position: wholeLog(state, this.controlRoot(name)).length, lastReceipt: last?.receipt ?? null });
|
|
1965
1969
|
}
|
|
1966
1970
|
if (verb === 'refresh' && request.method === 'POST') {
|
|
1967
1971
|
const root = rootForControlRoot(this.controlRoot(name), name);
|