@telorun/http-server 0.14.1 → 0.15.1

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @telorun/http-server
2
2
 
3
+ ## 0.15.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 06c675b: Fix CORS preflight returning 404. `Http.Server` forwarded every `cors` option to `@fastify/cors`, including the ones the manifest left unset — spreading `preflight: undefined` (and friends) clobbered the plugin's own default `preflight: true`, so its `OPTIONS *` handler `callNotFound()`'d and the preflight came back 404. Browsers reject that ("Response to preflight request … does not have HTTP ok status") and block every cross-origin `POST`. Now only the fields actually set on `cors` are passed through, so unset options keep the plugin's defaults and preflight replies 204.
8
+
9
+ ## 0.15.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 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.
14
+
15
+ ### Patch Changes
16
+
17
+ - @telorun/http-dispatch@0.4.1
18
+
3
19
  ## 0.14.1
4
20
 
5
21
  ### Patch 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),
@@ -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
@@ -73,20 +73,31 @@ class HttpServer {
73
73
  }
74
74
  }
75
75
  if (this.resource.cors) {
76
- await this.app.register(cors, {
77
- origin: this.resource.cors.origin,
78
- methods: this.resource.cors.methods,
79
- allowedHeaders: this.resource.cors.allowedHeaders,
80
- exposedHeaders: this.resource.cors.exposedHeaders,
81
- credentials: this.resource.cors.credentials,
82
- maxAge: this.resource.cors.maxAge,
83
- cacheControl: this.resource.cors.cacheControl,
84
- preflightContinue: this.resource.cors.preflightContinue,
85
- optionsSuccessStatus: this.resource.cors.optionsSuccessStatus,
86
- preflight: this.resource.cors.preflight,
87
- strictPreflight: this.resource.cors.strictPreflight,
88
- hideOptionsRoute: this.resource.cors.hideOptionsRoute,
89
- });
76
+ // Only forward the fields the manifest actually set. Spreading `undefined`
77
+ // for an unset option overrides @fastify/cors's own defaults with
78
+ // `undefined` — notably `preflight: undefined` disables the preflight 204
79
+ // reply (its `OPTIONS *` handler then `callNotFound()`s → 404), which a
80
+ // browser reports as "preflight … does not have HTTP ok status".
81
+ const cfg = this.resource.cors;
82
+ const corsOptions = {};
83
+ for (const key of [
84
+ "origin",
85
+ "methods",
86
+ "allowedHeaders",
87
+ "exposedHeaders",
88
+ "credentials",
89
+ "maxAge",
90
+ "cacheControl",
91
+ "preflightContinue",
92
+ "optionsSuccessStatus",
93
+ "preflight",
94
+ "strictPreflight",
95
+ "hideOptionsRoute",
96
+ ]) {
97
+ if (cfg[key] !== undefined)
98
+ corsOptions[key] = cfg[key];
99
+ }
100
+ await this.app.register(cors, corsOptions);
90
101
  }
91
102
  // Register custom error handler for validation errors
92
103
  this.app.setErrorHandler((error, request, reply) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/http-server",
3
- "version": "0.14.1",
3
+ "version": "0.15.1",
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.36.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
 
@@ -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
@@ -155,20 +155,30 @@ class HttpServer implements ResourceInstance {
155
155
  }
156
156
 
157
157
  if (this.resource.cors) {
158
- await this.app.register(cors, {
159
- origin: this.resource.cors.origin,
160
- methods: this.resource.cors.methods,
161
- allowedHeaders: this.resource.cors.allowedHeaders,
162
- exposedHeaders: this.resource.cors.exposedHeaders,
163
- credentials: this.resource.cors.credentials,
164
- maxAge: this.resource.cors.maxAge,
165
- cacheControl: this.resource.cors.cacheControl,
166
- preflightContinue: this.resource.cors.preflightContinue,
167
- optionsSuccessStatus: this.resource.cors.optionsSuccessStatus,
168
- preflight: this.resource.cors.preflight,
169
- strictPreflight: this.resource.cors.strictPreflight,
170
- hideOptionsRoute: this.resource.cors.hideOptionsRoute,
171
- });
158
+ // Only forward the fields the manifest actually set. Spreading `undefined`
159
+ // for an unset option overrides @fastify/cors's own defaults with
160
+ // `undefined` — notably `preflight: undefined` disables the preflight 204
161
+ // reply (its `OPTIONS *` handler then `callNotFound()`s → 404), which a
162
+ // browser reports as "preflight … does not have HTTP ok status".
163
+ const cfg = this.resource.cors;
164
+ const corsOptions: Record<string, unknown> = {};
165
+ for (const key of [
166
+ "origin",
167
+ "methods",
168
+ "allowedHeaders",
169
+ "exposedHeaders",
170
+ "credentials",
171
+ "maxAge",
172
+ "cacheControl",
173
+ "preflightContinue",
174
+ "optionsSuccessStatus",
175
+ "preflight",
176
+ "strictPreflight",
177
+ "hideOptionsRoute",
178
+ ] as const) {
179
+ if (cfg[key] !== undefined) corsOptions[key] = cfg[key];
180
+ }
181
+ await this.app.register(cors, corsOptions);
172
182
  }
173
183
 
174
184
  // Register custom error handler for validation errors