@telorun/kernel 0.24.1 → 0.25.0
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/application-env.d.ts +34 -0
- package/dist/application-env.d.ts.map +1 -1
- package/dist/application-env.js +77 -0
- package/dist/application-env.js.map +1 -1
- package/dist/controller-loaders/npm-loader.d.ts +41 -12
- package/dist/controller-loaders/npm-loader.d.ts.map +1 -1
- package/dist/controller-loaders/npm-loader.js +117 -51
- package/dist/controller-loaders/npm-loader.js.map +1 -1
- package/dist/controller-registry.d.ts +9 -0
- package/dist/controller-registry.d.ts.map +1 -1
- package/dist/controller-registry.js +18 -0
- package/dist/controller-registry.js.map +1 -1
- package/dist/controllers/module/import-controller.d.ts.map +1 -1
- package/dist/controllers/module/import-controller.js +50 -28
- package/dist/controllers/module/import-controller.js.map +1 -1
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +13 -5
- package/dist/evaluation-context.js.map +1 -1
- package/dist/kernel.d.ts +13 -2
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +41 -2
- package/dist/kernel.js.map +1 -1
- package/dist/module-context.d.ts +56 -7
- package/dist/module-context.d.ts.map +1 -1
- package/dist/module-context.js +121 -10
- package/dist/module-context.js.map +1 -1
- package/dist/schema-validator.d.ts.map +1 -1
- package/dist/schema-validator.js +39 -3
- package/dist/schema-validator.js.map +1 -1
- package/package.json +2 -2
- package/src/application-env.ts +81 -0
- package/src/controller-loaders/npm-loader.ts +124 -58
- package/src/controller-registry.ts +18 -0
- package/src/controllers/module/import-controller.ts +58 -28
- package/src/evaluation-context.ts +15 -5
- package/src/kernel.ts +52 -2
- package/src/module-context.ts +153 -11
- package/src/schema-validator.ts +41 -3
package/dist/schema-validator.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { evaluate } from "@marcbachmann/cel-js";
|
|
2
|
-
import { RuntimeError } from "@telorun/sdk";
|
|
2
|
+
import { isCompiledValue, RuntimeError } from "@telorun/sdk";
|
|
3
3
|
import AjvModule from "ajv";
|
|
4
4
|
import standaloneCodeMod from "ajv/dist/standalone/index.js";
|
|
5
5
|
import addFormats from "ajv-formats";
|
|
@@ -8,7 +8,7 @@ import * as fs from "node:fs";
|
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { formatAjvErrors } from "./manifest-schemas.js";
|
|
11
|
-
import { ManifestRootSchema, normalizeRefSlots } from "@telorun/templating";
|
|
11
|
+
import { isTaggedSentinel, ManifestRootSchema, normalizeRefSlots } from "@telorun/templating";
|
|
12
12
|
const Ajv = AjvModule.default ?? AjvModule;
|
|
13
13
|
// AJV's standalone subpath is CJS — the default export shows up as either
|
|
14
14
|
// the function itself or `.default` depending on how the bundler/loader
|
|
@@ -76,6 +76,39 @@ function verifyAndExtractBody(text) {
|
|
|
76
76
|
const actual = createHash("sha256").update(body).digest("hex");
|
|
77
77
|
return actual === match[1] ? body : null;
|
|
78
78
|
}
|
|
79
|
+
/** Deep-clone `value` collapsing every CEL/template sentinel to its original
|
|
80
|
+
* `source` string, for use as the validator cache *key* only.
|
|
81
|
+
*
|
|
82
|
+
* The same `Telo.Definition` schema reaches `compile()` in two forms: the
|
|
83
|
+
* build-time validator warm feeds the raw analysis graph (parse-time
|
|
84
|
+
* `{__tagged, engine, source}` sentinels, or plain `${{ }}` strings for inline
|
|
85
|
+
* templates), while the runtime feeds the compile-loader output (`{__compiled,
|
|
86
|
+
* source, parts}`). A `!cel` / `!sql` tag embedded anywhere in a schema — most
|
|
87
|
+
* commonly inside `examples` / `description`, but the loader rewrites them at
|
|
88
|
+
* any position — therefore serialises differently between the two, so without
|
|
89
|
+
* normalisation the same kind hashes differently and the runtime misses the
|
|
90
|
+
* warmed cache (and fails to persist it read-only). Both sentinel shapes carry
|
|
91
|
+
* the same original `source`, so collapsing each to that string converges them.
|
|
92
|
+
*
|
|
93
|
+
* This rewrites only the hash key — AJV still compiles the full `injected`
|
|
94
|
+
* schema. Unlike dropping keys by name, it never removes a structural node, so
|
|
95
|
+
* a property literally named `description` / `examples` keeps its schema in the
|
|
96
|
+
* key and two genuinely different shapes never collide. */
|
|
97
|
+
function normalizeSentinelsForHash(value) {
|
|
98
|
+
if (isCompiledValue(value) || isTaggedSentinel(value)) {
|
|
99
|
+
return typeof value.source === "string" ? value.source : { __teloSentinel: true };
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(value))
|
|
102
|
+
return value.map(normalizeSentinelsForHash);
|
|
103
|
+
if (value && typeof value === "object") {
|
|
104
|
+
const out = {};
|
|
105
|
+
for (const [k, v] of Object.entries(value)) {
|
|
106
|
+
out[k] = normalizeSentinelsForHash(v);
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
79
112
|
export class SchemaValidator {
|
|
80
113
|
constructor() {
|
|
81
114
|
this.typeRules = new Map();
|
|
@@ -179,7 +212,10 @@ export class SchemaValidator {
|
|
|
179
212
|
// would otherwise reject.
|
|
180
213
|
const injected = normalizeRefSlots(withImplicit);
|
|
181
214
|
const hash = createHash("sha256")
|
|
182
|
-
.update(JSON.stringify({
|
|
215
|
+
.update(JSON.stringify({
|
|
216
|
+
runtime: VALIDATOR_RUNTIME_TAG,
|
|
217
|
+
schema: normalizeSentinelsForHash(injected),
|
|
218
|
+
}))
|
|
183
219
|
.digest("hex")
|
|
184
220
|
.slice(0, 32);
|
|
185
221
|
const cachedByHash = this.hashCache.get(hash);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-validator.js","sourceRoot":"","sources":["../src/schema-validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAiB,YAAY,EAAY,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"schema-validator.js","sourceRoot":"","sources":["../src/schema-validator.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/kernel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Telo Runtime - A lightweight, polyglot execution host.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@marcbachmann/cel-js": "^7.6.1",
|
|
49
49
|
"@sinclair/typebox": "^0.34.48",
|
|
50
|
-
"@telorun/analyzer": "0.
|
|
50
|
+
"@telorun/analyzer": "0.23.0",
|
|
51
51
|
"@telorun/templating": "0.8.0",
|
|
52
52
|
"ajv": "^8.17.1",
|
|
53
53
|
"ajv-formats": "^3.0.1",
|
package/src/application-env.ts
CHANGED
|
@@ -80,6 +80,87 @@ export function resolveApplicationEnv(
|
|
|
80
80
|
return { variables, secrets, ports };
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Build-time cache warm: compile — but do NOT validate — the residual schema
|
|
85
|
+
* for every `variables` / `secrets` / `ports` entry the runtime
|
|
86
|
+
* `resolveApplicationEnv` would, so each standalone validator lands in the
|
|
87
|
+
* on-disk `__validators` cache. Schema *compilation* is value-independent, so
|
|
88
|
+
* this needs none of the host env vars / secrets `resolveApplicationEnv`
|
|
89
|
+
* requires — it can run during `telo install`. At run time on a read-only
|
|
90
|
+
* session rootfs `resolveApplicationEnv` then hits the cache instead of
|
|
91
|
+
* recompiling and failing to persist (ENOENT / EROFS).
|
|
92
|
+
*
|
|
93
|
+
* Mirrors `resolveApplicationEnv` exactly: same `residualEntrySchema` per
|
|
94
|
+
* variable/secret and the same `PORT_RESIDUAL_SCHEMA`, so the cache keys
|
|
95
|
+
* match byte-for-byte. Compile failures are swallowed — a genuinely broken
|
|
96
|
+
* schema surfaces through the normal analysis/runtime path, not here.
|
|
97
|
+
*/
|
|
98
|
+
export function precompileApplicationEnvSchemas(
|
|
99
|
+
manifest: Record<string, any>,
|
|
100
|
+
validator: SchemaValidator,
|
|
101
|
+
): void {
|
|
102
|
+
const compile = (schema: Record<string, unknown>): void => {
|
|
103
|
+
try {
|
|
104
|
+
validator.compile(schema as any);
|
|
105
|
+
} catch {
|
|
106
|
+
// Broken schemas are reported by analysis / runtime, not the warm pass.
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
for (const block of [manifest.variables, manifest.secrets]) {
|
|
110
|
+
if (!block || typeof block !== "object" || Array.isArray(block)) continue;
|
|
111
|
+
for (const entry of Object.values(block as Record<string, EnvEntry>)) {
|
|
112
|
+
if (!entry || typeof entry !== "object") continue;
|
|
113
|
+
compile(residualEntrySchema(entry as Record<string, unknown>));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const ports = manifest.ports;
|
|
117
|
+
if (
|
|
118
|
+
ports &&
|
|
119
|
+
typeof ports === "object" &&
|
|
120
|
+
!Array.isArray(ports) &&
|
|
121
|
+
Object.keys(ports).length > 0
|
|
122
|
+
) {
|
|
123
|
+
compile(PORT_RESIDUAL_SCHEMA);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build-time cache warm for resource-config validators. The runtime
|
|
129
|
+
* `_createInstance` compiles `controller.schema` — which falls back to the
|
|
130
|
+
* declaring `Telo.Definition`'s own `schema` — to validate every resource's
|
|
131
|
+
* config, then validates inputs/outputs against `inputType` / `outputType`.
|
|
132
|
+
* The analyze-only warm pass stops before instantiation, so without this those
|
|
133
|
+
* validators are absent from the `__validators` cache and the runtime
|
|
134
|
+
* recompiles (and, on a read-only image, fails to persist) them on every boot.
|
|
135
|
+
*
|
|
136
|
+
* Compiling each definition's `schema` (plus any inline `inputType` /
|
|
137
|
+
* `outputType` object schemas) here writes them into the same content-addressed
|
|
138
|
+
* cache the runtime reads, keyed identically because the same schema object is
|
|
139
|
+
* fed to the same `validator.compile`. Definitions whose controller exports its
|
|
140
|
+
* own `schema` (rare) still recompile at runtime — that needs the controller
|
|
141
|
+
* loaded, which the warm pass does not do. Compile failures are swallowed; a
|
|
142
|
+
* genuinely broken schema surfaces through analysis / runtime, not here.
|
|
143
|
+
*/
|
|
144
|
+
export function precompileDefinitionSchemas(
|
|
145
|
+
manifests: Array<Record<string, any>>,
|
|
146
|
+
validator: SchemaValidator,
|
|
147
|
+
): void {
|
|
148
|
+
const compile = (schema: unknown): void => {
|
|
149
|
+
if (!schema || typeof schema !== "object") return;
|
|
150
|
+
try {
|
|
151
|
+
validator.compile(schema as any);
|
|
152
|
+
} catch {
|
|
153
|
+
// Broken schemas are reported by analysis / runtime, not the warm pass.
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
for (const m of manifests) {
|
|
157
|
+
if (m?.kind !== "Telo.Definition") continue;
|
|
158
|
+
compile(m.schema);
|
|
159
|
+
compile(m.inputType);
|
|
160
|
+
compile(m.outputType);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
83
164
|
/**
|
|
84
165
|
* Populate the root Application's `ports` namespace from host environment
|
|
85
166
|
* variables. Mirrors `resolveBlock` but fixes the value type to a port integer
|
|
@@ -104,14 +104,17 @@ export interface NpmControllerLoaderOptions {
|
|
|
104
104
|
* local entry manifest (`file://` URL or bare path) the root lives at
|
|
105
105
|
* `<entry-manifest-dir>/.telo/npm/`; for an HTTP(S) entry URL it lives in a
|
|
106
106
|
* user-level cache keyed by `sha256(entryUrl)` (see `computeInstallRoot`).
|
|
107
|
-
* Every controller — registry tag or `local_path` — is installed
|
|
108
|
-
* `npm install <spec
|
|
109
|
-
* `<root>/node_modules/<
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
107
|
+
* Every controller — registry tag or `local_path` — is installed under a
|
|
108
|
+
* version-scoped npm alias (`npm install <alias>@<spec>`, see `installAlias`)
|
|
109
|
+
* into this root, then imported from `<root>/node_modules/<alias>`. The alias
|
|
110
|
+
* encodes `name@version`, so a graph that references one package at two
|
|
111
|
+
* versions keeps both in the single flat `node_modules` instead of the last
|
|
112
|
+
* `--save` clobbering the first. This still collapses two parallel module
|
|
113
|
+
* realms (kernel-side @telorun/sdk vs. controller-side @telorun/sdk) into one:
|
|
114
|
+
* `@telorun/sdk` is exempt from aliasing and wired in as a `file:` dep under
|
|
115
|
+
* its real name, npm/pnpm hoist a single copy, and Node's ESM resolver follows
|
|
116
|
+
* it to the same realpath as the kernel — so `Stream` (and any other
|
|
117
|
+
* class-identity-sensitive type) has a single constructor across the process.
|
|
115
118
|
*
|
|
116
119
|
* Per-kernel state lives on each `NpmControllerLoader` instance (one is
|
|
117
120
|
* constructed per `ControllerLoader`, which is itself constructed per
|
|
@@ -169,17 +172,21 @@ export class NpmControllerLoader {
|
|
|
169
172
|
throw new Error(`Invalid PURL '${purl}': missing package name`);
|
|
170
173
|
}
|
|
171
174
|
const packageName = parsed.namespace ? `${parsed.namespace}/${parsed.name}` : parsed.name;
|
|
175
|
+
const version = parsed.version ?? null;
|
|
176
|
+
// Version-scope the install under an alias so the same package at two
|
|
177
|
+
// versions can coexist in one flat node_modules (see installAlias).
|
|
178
|
+
const alias = installAlias(packageName, version);
|
|
172
179
|
|
|
173
180
|
const installRoot = await this.ensureInstallRoot();
|
|
174
181
|
const resolved = await resolveInstallSpec(parsed, packageName, baseUri);
|
|
175
182
|
const source = await this.installPackage(
|
|
176
183
|
installRoot,
|
|
177
|
-
|
|
184
|
+
alias,
|
|
178
185
|
resolved.spec,
|
|
179
186
|
resolved.kind,
|
|
180
|
-
|
|
187
|
+
version,
|
|
181
188
|
);
|
|
182
|
-
const instance = await loadFromInstall(installRoot,
|
|
189
|
+
const instance = await loadFromInstall(installRoot, alias, parsed.subpath ?? null, purl);
|
|
183
190
|
return { instance, source };
|
|
184
191
|
}
|
|
185
192
|
|
|
@@ -296,6 +303,11 @@ export class NpmControllerLoader {
|
|
|
296
303
|
installRoot: string,
|
|
297
304
|
dependencies: Record<string, string>,
|
|
298
305
|
): Promise<void> {
|
|
306
|
+
// `dependencies` is only the realm-collapse deps (`@telorun/sdk`), keyed by
|
|
307
|
+
// real `name@spec`. Controllers, by contrast, key `installedSpecs` by their
|
|
308
|
+
// version-scoped alias in `installPackage`. The two key spaces are
|
|
309
|
+
// intentionally disjoint — realm-collapse names are wired in here and never
|
|
310
|
+
// flow through `installPackage`, so an alias and a `name@spec` never clash.
|
|
299
311
|
for (const [name, spec] of Object.entries(dependencies)) {
|
|
300
312
|
this.installedSpecs.add(`${name}@${spec}`);
|
|
301
313
|
}
|
|
@@ -303,11 +315,14 @@ export class NpmControllerLoader {
|
|
|
303
315
|
}
|
|
304
316
|
|
|
305
317
|
/**
|
|
306
|
-
* Install one controller
|
|
307
|
-
*
|
|
308
|
-
* `npm install` call. If the
|
|
309
|
-
*
|
|
318
|
+
* Install one controller into the existing root under its version-scoped
|
|
319
|
+
* `alias` folder. Single-flight per alias within the process; cross-process
|
|
320
|
+
* safety is the fs-lock around the `npm install` call. If the alias is
|
|
321
|
+
* already installed (right version on disk, or a matching `file:` record),
|
|
322
|
+
* skip — reusing the previous install.
|
|
310
323
|
*
|
|
324
|
+
* `spec` is the source half of the install (`npm:<name>@<version>` or
|
|
325
|
+
* `file:<abs>`); the package manager is invoked with `<alias>@<spec>`.
|
|
311
326
|
* `kind` distinguishes a `local_path` source (loader synthesized `file:`
|
|
312
327
|
* spec) from a registry tag. The CLI silences progress for both `cache`
|
|
313
328
|
* and `local`; `npm-install` is the only event surfaced. Folding this
|
|
@@ -316,12 +331,12 @@ export class NpmControllerLoader {
|
|
|
316
331
|
*/
|
|
317
332
|
private async installPackage(
|
|
318
333
|
installRoot: string,
|
|
319
|
-
|
|
334
|
+
alias: string,
|
|
320
335
|
spec: string,
|
|
321
336
|
kind: SpecKind,
|
|
322
337
|
requestedVersion: string | null,
|
|
323
338
|
): Promise<NpmResolveSource> {
|
|
324
|
-
const cacheKey =
|
|
339
|
+
const cacheKey = alias;
|
|
325
340
|
if (this.installedSpecs.has(cacheKey)) return "cache";
|
|
326
341
|
|
|
327
342
|
const inFlight = this.inFlight.get(cacheKey);
|
|
@@ -330,17 +345,20 @@ export class NpmControllerLoader {
|
|
|
330
345
|
return "cache";
|
|
331
346
|
}
|
|
332
347
|
|
|
333
|
-
|
|
348
|
+
// The package is installed under its version-scoped alias, so its folder
|
|
349
|
+
// name in node_modules is the alias, not the bare package name.
|
|
350
|
+
const targetPath = path.join(installRoot, "node_modules", alias);
|
|
334
351
|
|
|
335
352
|
// Registry fast path: compare against the installed package's own
|
|
336
|
-
// `package.json` version field.
|
|
337
|
-
//
|
|
338
|
-
//
|
|
339
|
-
// never matches and every fresh `NpmControllerLoader`
|
|
340
|
-
// `Telo.Definition.init`) would fall through to a no-op but ~200ms
|
|
353
|
+
// `package.json` version field. The recorded dep spec can't be used here
|
|
354
|
+
// because npm rewrites registry specs on `--save` — we pass
|
|
355
|
+
// `<alias>@npm:@scope/pkg@0.3.4`, npm writes `npm:@scope/pkg@^0.3.4` — so a
|
|
356
|
+
// string comparison never matches and every fresh `NpmControllerLoader`
|
|
357
|
+
// (one per `Telo.Definition.init`) would fall through to a no-op but ~200ms
|
|
341
358
|
// `npm install`. Reading the installed package.json sidesteps that
|
|
342
|
-
// entirely: if the requested PURL version equals what's on disk
|
|
343
|
-
// already have the right thing.
|
|
359
|
+
// entirely: if the requested PURL version equals what's on disk under this
|
|
360
|
+
// alias, we already have the right thing. Because the alias encodes the
|
|
361
|
+
// version, a present alias folder is always the requested version.
|
|
344
362
|
//
|
|
345
363
|
// Ranges (`^1.0.0`, `~2.3.0`) fall through to a real `npm install` —
|
|
346
364
|
// they're rare in PURLs (which typically pin) and a proper range check
|
|
@@ -354,37 +372,47 @@ export class NpmControllerLoader {
|
|
|
354
372
|
this.installedSpecs.add(cacheKey);
|
|
355
373
|
return "cache";
|
|
356
374
|
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
375
|
+
} else {
|
|
376
|
+
// Local (`file:`) spec fast path: consult the in-process snapshot of the
|
|
377
|
+
// install root's `dependencies` map (seeded by `materializeInstallRoot`).
|
|
378
|
+
// Normalize because npm rewrites absolute `file:` deps to relative paths
|
|
379
|
+
// inside the install root's `package.json` — the loader passes the
|
|
380
|
+
// absolute path, the on-disk record is `file:../../foo`.
|
|
381
|
+
const cachedSpec = this.rootDeps[alias];
|
|
382
|
+
if (
|
|
383
|
+
cachedSpec !== undefined &&
|
|
384
|
+
normalizeFileSpec(cachedSpec, installRoot) === normalizeFileSpec(spec, installRoot) &&
|
|
385
|
+
(await pathExists(targetPath))
|
|
386
|
+
) {
|
|
387
|
+
this.installedSpecs.add(cacheKey);
|
|
388
|
+
return "cache";
|
|
389
|
+
}
|
|
372
390
|
}
|
|
373
391
|
|
|
374
392
|
const work = (async () => {
|
|
375
393
|
await withInstallLock(installRoot, async () => {
|
|
376
394
|
// Re-check inside the lock: a peer process may have installed the
|
|
377
|
-
// spec between the fast-path miss and our acquisition.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
395
|
+
// spec between the fast-path miss and our acquisition.
|
|
396
|
+
if (kind === "registry") {
|
|
397
|
+
const installedVersion = await readInstalledVersion(targetPath);
|
|
398
|
+
if (
|
|
399
|
+
installedVersion !== null &&
|
|
400
|
+
(requestedVersion === null || requestedVersion === installedVersion)
|
|
401
|
+
) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
// Normalize the on-disk record to absolute form before comparing —
|
|
406
|
+
// npm rewrites `file:` deps to be relative to the install root.
|
|
407
|
+
const lockedSpec = await readDepSpec(installRoot, alias);
|
|
408
|
+
if (
|
|
409
|
+
lockedSpec !== undefined &&
|
|
410
|
+
normalizeFileSpec(lockedSpec, installRoot) === normalizeFileSpec(spec, installRoot) &&
|
|
411
|
+
(await pathExists(targetPath))
|
|
412
|
+
) {
|
|
413
|
+
this.rootDeps[alias] = lockedSpec;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
388
416
|
}
|
|
389
417
|
|
|
390
418
|
await runPackageManager(installRoot, [
|
|
@@ -394,14 +422,17 @@ export class NpmControllerLoader {
|
|
|
394
422
|
"--silent",
|
|
395
423
|
...PEER_INSTALL_FLAGS,
|
|
396
424
|
"--save",
|
|
397
|
-
spec
|
|
425
|
+
// `<alias>@<source-spec>` installs the package under the alias folder
|
|
426
|
+
// (`npm:` for registry, `file:` for local) so multiple versions of
|
|
427
|
+
// one package name coexist in the single install root.
|
|
428
|
+
`${alias}@${spec}`,
|
|
398
429
|
]);
|
|
399
430
|
// Re-read what npm actually wrote (it normalizes `file:` paths to
|
|
400
431
|
// be relative to the install root). Caching the spec in its on-disk
|
|
401
432
|
// form keeps subsequent fast-path comparisons stable.
|
|
402
|
-
const written = await readDepSpec(installRoot,
|
|
403
|
-
if (written !== undefined) this.rootDeps[
|
|
404
|
-
else this.rootDeps[
|
|
433
|
+
const written = await readDepSpec(installRoot, alias);
|
|
434
|
+
if (written !== undefined) this.rootDeps[alias] = written;
|
|
435
|
+
else this.rootDeps[alias] = spec;
|
|
405
436
|
});
|
|
406
437
|
})();
|
|
407
438
|
|
|
@@ -693,6 +724,35 @@ function computeInstallRoot(entryUrl: string): string {
|
|
|
693
724
|
);
|
|
694
725
|
}
|
|
695
726
|
|
|
727
|
+
/**
|
|
728
|
+
* Version-qualified install alias for a controller package. The kernel's single
|
|
729
|
+
* flat install root can hold only one folder per package name, but a manifest
|
|
730
|
+
* graph may legitimately reference the same package at multiple versions (e.g.
|
|
731
|
+
* a library pins `@telorun/mcp-client@0.3.1` while the app uses `0.4.0`).
|
|
732
|
+
* Installing each `name@version` under a distinct npm alias
|
|
733
|
+
* (`npm install <alias>@npm:<name>@<version>`) lets every version coexist in
|
|
734
|
+
* one `node_modules`, mirroring the per-(name, version) identity of a Telo
|
|
735
|
+
* module singleton. `@telorun/sdk` is intentionally NOT routed through here —
|
|
736
|
+
* it stays under its real name so realm-collapse hoists a single copy across
|
|
737
|
+
* the kernel/controller boundary (see REALM_COLLAPSE_NAMES).
|
|
738
|
+
*
|
|
739
|
+
* The result is a valid unscoped npm package name: the scope `@` is dropped,
|
|
740
|
+
* `/` becomes `__`, and any character outside npm's name grammar is replaced
|
|
741
|
+
* with `-`. Because that sanitization is lossy (e.g. build metadata `1.0.0+x`
|
|
742
|
+
* and prerelease `1.0.0-x` both fold to `1.0.0-x`, and a `__` already in a
|
|
743
|
+
* package name overlaps the scope separator), the alias ends with a short hash
|
|
744
|
+
* of the *exact* `name@version` — so two distinct pairs can never collide onto
|
|
745
|
+
* one folder regardless of how their readable prefixes sanitize. The readable
|
|
746
|
+
* prefix is kept purely so the install tree is greppable.
|
|
747
|
+
*/
|
|
748
|
+
function installAlias(packageName: string, version: string | null): string {
|
|
749
|
+
const prefix = `${packageName.replace(/^@/, "").replace(/\//g, "__")}__${version ?? "latest"}`
|
|
750
|
+
.replace(/[^a-zA-Z0-9._-]/g, "-")
|
|
751
|
+
.toLowerCase();
|
|
752
|
+
const digest = sha256(`${packageName}@${version ?? ""}`).slice(0, 8);
|
|
753
|
+
return `${prefix}__${digest}`;
|
|
754
|
+
}
|
|
755
|
+
|
|
696
756
|
/**
|
|
697
757
|
* Normalize a `file:` spec to an absolute path so two specs that point at the
|
|
698
758
|
* same source — one absolute (what the loader synthesizes from `local_path`)
|
|
@@ -770,7 +830,10 @@ async function resolveInstallSpec(
|
|
|
770
830
|
return { kind: "local", spec: `file:${absolutePath}`, absolutePath };
|
|
771
831
|
}
|
|
772
832
|
}
|
|
773
|
-
|
|
833
|
+
// `npm:` source spec so the caller can install it under a version-scoped
|
|
834
|
+
// alias (`<alias>@npm:<name>@<version>`); both versions of one package name
|
|
835
|
+
// then coexist in the single flat install root.
|
|
836
|
+
const spec = parsed.version ? `npm:${packageName}@${parsed.version}` : `npm:${packageName}`;
|
|
774
837
|
return { kind: "registry", spec };
|
|
775
838
|
}
|
|
776
839
|
|
|
@@ -782,11 +845,13 @@ async function resolveInstallSpec(
|
|
|
782
845
|
*/
|
|
783
846
|
async function loadFromInstall(
|
|
784
847
|
installRoot: string,
|
|
785
|
-
|
|
848
|
+
alias: string,
|
|
786
849
|
subpath: string | null,
|
|
787
850
|
purl: string,
|
|
788
851
|
): Promise<ControllerInstance> {
|
|
789
|
-
|
|
852
|
+
// The package lives under its version-scoped alias folder (see installAlias),
|
|
853
|
+
// not its bare scoped name.
|
|
854
|
+
const packageRoot = path.join(installRoot, "node_modules", alias);
|
|
790
855
|
const entry = subpath ? `./${subpath}` : ".";
|
|
791
856
|
const entryFile = await resolvePackageEntry(packageRoot, entry);
|
|
792
857
|
// ESM dynamic `import()` accepts either a relative specifier or a `file://`
|
|
@@ -937,6 +1002,7 @@ function resolveExportTargetValue(
|
|
|
937
1002
|
* Not part of the kernel's public API — consumers should not import these.
|
|
938
1003
|
*/
|
|
939
1004
|
export const __testing__ = {
|
|
1005
|
+
installAlias,
|
|
940
1006
|
normalizeFileSpec,
|
|
941
1007
|
resolvePackageExportTarget,
|
|
942
1008
|
resolveExportTargetValue,
|
|
@@ -98,6 +98,24 @@ export class ControllerRegistry {
|
|
|
98
98
|
return Array.from(this.controllersByKind.keys());
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Distinct controller `schema` objects across all registered kinds (one per
|
|
103
|
+
* kind, default fingerprint preferred). Used by the build-time validator warm
|
|
104
|
+
* to pre-compile the framework/builtin controller schemas (`Telo.Import`,
|
|
105
|
+
* `Telo.Definition`, the module controller, …) the runtime validates
|
|
106
|
+
* resources against — module-defined kinds aren't registered here until
|
|
107
|
+
* instantiation, so those are warmed from the static manifests instead.
|
|
108
|
+
*/
|
|
109
|
+
getControllerSchemas(): object[] {
|
|
110
|
+
const schemas: object[] = [];
|
|
111
|
+
for (const byFp of this.controllersByKind.values()) {
|
|
112
|
+
const controller = byFp.get(DEFAULT_FINGERPRINT) ?? byFp.values().next().value;
|
|
113
|
+
const schema = controller?.schema;
|
|
114
|
+
if (schema && typeof schema === "object") schemas.push(schema);
|
|
115
|
+
}
|
|
116
|
+
return schemas;
|
|
117
|
+
}
|
|
118
|
+
|
|
101
119
|
/**
|
|
102
120
|
* Register a controller for a (kind, fingerprint). Multiple registrations
|
|
103
121
|
* for the same kind with different fingerprints coexist; same fingerprint
|