@telorun/http-server 0.7.0 → 0.8.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 +42 -0
- package/dist/http-api-controller.js +18 -2
- package/package.json +2 -2
- package/src/http-api-controller.ts +23 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @telorun/http-server
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e3146f3: Fix spurious request cancellation (HTTP 499) for body-bearing requests whose handler awaits before replying. Per-request cancellation was wired to the request stream's `close` event, which fires as normal cleanup once a request body has been fully received — so any `PUT`/`POST` whose handler did async work (e.g. a DB query) before sending a response was cancelled mid-flight and answered with 499. Cancellation now listens on the response socket, which only closes early on a genuine client disconnect; normal completions and synchronous rejects are unaffected.
|
|
8
|
+
|
|
9
|
+
## 0.8.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 5331205: Add cooperative invoke cancellation via an out-of-band `InvokeContext`.
|
|
14
|
+
|
|
15
|
+
Every `invoke(inputs, ctx?)` now receives a second argument carrying a read-only
|
|
16
|
+
cancellation token (`ctx.cancellation`): poll `isCancelled`, subscribe via
|
|
17
|
+
`onCancelled`, bail with `throwIfCancelled`, or hand its `signal` to a Web API.
|
|
18
|
+
The SDK exposes the source/token split (`createCancellationSource`,
|
|
19
|
+
`CancellationSource`/`CancellationToken`), a never-cancellable sentinel, and the
|
|
20
|
+
`isCancellationError` helper. Deadlines are scheduled cancellation
|
|
21
|
+
(`source.cancelAt(epochMs)` / `cancelAfter(ms)`).
|
|
22
|
+
|
|
23
|
+
The kernel mints one cancellation scope per invocation tree (inherited by nested
|
|
24
|
+
invokes via a kernel-internal `AsyncLocalStorage`, always passed to controllers
|
|
25
|
+
as the explicit argument), refuses a not-yet-dispatched invoke whose tree was
|
|
26
|
+
cancelled with `ERR_INVOKE_CANCELLED`, and emits a scoped `InvokeCancelled`
|
|
27
|
+
event. `Kernel.invoke(ref, inputs, opts?)` accepts `{ signal, deadlineAt }`.
|
|
28
|
+
Sources are allocated lazily, so invokes that never touch cancellation pay no
|
|
29
|
+
extra allocation.
|
|
30
|
+
|
|
31
|
+
The boot `targets` run is also cancellable: `Runnable.run(ctx?)` now receives
|
|
32
|
+
the token, `Kernel.cancel(reason?)` cancels the boot scope, and the CLI's
|
|
33
|
+
SIGINT/SIGTERM handler calls it so Ctrl-C cooperatively stops honoring targets
|
|
34
|
+
and in-flight invoke trees (then unblocks graceful exit via `forceIdle`).
|
|
35
|
+
|
|
36
|
+
Honoring leaves: `Ai.Text` / `Ai.TextStream` / `Ai.Agent` forward the token's
|
|
37
|
+
signal into the model (aborting a live LLM stream on cancel); `http-client`
|
|
38
|
+
merges it with its request timeout. Triggers: `http-server` cancels on client
|
|
39
|
+
disconnect and returns 499; `lambda` arms cancellation at the AWS deadline.
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- @telorun/http-dispatch@0.4.1
|
|
44
|
+
|
|
3
45
|
## 0.7.0
|
|
4
46
|
|
|
5
47
|
### Minor Changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { CatchEntry, dispatchCatches, dispatchReturns, ReturnEntry, validateNoContentTypeHeader, validateStreamWhenDoesNotReferenceResult, } from "@telorun/http-dispatch";
|
|
3
|
-
import { InvokeError, isInvokeError, Ref, Stream, } from "@telorun/sdk";
|
|
3
|
+
import { InvokeError, isCancellationError, isInvokeError, Ref, Stream, } from "@telorun/sdk";
|
|
4
4
|
import { fastifyReplySink } from "./fastify-reply-sink.js";
|
|
5
5
|
const HttpApiRouteManifest = Type.Object({
|
|
6
6
|
request: Type.Object({
|
|
@@ -106,13 +106,29 @@ export class HttpServerApi {
|
|
|
106
106
|
inputs: resolvedInputs,
|
|
107
107
|
};
|
|
108
108
|
const sink = fastifyReplySink(reply);
|
|
109
|
+
// Per-request cancellation: abandon downstream work when the client
|
|
110
|
+
// disconnects before the response is sent. Listen on the response
|
|
111
|
+
// socket, not the request stream — the latter's `close` fires as normal
|
|
112
|
+
// cleanup once a request body has been fully received, which would
|
|
113
|
+
// cancel any body-bearing request that awaits (e.g. a DB call) before
|
|
114
|
+
// replying. The response socket only closes early on a real disconnect.
|
|
115
|
+
const cancellation = this.ctx.createCancellationSource();
|
|
116
|
+
reply.raw.on("close", () => {
|
|
117
|
+
if (!reply.sent)
|
|
118
|
+
cancellation.cancel("client-disconnect");
|
|
119
|
+
});
|
|
109
120
|
let result;
|
|
110
121
|
try {
|
|
111
122
|
result = handler
|
|
112
|
-
? await this.ctx.invokeResolved(handlerKind, handlerName, handler, invokeInput)
|
|
123
|
+
? await this.ctx.invokeResolved(handlerKind, handlerName, handler, invokeInput, cancellation.context)
|
|
113
124
|
: undefined;
|
|
114
125
|
}
|
|
115
126
|
catch (err) {
|
|
127
|
+
if (isCancellationError(err)) {
|
|
128
|
+
if (!reply.sent)
|
|
129
|
+
reply.code(499).send();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
116
132
|
if (!isInvokeError(err))
|
|
117
133
|
throw err;
|
|
118
134
|
return dispatchCatches(route.catches, { code: err.code, message: err.message, data: err.data }, requestContext, acceptHeader, this.ctx.moduleContext, this.ctx.validateSchema.bind(this.ctx), sink);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/http-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Telo HTTP Server module - HTTP server and API resource kinds for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@types/node": "^20.0.0",
|
|
50
50
|
"typescript": "^5.0.0",
|
|
51
51
|
"vitest": "^2.1.8",
|
|
52
|
-
"@telorun/sdk": "0.
|
|
52
|
+
"@telorun/sdk": "0.19.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@telorun/sdk": "*"
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ControllerContext,
|
|
13
13
|
Invocable,
|
|
14
14
|
InvokeError,
|
|
15
|
+
isCancellationError,
|
|
15
16
|
isInvokeError,
|
|
16
17
|
KindRef,
|
|
17
18
|
Ref,
|
|
@@ -146,12 +147,33 @@ export class HttpServerApi implements ResourceInstance {
|
|
|
146
147
|
|
|
147
148
|
const sink = fastifyReplySink(reply);
|
|
148
149
|
|
|
150
|
+
// Per-request cancellation: abandon downstream work when the client
|
|
151
|
+
// disconnects before the response is sent. Listen on the response
|
|
152
|
+
// socket, not the request stream — the latter's `close` fires as normal
|
|
153
|
+
// cleanup once a request body has been fully received, which would
|
|
154
|
+
// cancel any body-bearing request that awaits (e.g. a DB call) before
|
|
155
|
+
// replying. The response socket only closes early on a real disconnect.
|
|
156
|
+
const cancellation = this.ctx.createCancellationSource();
|
|
157
|
+
reply.raw.on("close", () => {
|
|
158
|
+
if (!reply.sent) cancellation.cancel("client-disconnect");
|
|
159
|
+
});
|
|
160
|
+
|
|
149
161
|
let result: unknown;
|
|
150
162
|
try {
|
|
151
163
|
result = handler
|
|
152
|
-
? await this.ctx.invokeResolved(
|
|
164
|
+
? await this.ctx.invokeResolved(
|
|
165
|
+
handlerKind,
|
|
166
|
+
handlerName,
|
|
167
|
+
handler,
|
|
168
|
+
invokeInput,
|
|
169
|
+
cancellation.context,
|
|
170
|
+
)
|
|
153
171
|
: undefined;
|
|
154
172
|
} catch (err) {
|
|
173
|
+
if (isCancellationError(err)) {
|
|
174
|
+
if (!reply.sent) reply.code(499).send();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
155
177
|
if (!isInvokeError(err)) throw err;
|
|
156
178
|
return dispatchCatches(
|
|
157
179
|
route.catches,
|