@telorun/kernel 0.84.0 → 0.85.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/bundle/module-artifact.d.ts +1 -1
- package/dist/bundle/module-artifact.d.ts.map +1 -1
- package/dist/bundle/module-artifact.js +3 -3
- package/dist/bundle/module-artifact.js.map +1 -1
- package/dist/controller-loaders/bundle-loader.d.ts +5 -5
- package/dist/controller-loaders/bundle-loader.js +7 -7
- package/dist/controller-loaders/bundle-loader.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +0 -5
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +5 -7
- package/dist/kernel.js.map +1 -1
- package/dist/manifest-sources/local-manifest-cache-source.d.ts +6 -6
- package/dist/manifest-sources/local-manifest-cache-source.d.ts.map +1 -1
- package/dist/manifest-sources/local-manifest-cache-source.js +11 -12
- package/dist/manifest-sources/local-manifest-cache-source.js.map +1 -1
- package/dist/runtime-seam.d.ts.map +1 -1
- package/dist/runtime-seam.js +1 -2
- package/dist/runtime-seam.js.map +1 -1
- package/dist/transports/http-transport.d.ts +37 -0
- package/dist/transports/http-transport.d.ts.map +1 -0
- package/dist/transports/http-transport.js +168 -0
- package/dist/transports/http-transport.js.map +1 -0
- package/dist/transports/transport-registry.d.ts +13 -14
- package/dist/transports/transport-registry.d.ts.map +1 -1
- package/dist/transports/transport-registry.js +17 -24
- package/dist/transports/transport-registry.js.map +1 -1
- package/package.json +2 -2
- package/src/bundle/module-artifact.ts +2 -3
- package/src/controller-loaders/bundle-loader.ts +7 -7
- package/src/index.ts +1 -1
- package/src/kernel.ts +4 -11
- package/src/manifest-sources/local-manifest-cache-source.ts +9 -16
- package/src/runtime-seam.ts +1 -2
- package/src/transports/http-transport.ts +209 -0
- package/src/transports/transport-registry.ts +17 -24
- package/dist/transports/registry-transport.d.ts +0 -41
- package/dist/transports/registry-transport.d.ts.map +0 -1
- package/dist/transports/registry-transport.js +0 -282
- package/dist/transports/registry-transport.js.map +0 -1
- package/src/transports/registry-transport.ts +0 -339
package/src/kernel.ts
CHANGED
|
@@ -129,10 +129,6 @@ export interface KernelOptions {
|
|
|
129
129
|
* fails to dispatch). Order matters — later entries take priority over
|
|
130
130
|
* earlier ones (sources are unshifted onto the dispatch chain). */
|
|
131
131
|
sources: ManifestSource[];
|
|
132
|
-
/** Base URL for the registry source. When unset, the `RegistrySource`
|
|
133
|
-
* default applies. Callers (e.g. the CLI) are responsible for resolving
|
|
134
|
-
* `TELO_REGISTRY_URL` or any other env-based fallback before passing. */
|
|
135
|
-
registryUrl?: string;
|
|
136
132
|
}
|
|
137
133
|
|
|
138
134
|
/**
|
|
@@ -195,7 +191,6 @@ export class Kernel implements IKernel {
|
|
|
195
191
|
readonly stderr: NodeJS.WritableStream;
|
|
196
192
|
readonly env: Record<string, string | undefined>;
|
|
197
193
|
readonly argv: string[];
|
|
198
|
-
readonly registryUrl: string | undefined;
|
|
199
194
|
/** The sources this kernel was constructed with, kept so `ctx.runtime` can
|
|
200
195
|
* give a child manifest — or a static check of one — the same resolution
|
|
201
196
|
* chain this kernel runs on. The transports come from the registry and are
|
|
@@ -232,11 +227,10 @@ export class Kernel implements IKernel {
|
|
|
232
227
|
return { traceId: ambient.traceId, spanId };
|
|
233
228
|
});
|
|
234
229
|
this.argv = options.argv ?? [];
|
|
235
|
-
this.registryUrl = options.registryUrl;
|
|
236
230
|
// Resolution sources come from the transport registry, so a scheme-owning
|
|
237
231
|
// transport (OCI, later S3) joins the loader's dispatch chain by being
|
|
238
232
|
// registered — no source-chain edits here.
|
|
239
|
-
this.loader = new Loader(defaultTransportRegistry(
|
|
233
|
+
this.loader = new Loader(defaultTransportRegistry().sources(), {
|
|
240
234
|
celHandlers: nodeCelHandlers,
|
|
241
235
|
});
|
|
242
236
|
this.injectedSources = [...options.sources];
|
|
@@ -334,8 +328,8 @@ export class Kernel implements IKernel {
|
|
|
334
328
|
// import-controller's independent re-resolution onto the winning source so a
|
|
335
329
|
// sub-library importing a lower version loads the same controller/definition
|
|
336
330
|
// the analyzer registered — never a second, colliding copy. Keyed by
|
|
337
|
-
// canonical URL; `canonicalize` maps a
|
|
338
|
-
//
|
|
331
|
+
// canonical URL; `canonicalize` maps a ref returned verbatim by the loader
|
|
332
|
+
// to the URL the graph walk already resolved it to.
|
|
339
333
|
const overrides = this._loadedGraph?.overrides;
|
|
340
334
|
if (overrides && overrides.size > 0) {
|
|
341
335
|
const canonical = this.loader.canonicalize(resolved) ?? resolved;
|
|
@@ -1064,7 +1058,7 @@ export class Kernel implements IKernel {
|
|
|
1064
1058
|
private buildModuleArtifacts(graph: LoadedGraph, manifestsDir: string | undefined): void {
|
|
1065
1059
|
this.moduleArtifacts.clear();
|
|
1066
1060
|
this.siblingLibraries.clear();
|
|
1067
|
-
const transports = defaultTransportRegistry(
|
|
1061
|
+
const transports = defaultTransportRegistry();
|
|
1068
1062
|
const entryDir = this._entryUrl ? resolveEntryDir(this._entryUrl) ?? "" : "";
|
|
1069
1063
|
// The same pre-anchor root `LocalManifestCacheSource` falls back to. Layers
|
|
1070
1064
|
// live beside the cached manifest, so both halves have to look in the same
|
|
@@ -1086,7 +1080,6 @@ export class Kernel implements IKernel {
|
|
|
1086
1080
|
file.requestedUrl,
|
|
1087
1081
|
file.source,
|
|
1088
1082
|
entryDir,
|
|
1089
|
-
this.registryUrl,
|
|
1090
1083
|
manifestsDir,
|
|
1091
1084
|
legacyDir,
|
|
1092
1085
|
);
|
|
@@ -15,7 +15,6 @@ import { TransportRegistry, defaultTransportRegistry } from "../transports/trans
|
|
|
15
15
|
import { findWorkspaceRoot } from "../workspace-marker.js";
|
|
16
16
|
|
|
17
17
|
const CACHE_SUBDIR = ".telo/manifests";
|
|
18
|
-
const DEFAULT_REGISTRY_URL = "https://registry.telo.run";
|
|
19
18
|
|
|
20
19
|
/** Verify that `candidate` resolves to a path under `root`. Returns the
|
|
21
20
|
* candidate path on success, `null` when any segment escapes the root.
|
|
@@ -33,8 +32,8 @@ function joinUnder(root: string, ...segments: string[]): string | null {
|
|
|
33
32
|
|
|
34
33
|
/** Single source of truth for URL → cache path. Used identically by the
|
|
35
34
|
* reader (cache lookup) and writer (install-time persistence). For any
|
|
36
|
-
* given import ref —
|
|
37
|
-
*
|
|
35
|
+
* given import ref — an HTTP(S) URL or an `oci://` ref — both sides land on
|
|
36
|
+
* the same file: the owning transport supplies
|
|
38
37
|
* the coordinates and the analyzer's `manifestCacheKey` renders them, the same
|
|
39
38
|
* grammar the hub's static manifest bucket and the editor's read path use.
|
|
40
39
|
*
|
|
@@ -80,8 +79,8 @@ export function legacyManifestsDirFallback(
|
|
|
80
79
|
|
|
81
80
|
/**
|
|
82
81
|
* Reads previously-cached manifest YAMLs from the resolved manifest cache. Sits
|
|
83
|
-
* ahead of `
|
|
84
|
-
*
|
|
82
|
+
* ahead of `HttpSource` in the source chain — a hit makes boot hermetic, a miss
|
|
83
|
+
* falls through to the network source unchanged.
|
|
85
84
|
*
|
|
86
85
|
* Populated by `writeManifestCache` at install time.
|
|
87
86
|
*
|
|
@@ -90,7 +89,7 @@ export function legacyManifestsDirFallback(
|
|
|
90
89
|
* costs only CPU when it goes cold; this one costs network, so without the
|
|
91
90
|
* fallback the move to a workspace-anchored root would stop a hermetic setup from
|
|
92
91
|
* booting — its `telo install` output stranded at the old path, with the failure
|
|
93
|
-
* surfacing as a
|
|
92
|
+
* surfacing as a network fetch on a machine that has no route out. Read-only
|
|
94
93
|
* and one directory deep: writes always go to the current root, so the old copy
|
|
95
94
|
* ages out rather than being maintained.
|
|
96
95
|
*/
|
|
@@ -99,17 +98,13 @@ export class LocalManifestCacheSource implements ManifestSource {
|
|
|
99
98
|
private readonly legacyRoot: string | null;
|
|
100
99
|
private readonly transports: TransportRegistry;
|
|
101
100
|
|
|
102
|
-
constructor(
|
|
103
|
-
entryDir: string,
|
|
104
|
-
registryUrl: string = DEFAULT_REGISTRY_URL,
|
|
105
|
-
manifestsDir?: string,
|
|
106
|
-
) {
|
|
101
|
+
constructor(entryDir: string, manifestsDir?: string) {
|
|
107
102
|
// `manifestsDir` is the resolved manifest-cache directory threaded from a
|
|
108
103
|
// single `resolveCacheRoot` (honours `TELO_CACHE_DIR`); when absent we fall
|
|
109
104
|
// back to the entry-anchored default so library/test callers are unchanged.
|
|
110
105
|
this.cacheRoot = manifestsDir ?? legacyManifestsDir(entryDir);
|
|
111
106
|
this.legacyRoot = legacyManifestsDirFallback(entryDir, this.cacheRoot);
|
|
112
|
-
this.transports = defaultTransportRegistry(
|
|
107
|
+
this.transports = defaultTransportRegistry();
|
|
113
108
|
}
|
|
114
109
|
|
|
115
110
|
supports(url: string): boolean {
|
|
@@ -176,11 +171,10 @@ export class LocalManifestCacheSource implements ManifestSource {
|
|
|
176
171
|
export function cachePathForCanonical(
|
|
177
172
|
canonicalSource: string,
|
|
178
173
|
entryDir: string,
|
|
179
|
-
registryUrl: string | undefined = DEFAULT_REGISTRY_URL,
|
|
180
174
|
manifestsDir?: string,
|
|
181
175
|
): string | null {
|
|
182
176
|
const cacheRoot = manifestsDir ?? path.join(entryDir, CACHE_SUBDIR);
|
|
183
|
-
return cachePathForUrl(canonicalSource, cacheRoot, defaultTransportRegistry(
|
|
177
|
+
return cachePathForUrl(canonicalSource, cacheRoot, defaultTransportRegistry());
|
|
184
178
|
}
|
|
185
179
|
|
|
186
180
|
/**
|
|
@@ -198,7 +192,6 @@ export function cachePathForCanonical(
|
|
|
198
192
|
export async function writeManifestCache(
|
|
199
193
|
graph: LoadedGraph,
|
|
200
194
|
entryDir: string,
|
|
201
|
-
registryUrl: string = DEFAULT_REGISTRY_URL,
|
|
202
195
|
manifestsDir?: string,
|
|
203
196
|
): Promise<string[]> {
|
|
204
197
|
const written: string[] = [];
|
|
@@ -210,7 +203,7 @@ export async function writeManifestCache(
|
|
|
210
203
|
if (seen.has(file.source)) continue;
|
|
211
204
|
seen.add(file.source);
|
|
212
205
|
|
|
213
|
-
const target = cachePathForCanonical(file.source, entryDir,
|
|
206
|
+
const target = cachePathForCanonical(file.source, entryDir, manifestsDir);
|
|
214
207
|
if (!target) continue;
|
|
215
208
|
|
|
216
209
|
await fs.mkdir(path.dirname(target), { recursive: true });
|
package/src/runtime-seam.ts
CHANGED
|
@@ -167,7 +167,6 @@ export class KernelRuntimeSeam implements RuntimeSeam {
|
|
|
167
167
|
stdout: stdout.writable,
|
|
168
168
|
stderr: stderr.writable,
|
|
169
169
|
sources: [...this.kernel.injectedSources],
|
|
170
|
-
registryUrl: this.kernel.registryUrl,
|
|
171
170
|
});
|
|
172
171
|
|
|
173
172
|
// A child that fails to load is not an exception on this side: the caller
|
|
@@ -223,7 +222,7 @@ export class KernelRuntimeSeam implements RuntimeSeam {
|
|
|
223
222
|
// rather than the kernel's own loader because a checked manifest is often
|
|
224
223
|
// deliberately broken and has no business entering the running kernel's
|
|
225
224
|
// parse cache.
|
|
226
|
-
const loader = new Loader(defaultTransportRegistry(
|
|
225
|
+
const loader = new Loader(defaultTransportRegistry().sources(), {
|
|
227
226
|
celHandlers: nodeCelHandlers,
|
|
228
227
|
});
|
|
229
228
|
for (const injected of this.kernel.injectedSources) {
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_MANIFEST_FILENAME,
|
|
3
|
+
HttpSource,
|
|
4
|
+
sha256Base64Url,
|
|
5
|
+
splitIntegrity,
|
|
6
|
+
type ArtifactLayer,
|
|
7
|
+
type ManifestCacheCoords,
|
|
8
|
+
type ManifestSource,
|
|
9
|
+
} from "@telorun/analyzer";
|
|
10
|
+
import { fetchOrThrow } from "@telorun/sdk";
|
|
11
|
+
import { createHash } from "crypto";
|
|
12
|
+
|
|
13
|
+
import type { PayloadFile } from "../bundle/files-integrity.js";
|
|
14
|
+
import { assertPublicEgress } from "./egress-guard.js";
|
|
15
|
+
import type {
|
|
16
|
+
PayloadLayer,
|
|
17
|
+
PublishBundle,
|
|
18
|
+
PublishOptions,
|
|
19
|
+
PublishResult,
|
|
20
|
+
Transport,
|
|
21
|
+
} from "./transport.js";
|
|
22
|
+
|
|
23
|
+
const QUERY_HASH_LENGTH = 12;
|
|
24
|
+
|
|
25
|
+
/** Mirror `HttpSource.read`'s `fetchUrl` derivation: when the URL does not
|
|
26
|
+
* already point at a YAML file, append `/telo.yaml`, so a raw import URL and
|
|
27
|
+
* the canonical source it resolves to map to the same cache path. */
|
|
28
|
+
function normalizePathname(rawUrl: string, parsed: URL): string {
|
|
29
|
+
let pathname = parsed.pathname;
|
|
30
|
+
if (!rawUrl.includes(".yaml")) {
|
|
31
|
+
pathname = pathname.endsWith("/")
|
|
32
|
+
? `${pathname}${DEFAULT_MANIFEST_FILENAME}`
|
|
33
|
+
: `${pathname}/${DEFAULT_MANIFEST_FILENAME}`;
|
|
34
|
+
}
|
|
35
|
+
return pathname;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Short hash of `search + hash` so two URLs that differ only in query /
|
|
39
|
+
* fragment do not collide at the same cache path. */
|
|
40
|
+
function disambiguatePath(pathname: string, search: string, hash: string): string {
|
|
41
|
+
if (!search && !hash) return pathname;
|
|
42
|
+
const digest = createHash("sha256")
|
|
43
|
+
.update(search + hash)
|
|
44
|
+
.digest("hex")
|
|
45
|
+
.slice(0, QUERY_HASH_LENGTH);
|
|
46
|
+
const dotIdx = pathname.lastIndexOf(".");
|
|
47
|
+
const slashIdx = pathname.lastIndexOf("/");
|
|
48
|
+
const ext = dotIdx > slashIdx ? pathname.slice(dotIdx) : "";
|
|
49
|
+
const base = pathname.slice(0, pathname.length - ext.length);
|
|
50
|
+
return `${base}.${digest}${ext}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The transport for direct `https://…` (and `http://`) module URLs. Its
|
|
54
|
+
* resolution `source` composes the browser-safe `HttpSource` from `analyzer`;
|
|
55
|
+
* the Node-only management methods live here. This is the fallback transport
|
|
56
|
+
* for any ref that carries no owning scheme, so `oci://` (or a future `s3://`)
|
|
57
|
+
* never falls through to it — those refs are claimed by their own transport's
|
|
58
|
+
* `supports()`.
|
|
59
|
+
*
|
|
60
|
+
* A URL addresses exactly one file, so it names no enumerable version: this
|
|
61
|
+
* transport publishes nothing, lists no versions, and has no `@version` segment
|
|
62
|
+
* to bump. What it does own is reading, hashing and caching those bytes. */
|
|
63
|
+
export class HttpTransport implements Transport {
|
|
64
|
+
private readonly httpSource: HttpSource;
|
|
65
|
+
readonly source: ManifestSource;
|
|
66
|
+
|
|
67
|
+
constructor() {
|
|
68
|
+
this.httpSource = new HttpSource();
|
|
69
|
+
this.source = {
|
|
70
|
+
supports: (url) => this.supports(url),
|
|
71
|
+
read: async (url) => {
|
|
72
|
+
// The browser-safe source does the fetch; the Node-side egress policy
|
|
73
|
+
// is enforced here, on the host the read will actually hit.
|
|
74
|
+
await assertPublicEgress(url);
|
|
75
|
+
return this.httpSource.read(url);
|
|
76
|
+
},
|
|
77
|
+
resolveRelative: (base, relative) => this.httpSource.resolveRelative(base, relative),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
supports(ref: string): boolean {
|
|
82
|
+
const { base } = splitIntegrity(ref);
|
|
83
|
+
return base.startsWith("http://") || base.startsWith("https://");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
cacheCoords(ref: string): ManifestCacheCoords | null {
|
|
87
|
+
const url = splitIntegrity(ref).base;
|
|
88
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) return null;
|
|
89
|
+
|
|
90
|
+
let parsed: URL;
|
|
91
|
+
try {
|
|
92
|
+
parsed = new URL(url);
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const pathname = normalizePathname(url, parsed);
|
|
97
|
+
|
|
98
|
+
// `url` subtree, query-hash suffix on collision. No version segment: a URL
|
|
99
|
+
// addresses exactly one file, and the version it declares lives inside
|
|
100
|
+
// bytes the cache maps paths without.
|
|
101
|
+
const cleanPath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
102
|
+
const segments = disambiguatePath(cleanPath, parsed.search, parsed.hash).split("/");
|
|
103
|
+
const file = segments.pop();
|
|
104
|
+
if (!file) return null;
|
|
105
|
+
return { transport: "url", host: parsed.host, path: segments.join("/"), file };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async listVersions(): Promise<string[] | null> {
|
|
109
|
+
// A direct `https://` URL has no version-list endpoint.
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
refVersion(): string | null {
|
|
114
|
+
// A direct `https://` URL has no version segment to bump.
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
withVersion(ref: string): string {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`cannot set a version on '${ref}': a URL addresses one file and carries no version segment.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Mirrors the source's fetch-URL derivation: the URL points at (or contains)
|
|
125
|
+
* the YAML file. `null` when this transport does not own the ref's shape. */
|
|
126
|
+
private manifestUrl(ref: string): string | null {
|
|
127
|
+
const { base } = splitIntegrity(ref);
|
|
128
|
+
if (!base.startsWith("http://") && !base.startsWith("https://")) return null;
|
|
129
|
+
return base.includes(".yaml") ? base : `${base}/${DEFAULT_MANIFEST_FILENAME}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async digest(ref: string): Promise<string | null> {
|
|
133
|
+
// The digest is Telo's canonical hash over the `telo.yaml` bytes — the same
|
|
134
|
+
// value `manifestHash` returns, but absent-is-null rather than a throw.
|
|
135
|
+
const fetchUrl = this.manifestUrl(ref);
|
|
136
|
+
if (!fetchUrl) return null;
|
|
137
|
+
await assertPublicEgress(fetchUrl);
|
|
138
|
+
const res = await fetchOrThrow(fetchUrl, undefined, { operation: "Module manifest read" });
|
|
139
|
+
if (res.status === 404) return null;
|
|
140
|
+
if (!res.ok) {
|
|
141
|
+
throw new Error(`${fetchUrl} returned ${res.status} ${res.statusText}`);
|
|
142
|
+
}
|
|
143
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
144
|
+
return `sha256-${await sha256Base64Url(bytes)}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Hashes the **raw response bytes**, which is exactly what `verifiedFetch`
|
|
148
|
+
* checks an inline `#sha256-…` pin against on the read path. */
|
|
149
|
+
async manifestHash(ref: string): Promise<string> {
|
|
150
|
+
const fetchUrl = this.manifestUrl(ref);
|
|
151
|
+
if (!fetchUrl) {
|
|
152
|
+
throw new Error(`cannot hash non-remote import '${ref}'`);
|
|
153
|
+
}
|
|
154
|
+
await assertPublicEgress(fetchUrl);
|
|
155
|
+
const res = await fetchOrThrow(fetchUrl, undefined, { operation: "Module manifest hash" });
|
|
156
|
+
if (!res.ok) {
|
|
157
|
+
throw new Error(`fetch ${fetchUrl}: ${res.status} ${res.statusText}`);
|
|
158
|
+
}
|
|
159
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
160
|
+
return `sha256-${await sha256Base64Url(bytes)}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Layered artifacts are an OCI concept — a module reached over a plain URL is
|
|
164
|
+
* manifest-only, and its controllers come from npm. */
|
|
165
|
+
async fetchLayer(ref: string, blobDigest: string): Promise<PayloadFile[]> {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Cannot fetch layer ${blobDigest} of ${ref}: a plain URL serves the manifest only. ` +
|
|
168
|
+
`A module with a bundled payload is published as an OCI artifact (oci://host/repo).`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async layerIndex(layers: readonly PayloadLayer[]): Promise<ArtifactLayer[]> {
|
|
173
|
+
// Same boundary `fetchLayer` and `publish` draw: a plain URL serves the
|
|
174
|
+
// manifest only, so it frames no layer and can name no blob. An empty set is
|
|
175
|
+
// not a payload, so it answers rather than throws — a manifest-only module
|
|
176
|
+
// builds its payload through this transport during analysis.
|
|
177
|
+
if (layers.every((layer) => layer.files.length === 0)) return [];
|
|
178
|
+
throw new Error(
|
|
179
|
+
"A plain URL serves the manifest only, so it cannot index payload layers. " +
|
|
180
|
+
"A module with a bundled payload is published as an OCI artifact (oci://host/repo).",
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async publish(
|
|
185
|
+
destination: string,
|
|
186
|
+
bundle: PublishBundle,
|
|
187
|
+
opts: PublishOptions = {},
|
|
188
|
+
): Promise<PublishResult> {
|
|
189
|
+
// `telo publish` rejects a non-OCI destination up front, so this is only a
|
|
190
|
+
// guard for a direct programmatic call.
|
|
191
|
+
throw new Error(
|
|
192
|
+
"Publishing over HTTP has been removed. Publish to an OCI registry " +
|
|
193
|
+
"(oci://host/repo) instead.",
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
canonicalizeSiblingRef(destination: string, relativeSource: string, version: string): string {
|
|
198
|
+
// Canonicalizing a sibling is publish-path work, and this transport does not
|
|
199
|
+
// publish — so it refuses here for the same reason `publish` does, rather
|
|
200
|
+
// than computing a ref nothing could ever push. Silence would be worse than
|
|
201
|
+
// useless: an `https://host/` destination has no path to resolve `../lib`
|
|
202
|
+
// beside, so joining anyway yields a ref one segment short with no error.
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Cannot canonicalize the relative import '${relativeSource}' against '${destination}': ` +
|
|
205
|
+
`publishing over plain HTTP has been removed. Publish to an OCI registry ` +
|
|
206
|
+
`(oci://host/repo) instead.`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ManifestCacheCoords, ManifestSource } from "@telorun/analyzer";
|
|
2
2
|
|
|
3
3
|
import type { PayloadFile } from "../bundle/files-integrity.js";
|
|
4
|
+
import { HttpTransport } from "./http-transport.js";
|
|
4
5
|
import { OciTransport } from "./oci/oci-transport.js";
|
|
5
|
-
import { RegistryTransport } from "./registry-transport.js";
|
|
6
6
|
import type {
|
|
7
7
|
PublishBundle,
|
|
8
8
|
PublishOptions,
|
|
@@ -12,9 +12,9 @@ import type {
|
|
|
12
12
|
|
|
13
13
|
/** Dispatches ref-scheme-specific operations to the transport that owns a ref.
|
|
14
14
|
* The loader, cache source, `upgrade`, and `publish` consult this instead of
|
|
15
|
-
* branching on ref shape. `
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* branching on ref shape. `HttpTransport` is always last so it is the fallback
|
|
16
|
+
* for plain `https` refs, and a scheme-owning transport (OCI, later S3) claims
|
|
17
|
+
* its refs via `supports()` before the fallback is reached. */
|
|
18
18
|
export class TransportRegistry {
|
|
19
19
|
constructor(private readonly transports: Transport[]) {}
|
|
20
20
|
|
|
@@ -73,27 +73,20 @@ export class TransportRegistry {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/** The default transport set. Scheme-owning transports come first; the
|
|
76
|
-
* `
|
|
77
|
-
*
|
|
78
|
-
export function defaultTransports(
|
|
79
|
-
return [new OciTransport(), new
|
|
76
|
+
* `HttpTransport` is last, the fallback for plain `https` refs. OCI (and later
|
|
77
|
+
* S3) claim their `oci://` / `s3://` refs before the fallback is reached. */
|
|
78
|
+
export function defaultTransports(): Transport[] {
|
|
79
|
+
return [new OciTransport(), new HttpTransport()];
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
let defaultRegistry: TransportRegistry | undefined;
|
|
83
83
|
|
|
84
|
-
/** A `TransportRegistry` seeded with {@link defaultTransports}, memoized
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const key = registryUrl ?? "";
|
|
93
|
-
let cached = defaultRegistryCache.get(key);
|
|
94
|
-
if (!cached) {
|
|
95
|
-
cached = new TransportRegistry(defaultTransports(registryUrl));
|
|
96
|
-
defaultRegistryCache.set(key, cached);
|
|
97
|
-
}
|
|
98
|
-
return cached;
|
|
84
|
+
/** A `TransportRegistry` seeded with {@link defaultTransports}, memoized. The
|
|
85
|
+
* default transports hold no per-call state, so one shared instance is safe —
|
|
86
|
+
* and avoids re-instantiating the whole set on hot paths like
|
|
87
|
+
* `cachePathForCanonical`. It is also what gives `OciTransport`'s per-instance
|
|
88
|
+
* read-client pool a process-wide lifetime here, so the bearer-token cache
|
|
89
|
+
* survives across operations without the pool having to be global. */
|
|
90
|
+
export function defaultTransportRegistry(): TransportRegistry {
|
|
91
|
+
return (defaultRegistry ??= new TransportRegistry(defaultTransports()));
|
|
99
92
|
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { type ArtifactLayer, type ManifestCacheCoords, type ManifestSource } from "@telorun/analyzer";
|
|
2
|
-
import type { PayloadFile } from "../bundle/files-integrity.js";
|
|
3
|
-
import type { PayloadLayer, PublishBundle, PublishOptions, PublishResult, Transport } from "./transport.js";
|
|
4
|
-
/** The default HTTP transport: bare `namespace/name@version` registry refs and
|
|
5
|
-
* direct `https://…` URLs, resolving against `registry.telo.run` (or a
|
|
6
|
-
* configured registry). Its resolution `source` composes the browser-safe
|
|
7
|
-
* `RegistrySource` / `HttpSource` from `analyzer`; the Node-only management
|
|
8
|
-
* methods live here. This is the fallback transport for any ref that carries
|
|
9
|
-
* no owning scheme, so `oci://` (or a future `s3://`) never falls through to
|
|
10
|
-
* it — those refs are claimed by their own transport's `supports()`. */
|
|
11
|
-
export declare class RegistryTransport implements Transport {
|
|
12
|
-
private readonly registryUrl;
|
|
13
|
-
private readonly registrySource;
|
|
14
|
-
private readonly httpSource;
|
|
15
|
-
readonly source: ManifestSource;
|
|
16
|
-
constructor(registryUrl?: string);
|
|
17
|
-
supports(ref: string): boolean;
|
|
18
|
-
cacheCoords(ref: string): ManifestCacheCoords | null;
|
|
19
|
-
/** Host of the configured registry, or `null` when the URL is unparseable. */
|
|
20
|
-
private registryHost;
|
|
21
|
-
listVersions(ref: string): Promise<string[] | null>;
|
|
22
|
-
refVersion(ref: string): string | null;
|
|
23
|
-
withVersion(ref: string, version: string): string;
|
|
24
|
-
/** Mirrors the sources' fetch-URL derivation: a direct URL points at (or
|
|
25
|
-
* contains) the YAML file; a bare registry ref folds into the registry
|
|
26
|
-
* layout. `null` when this transport does not own the ref's shape. */
|
|
27
|
-
private manifestUrl;
|
|
28
|
-
digest(ref: string): Promise<string | null>;
|
|
29
|
-
/** Hashes the **raw response bytes**, which is exactly what `verifiedFetch`
|
|
30
|
-
* checks an inline `#sha256-…` pin against on the read path. */
|
|
31
|
-
manifestHash(ref: string): Promise<string>;
|
|
32
|
-
/** Layered artifacts are an OCI concept, and the registry origin is read-only
|
|
33
|
-
* — nothing has ever published a payload here, so there is no layer to pull.
|
|
34
|
-
* A module reached over the registry is manifest-only, and its controllers
|
|
35
|
-
* come from npm. */
|
|
36
|
-
fetchLayer(ref: string, blobDigest: string): Promise<PayloadFile[]>;
|
|
37
|
-
layerIndex(layers: readonly PayloadLayer[]): Promise<ArtifactLayer[]>;
|
|
38
|
-
publish(destination: string, bundle: PublishBundle, opts?: PublishOptions): Promise<PublishResult>;
|
|
39
|
-
canonicalizeSiblingRef(destination: string, relativeSource: string, version: string): string;
|
|
40
|
-
}
|
|
41
|
-
//# sourceMappingURL=registry-transport.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registry-transport.d.ts","sourceRoot":"","sources":["../../src/transports/registry-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAEhE,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,SAAS,EACV,MAAM,gBAAgB,CAAC;AAkDxB;;;;;;yEAMyE;AACzE,qBAAa,iBAAkB,YAAW,SAAS;IAKrC,OAAO,CAAC,QAAQ,CAAC,WAAW;IAJxC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;gBAEH,WAAW,GAAE,MAA6B;IAiBvE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAK9B,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI;IAwEpD,8EAA8E;IAC9E,OAAO,CAAC,YAAY;IAQd,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC;IAoBzD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAMtC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAIjD;;2EAEuE;IACvE,OAAO,CAAC,WAAW;IAYb,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAkBjD;qEACiE;IAC3D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBhD;;;yBAGqB;IACf,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAOnE,UAAU,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAYrE,OAAO,CACX,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,aAAa,EACrB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,aAAa,CAAC;IAWzB,sBAAsB,CACpB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,GACd,MAAM;CAwBV"}
|