@telorun/http-server 0.14.0 → 0.15.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 +22 -0
- package/README.md +1 -0
- package/dist/http-api-controller.d.ts +4 -0
- package/dist/http-api-controller.js +24 -1
- package/package.json +2 -2
- package/src/http-api-controller.ts +21 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @telorun/http-server
|
|
2
2
|
|
|
3
|
+
## 0.15.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a9ac4ba: Add optional `operationId`, `summary`, `description`, and `tags` fields to `Http.Api` routes. They are passed through to the underlying framework and rendered into the generated OpenAPI document.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- @telorun/http-dispatch@0.4.1
|
|
12
|
+
|
|
13
|
+
## 0.14.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- dded615: Templated definitions can now produce a mountable HTTP surface, and their dispatch targets are created once instead of per call.
|
|
18
|
+
|
|
19
|
+
- **`mount:` template dispatch** — a `Telo.Definition` with `capability: Telo.Mount` may declare `mount: <child>` (sibling to `invoke:` / `run:` / `provide:`) naming a `resources:` entry that is itself a `Telo.Mount` (e.g. an `Http.Api`). The template instance's `register()` delegates to that persistent child, so a library can ship a self-contained, declarative HTTP resource. The analyzer validates the new field (`MOUNT_ON_NON_MOUNT`, `MOUNT_DISPATCHER_CONFLICT`, `MOUNT_TARGET_UNKNOWN`, `MOUNT_TARGET_NOT_MOUNTABLE`).
|
|
20
|
+
- **Persistent dispatch targets** — the template controller no longer re-creates its `invoke:` / `run:` / `provide:` target on every call (`withEphemeral` is removed). Every `resources:` entry is created once at `init()` and reused; per-call data flows exclusively through the top-level `inputs:` sibling. A resource body may reference only `self`; `${{ inputs.* }}` inside a target body is no longer supported (move it to the top-level `inputs:`).
|
|
21
|
+
- **Library-scoped child resolution** — a template's `resources:` are spawned in a child context rooted on the _defining_ library's module context (new `EvaluationContext.spawnChildContext()`), so their internal kind aliases and `!ref`s resolve against the library's own imports rather than the consumer's.
|
|
22
|
+
- **http-server** — a route declared at `/` now sits at the mount root (`/todos` + `/` → `/todos`) instead of a trailing-slash variant Fastify treats as a distinct, unmatched URL, so collection-style mounts respond at the mount path itself.
|
|
23
|
+
- @telorun/http-dispatch@0.4.1
|
|
24
|
+
|
|
3
25
|
## 0.14.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ Language- and framework-agnostic HTTP server for Telo. Declarative routes, schem
|
|
|
8
8
|
- **OpenAPI-style paths** — `/users/{id}` syntax everywhere; the adapter translates to its native router.
|
|
9
9
|
- **Schema-driven validation** — `request.schema` (`body`, `query`, `params`, `headers`) yields a standardized HTTP 400 with `details[]` on failure.
|
|
10
10
|
- **Typed returns and catches** — render successful values and structured `InvokeError`s into status + headers + per-MIME bodies via CEL.
|
|
11
|
+
- **OpenAPI operation metadata** — a route may declare `operationId`, `summary`, `description`, and `tags`; they are rendered into the generated OpenAPI document.
|
|
11
12
|
- **Composable mounts** — attach `Telo.Mount` resources (HTTP APIs, MCP endpoints, custom mounts) under any path prefix.
|
|
12
13
|
- **Serve a frontend** — `Http.Static` serves a directory of assets (a built SPA, plain HTML) so one application delivers both its API and its UI.
|
|
13
14
|
- **CORS and content-type parsers** — first-class manifest fields; no controller code needed.
|
|
@@ -38,6 +38,10 @@ declare const HttpApiManifest: import("@sinclair/typebox").TObject<{
|
|
|
38
38
|
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
|
|
39
39
|
}>>>;
|
|
40
40
|
}>>>;
|
|
41
|
+
operationId: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
42
|
+
summary: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
43
|
+
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
44
|
+
tags: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
41
45
|
}>>;
|
|
42
46
|
}>;
|
|
43
47
|
type HttpApiManifest = Static<typeof HttpApiManifest>;
|
|
@@ -17,6 +17,10 @@ const HttpApiRouteManifest = Type.Object({
|
|
|
17
17
|
inputs: Type.Optional(Type.Record(Type.String(), Type.Any())),
|
|
18
18
|
returns: Type.Array(ReturnEntry),
|
|
19
19
|
catches: Type.Optional(Type.Array(CatchEntry)),
|
|
20
|
+
operationId: Type.Optional(Type.String()),
|
|
21
|
+
summary: Type.Optional(Type.String()),
|
|
22
|
+
description: Type.Optional(Type.String()),
|
|
23
|
+
tags: Type.Optional(Type.Array(Type.String())),
|
|
20
24
|
});
|
|
21
25
|
const HttpApiManifest = Type.Object({
|
|
22
26
|
routes: Type.Array(HttpApiRouteManifest),
|
|
@@ -55,7 +59,7 @@ export class HttpServerApi {
|
|
|
55
59
|
const handlerRef = this.handlerRefs.get(route);
|
|
56
60
|
const handlerKind = handlerRef?.kind ?? "";
|
|
57
61
|
const handlerName = handlerRef?.name ?? "";
|
|
58
|
-
const translatedPath = prefix
|
|
62
|
+
const translatedPath = joinMountPath(prefix, translateOpenApiPath(route.request.path));
|
|
59
63
|
const schema = { response: {} };
|
|
60
64
|
// A stream-marked body is delivered as a raw `Stream<Uint8Array>` (see the
|
|
61
65
|
// server's `contentTypeParsers[].stream`); it is opaque to AJV, so skip
|
|
@@ -69,6 +73,16 @@ export class HttpServerApi {
|
|
|
69
73
|
schema.body = route.request.schema.body;
|
|
70
74
|
if (route.request.schema?.headers)
|
|
71
75
|
schema.headers = route.request.schema.headers;
|
|
76
|
+
// OpenAPI operation metadata — @fastify/swagger reads these off the route
|
|
77
|
+
// schema and renders them into the generated document.
|
|
78
|
+
if (route.operationId)
|
|
79
|
+
schema.operationId = route.operationId;
|
|
80
|
+
if (route.summary)
|
|
81
|
+
schema.summary = route.summary;
|
|
82
|
+
if (route.description)
|
|
83
|
+
schema.description = route.description;
|
|
84
|
+
if (route.tags)
|
|
85
|
+
schema.tags = route.tags;
|
|
72
86
|
// Response schemas: register the FIRST content[mime].schema we find for
|
|
73
87
|
// each status. Multiple MIMEs per status all get the same response shape
|
|
74
88
|
// (Fastify's response schema is per-status, not per-MIME); the per-MIME
|
|
@@ -205,6 +219,15 @@ function normalizeMountPrefix(prefix) {
|
|
|
205
219
|
return "";
|
|
206
220
|
return prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
207
221
|
}
|
|
222
|
+
/** Join a normalized mount prefix with a translated route path. A collection
|
|
223
|
+
* route declared at `/` sits at the mount root itself (`/todos` + `/` → `/todos`),
|
|
224
|
+
* not a trailing-slash variant Fastify would treat as a distinct, unmatched URL.
|
|
225
|
+
* An empty prefix (root mount) keeps `/`. */
|
|
226
|
+
function joinMountPath(prefix, path) {
|
|
227
|
+
if (path === "/")
|
|
228
|
+
return prefix || "/";
|
|
229
|
+
return prefix + path;
|
|
230
|
+
}
|
|
208
231
|
/**
|
|
209
232
|
* Wraps an incoming request's raw body as a `Stream<Uint8Array>`. Requires a
|
|
210
233
|
* stream content-type parser (`contentTypeParsers[].stream`) for the request's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/http-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Telo HTTP Server module - HTTP server and API resource kinds for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -55,7 +55,7 @@
|
|
|
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.38.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@telorun/sdk": "*"
|
|
@@ -40,6 +40,10 @@ const HttpApiRouteManifest = Type.Object({
|
|
|
40
40
|
inputs: Type.Optional(Type.Record(Type.String(), Type.Any())),
|
|
41
41
|
returns: Type.Array(ReturnEntry),
|
|
42
42
|
catches: Type.Optional(Type.Array(CatchEntry)),
|
|
43
|
+
operationId: Type.Optional(Type.String()),
|
|
44
|
+
summary: Type.Optional(Type.String()),
|
|
45
|
+
description: Type.Optional(Type.String()),
|
|
46
|
+
tags: Type.Optional(Type.Array(Type.String())),
|
|
43
47
|
});
|
|
44
48
|
type HttpApiRouteManifest = Static<typeof HttpApiRouteManifest>;
|
|
45
49
|
|
|
@@ -87,7 +91,7 @@ export class HttpServerApi implements ResourceInstance {
|
|
|
87
91
|
const handlerRef = this.handlerRefs.get(route as unknown as object);
|
|
88
92
|
const handlerKind = handlerRef?.kind ?? "";
|
|
89
93
|
const handlerName = handlerRef?.name ?? "";
|
|
90
|
-
const translatedPath = prefix
|
|
94
|
+
const translatedPath = joinMountPath(prefix, translateOpenApiPath(route.request.path));
|
|
91
95
|
|
|
92
96
|
const schema: any = { response: {} };
|
|
93
97
|
|
|
@@ -101,6 +105,13 @@ export class HttpServerApi implements ResourceInstance {
|
|
|
101
105
|
if (route.request.schema?.body && !streamBody) schema.body = route.request.schema.body;
|
|
102
106
|
if (route.request.schema?.headers) schema.headers = route.request.schema.headers;
|
|
103
107
|
|
|
108
|
+
// OpenAPI operation metadata — @fastify/swagger reads these off the route
|
|
109
|
+
// schema and renders them into the generated document.
|
|
110
|
+
if (route.operationId) schema.operationId = route.operationId;
|
|
111
|
+
if (route.summary) schema.summary = route.summary;
|
|
112
|
+
if (route.description) schema.description = route.description;
|
|
113
|
+
if (route.tags) schema.tags = route.tags;
|
|
114
|
+
|
|
104
115
|
// Response schemas: register the FIRST content[mime].schema we find for
|
|
105
116
|
// each status. Multiple MIMEs per status all get the same response shape
|
|
106
117
|
// (Fastify's response schema is per-status, not per-MIME); the per-MIME
|
|
@@ -269,6 +280,15 @@ function normalizeMountPrefix(prefix: string): string {
|
|
|
269
280
|
return prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
270
281
|
}
|
|
271
282
|
|
|
283
|
+
/** Join a normalized mount prefix with a translated route path. A collection
|
|
284
|
+
* route declared at `/` sits at the mount root itself (`/todos` + `/` → `/todos`),
|
|
285
|
+
* not a trailing-slash variant Fastify would treat as a distinct, unmatched URL.
|
|
286
|
+
* An empty prefix (root mount) keeps `/`. */
|
|
287
|
+
function joinMountPath(prefix: string, path: string): string {
|
|
288
|
+
if (path === "/") return prefix || "/";
|
|
289
|
+
return prefix + path;
|
|
290
|
+
}
|
|
291
|
+
|
|
272
292
|
/**
|
|
273
293
|
* Wraps an incoming request's raw body as a `Stream<Uint8Array>`. Requires a
|
|
274
294
|
* stream content-type parser (`contentTypeParsers[].stream`) for the request's
|