@telorun/http-server 0.17.0 → 0.18.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/CHANGELOG.md +27 -0
- package/README.md +4 -4
- package/dist/http-server-controller.d.ts +1 -1
- package/dist/http-server-controller.js +1 -1
- package/dist/http-static-controller.js +28 -12
- package/package.json +3 -3
- package/src/http-server-controller.ts +3 -3
- package/src/http-static-controller.ts +30 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @telorun/http-server
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d23de89: Layered module artifacts: a published module is now one artifact of several layers instead of one tarball, and each layer is materialized only when something needs it.
|
|
8
|
+
|
|
9
|
+
`telo.yaml` gets its own layer, so reading a manifest no longer downloads (and discards) the whole payload. The rest of `files:` is partitioned into one layer per bundled-controller selector — `format` plus optional `os`/`arch`/`libc` PURL qualifiers — plus an `assets` layer for what the new optional `assets:` list claims and a `common` layer for everything else. A Node kernel never fetches a `napi` layer, a `linux/amd64` host never fetches the `darwin/arm64` binary, and an app that imports a module for its API alone never fetches its frontend.
|
|
10
|
+
|
|
11
|
+
This fixes a cold-start failure: bundled controllers used to resolve against an `oci://` base URI that was read as a filesystem path, because the payload was written to disk by a CLI hook running _after_ `kernel.load()`. The first run of any OCI-imported module with bundled controllers failed and the second succeeded. Controller layers now materialize at resolve time through a module-scoped `ModuleArtifact`, built during load where the pinned import ref and the verified manifest are both available — so verification stays anchored to the importer's `#sha256-` pin rather than to whatever is in the cache.
|
|
12
|
+
|
|
13
|
+
`ctx.resolveModuleFile(relative)` is the new, URI-returning way to reach a file that ships with a module; it materializes the asset layer on first use. `Http.Static`, `mcp-client`, `assert`'s manifest loader and `Test.Suite` all use it, which also fixes a silent bug where a non-`file://` module resolved a relative root against the process working directory and served the wrong directory instead of failing.
|
|
14
|
+
|
|
15
|
+
Also: `telo install --platform os/arch[/libc]` pre-fetches layers for a platform other than the build machine's, the layer index and selector grammar are specified normatively in `kernel/specs/module-artifact.md`, and the cross-process cache lock is shared between the npm loader and layer materialization instead of duplicated.
|
|
16
|
+
|
|
17
|
+
Modules published before layers keep resolving: the manifest read path still accepts a single-blob artifact, which contains `telo.yaml` — so nothing that ships no payload needs anything done to it, and npm-backed modules are entirely unaffected. What such an artifact cannot supply is a layer index, so a module that _does_ ship a payload resolves its manifest and then fails at the controller with an actionable "republish" error. That is the six modules shipping `files:` — `oauth-client`, `scheduler`, `kv-store-memory`, `kv-store-redis`, `kv-store-sql`, `idempotency` — which must be republished, with consumers bumping to the new versions.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- @telorun/http-dispatch@0.4.2
|
|
22
|
+
|
|
23
|
+
## 0.17.1
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [f3b044d]
|
|
28
|
+
- @telorun/http-dispatch@0.4.2
|
|
29
|
+
|
|
3
30
|
## 0.17.0
|
|
4
31
|
|
|
5
32
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -27,8 +27,8 @@ Language- and framework-agnostic HTTP server for Telo. Declarative routes, schem
|
|
|
27
27
|
kind: Telo.Application
|
|
28
28
|
metadata: { name: hello-http, version: 1.0.0 }
|
|
29
29
|
imports:
|
|
30
|
-
Http: std/http-server
|
|
31
|
-
JS: std/javascript
|
|
30
|
+
Http: std/http-server@0.19.1
|
|
31
|
+
JS: std/javascript@0.7.0
|
|
32
32
|
targets: [ !ref Server ]
|
|
33
33
|
---
|
|
34
34
|
kind: Http.Server
|
|
@@ -56,7 +56,7 @@ routes:
|
|
|
56
56
|
description: Name to greet.
|
|
57
57
|
examples: [ "Ada" ]
|
|
58
58
|
inputs:
|
|
59
|
-
name: "
|
|
59
|
+
name: !cel "request.params.name"
|
|
60
60
|
handler: !ref Greet
|
|
61
61
|
returns:
|
|
62
62
|
- status: 200
|
|
@@ -69,7 +69,7 @@ routes:
|
|
|
69
69
|
type: string
|
|
70
70
|
description: The greeting.
|
|
71
71
|
examples: [ "Hello, Ada!" ]
|
|
72
|
-
body: { message: "
|
|
72
|
+
body: { message: !cel "result.message" }
|
|
73
73
|
---
|
|
74
74
|
kind: JS.Script
|
|
75
75
|
metadata: { name: Greet }
|
|
@@ -2,7 +2,7 @@ import { CatchEntry, ReturnEntry } from "@telorun/http-dispatch";
|
|
|
2
2
|
import { type Invocable, type KindRef, type ResourceContext, type ResourceInstance, type RuntimeResource } from "@telorun/sdk";
|
|
3
3
|
import { FastifyInstance } from "fastify";
|
|
4
4
|
/** A mounted Telo.Mount instance (Http.Api, Mcp.HttpEndpoint, …). The kernel injects the
|
|
5
|
-
* live instance into a mount's `mount` slot (x-telo-ref
|
|
5
|
+
* live instance into a mount's `mount` slot (x-telo-ref `Telo.Mount`) — cross-module refs
|
|
6
6
|
* resolve to an imported library's exported mount — and every mountable exposes register(). */
|
|
7
7
|
interface Mountable {
|
|
8
8
|
register(app: FastifyInstance, prefix: string): void | Promise<void>;
|
|
@@ -189,7 +189,7 @@ class HttpServer {
|
|
|
189
189
|
for (const mount of mounts) {
|
|
190
190
|
const prefix = mount.path || "";
|
|
191
191
|
// `mount.mount` is the live Telo.Mount instance injected by the kernel at Phase 5
|
|
192
|
-
// (x-telo-ref
|
|
192
|
+
// (x-telo-ref `Telo.Mount`) — a same-module or imported-library mount, uniformly.
|
|
193
193
|
const api = mount.mount;
|
|
194
194
|
if (!api || typeof api.register !== "function") {
|
|
195
195
|
throw new Error(`Failed to mount at "${prefix}": mount target did not resolve to a Telo.Mount instance`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fastifyStatic from "@fastify/static";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import {
|
|
3
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
/** Collapse a mount prefix to a single leading slash with no trailing slash;
|
|
6
6
|
* an empty/`"/"` prefix becomes `"/"`. Unlike Http.Api (which returns `""` and
|
|
@@ -23,8 +23,8 @@ class HttpStatic {
|
|
|
23
23
|
spaFallback;
|
|
24
24
|
maxAge;
|
|
25
25
|
immutable;
|
|
26
|
-
constructor(resource,
|
|
27
|
-
this.root =
|
|
26
|
+
constructor(resource, root) {
|
|
27
|
+
this.root = root;
|
|
28
28
|
this.index = resource.index ?? "index.html";
|
|
29
29
|
this.spaFallback = resource.spaFallback === true;
|
|
30
30
|
this.maxAge = resource.maxAge;
|
|
@@ -76,17 +76,33 @@ class HttpStatic {
|
|
|
76
76
|
}, { prefix: mountPrefix });
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
/**
|
|
80
|
-
*
|
|
81
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the asset root against the declaring module's own directory, so the
|
|
81
|
+
* frontend ships co-located with the app.
|
|
82
|
+
*
|
|
83
|
+
* `ctx.resolveModuleFile` is the only correct way to do this: for a published
|
|
84
|
+
* module the manifest URL is not where its payload lives, and it also materializes
|
|
85
|
+
* the module's asset layer on first access. Deriving the directory from
|
|
86
|
+
* `moduleContext.source` by hand silently fell back to the process working
|
|
87
|
+
* directory for any non-`file://` module — serving the wrong files instead of
|
|
88
|
+
* failing.
|
|
89
|
+
*/
|
|
90
|
+
async function resolveRoot(root, ctx) {
|
|
82
91
|
if (isAbsolute(root))
|
|
83
92
|
return root;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
// A directory reference: the trailing slash keeps URL resolution from treating
|
|
94
|
+
// the last segment as a sibling file.
|
|
95
|
+
// A module whose files cannot be located raises its own actionable error from
|
|
96
|
+
// `resolveModuleFile` (naming republication), so this only guards a scheme that
|
|
97
|
+
// resolved fine but is not servable from disk.
|
|
98
|
+
const uri = await ctx.resolveModuleFile(root.endsWith("/") ? root : `${root}/`);
|
|
99
|
+
if (!uri.startsWith("file://")) {
|
|
100
|
+
throw new Error(`Http.Static root '${root}' resolved to '${uri}'. A static root must be a local ` +
|
|
101
|
+
`directory, and this runtime can only serve files from one.`);
|
|
102
|
+
}
|
|
103
|
+
// Strip the trailing separator the directory form introduced.
|
|
104
|
+
return resolve(fileURLToPath(uri));
|
|
89
105
|
}
|
|
90
106
|
export async function create(resource, ctx) {
|
|
91
|
-
return new HttpStatic(resource, ctx);
|
|
107
|
+
return new HttpStatic(resource, await resolveRoot(resource.root, ctx));
|
|
92
108
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/http-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Telo HTTP Server module - HTTP server and API resource kinds for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -49,13 +49,13 @@
|
|
|
49
49
|
"ajv": "^8.17.1",
|
|
50
50
|
"ajv-formats": "^3.0.1",
|
|
51
51
|
"fastify": "^5.7.2",
|
|
52
|
-
"@telorun/http-dispatch": "0.4.
|
|
52
|
+
"@telorun/http-dispatch": "0.4.2"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^20.0.0",
|
|
56
56
|
"typescript": "^5.0.0",
|
|
57
57
|
"vitest": "^2.1.8",
|
|
58
|
-
"@telorun/sdk": "0.
|
|
58
|
+
"@telorun/sdk": "0.60.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@telorun/sdk": "*"
|
|
@@ -22,7 +22,7 @@ import Fastify, { FastifyInstance } from "fastify";
|
|
|
22
22
|
import { fastifyReplySink } from "./fastify-reply-sink.js";
|
|
23
23
|
|
|
24
24
|
/** A mounted Telo.Mount instance (Http.Api, Mcp.HttpEndpoint, …). The kernel injects the
|
|
25
|
-
* live instance into a mount's `mount` slot (x-telo-ref
|
|
25
|
+
* live instance into a mount's `mount` slot (x-telo-ref `Telo.Mount`) — cross-module refs
|
|
26
26
|
* resolve to an imported library's exported mount — and every mountable exposes register(). */
|
|
27
27
|
interface Mountable {
|
|
28
28
|
register(app: FastifyInstance, prefix: string): void | Promise<void>;
|
|
@@ -59,7 +59,7 @@ type HttpServerResource = RuntimeResource & {
|
|
|
59
59
|
};
|
|
60
60
|
mounts?: Array<{
|
|
61
61
|
path?: string;
|
|
62
|
-
// x-telo-ref
|
|
62
|
+
// x-telo-ref `Telo.Mount`: Phase 5 replaces this slot with the live mounted
|
|
63
63
|
// instance (Http.Api, Mcp.HttpEndpoint, …), local or imported.
|
|
64
64
|
mount?: Mountable;
|
|
65
65
|
}>;
|
|
@@ -270,7 +270,7 @@ class HttpServer implements ResourceInstance {
|
|
|
270
270
|
for (const mount of mounts) {
|
|
271
271
|
const prefix = mount.path || "";
|
|
272
272
|
// `mount.mount` is the live Telo.Mount instance injected by the kernel at Phase 5
|
|
273
|
-
// (x-telo-ref
|
|
273
|
+
// (x-telo-ref `Telo.Mount`) — a same-module or imported-library mount, uniformly.
|
|
274
274
|
const api = mount.mount;
|
|
275
275
|
if (!api || typeof api.register !== "function") {
|
|
276
276
|
throw new Error(
|
|
@@ -2,7 +2,7 @@ import fastifyStatic from "@fastify/static";
|
|
|
2
2
|
import { type ResourceContext, type ResourceInstance, type RuntimeResource } from "@telorun/sdk";
|
|
3
3
|
import { FastifyInstance } from "fastify";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
|
-
import {
|
|
5
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
8
|
type HttpStaticResource = RuntimeResource & {
|
|
@@ -35,8 +35,8 @@ class HttpStatic implements ResourceInstance {
|
|
|
35
35
|
private readonly maxAge?: number;
|
|
36
36
|
private readonly immutable: boolean;
|
|
37
37
|
|
|
38
|
-
constructor(resource: HttpStaticResource,
|
|
39
|
-
this.root =
|
|
38
|
+
constructor(resource: HttpStaticResource, root: string) {
|
|
39
|
+
this.root = root;
|
|
40
40
|
this.index = resource.index ?? "index.html";
|
|
41
41
|
this.spaFallback = resource.spaFallback === true;
|
|
42
42
|
this.maxAge = resource.maxAge;
|
|
@@ -95,19 +95,38 @@ class HttpStatic implements ResourceInstance {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/**
|
|
99
|
-
*
|
|
100
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Resolve the asset root against the declaring module's own directory, so the
|
|
100
|
+
* frontend ships co-located with the app.
|
|
101
|
+
*
|
|
102
|
+
* `ctx.resolveModuleFile` is the only correct way to do this: for a published
|
|
103
|
+
* module the manifest URL is not where its payload lives, and it also materializes
|
|
104
|
+
* the module's asset layer on first access. Deriving the directory from
|
|
105
|
+
* `moduleContext.source` by hand silently fell back to the process working
|
|
106
|
+
* directory for any non-`file://` module — serving the wrong files instead of
|
|
107
|
+
* failing.
|
|
108
|
+
*/
|
|
109
|
+
async function resolveRoot(root: string, ctx: ResourceContext): Promise<string> {
|
|
101
110
|
if (isAbsolute(root)) return root;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
// A directory reference: the trailing slash keeps URL resolution from treating
|
|
112
|
+
// the last segment as a sibling file.
|
|
113
|
+
// A module whose files cannot be located raises its own actionable error from
|
|
114
|
+
// `resolveModuleFile` (naming republication), so this only guards a scheme that
|
|
115
|
+
// resolved fine but is not servable from disk.
|
|
116
|
+
const uri = await ctx.resolveModuleFile(root.endsWith("/") ? root : `${root}/`);
|
|
117
|
+
if (!uri.startsWith("file://")) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Http.Static root '${root}' resolved to '${uri}'. A static root must be a local ` +
|
|
120
|
+
`directory, and this runtime can only serve files from one.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
// Strip the trailing separator the directory form introduced.
|
|
124
|
+
return resolve(fileURLToPath(uri));
|
|
106
125
|
}
|
|
107
126
|
|
|
108
127
|
export async function create(
|
|
109
128
|
resource: HttpStaticResource,
|
|
110
129
|
ctx: ResourceContext,
|
|
111
130
|
): Promise<ResourceInstance> {
|
|
112
|
-
return new HttpStatic(resource, ctx);
|
|
131
|
+
return new HttpStatic(resource, await resolveRoot(resource.root, ctx));
|
|
113
132
|
}
|