@telorun/http-server 0.7.0 → 0.8.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 +36 -0
- package/dist/http-api-controller.js +14 -2
- package/package.json +2 -2
- package/src/http-api-controller.ts +19 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @telorun/http-server
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5331205: Add cooperative invoke cancellation via an out-of-band `InvokeContext`.
|
|
8
|
+
|
|
9
|
+
Every `invoke(inputs, ctx?)` now receives a second argument carrying a read-only
|
|
10
|
+
cancellation token (`ctx.cancellation`): poll `isCancelled`, subscribe via
|
|
11
|
+
`onCancelled`, bail with `throwIfCancelled`, or hand its `signal` to a Web API.
|
|
12
|
+
The SDK exposes the source/token split (`createCancellationSource`,
|
|
13
|
+
`CancellationSource`/`CancellationToken`), a never-cancellable sentinel, and the
|
|
14
|
+
`isCancellationError` helper. Deadlines are scheduled cancellation
|
|
15
|
+
(`source.cancelAt(epochMs)` / `cancelAfter(ms)`).
|
|
16
|
+
|
|
17
|
+
The kernel mints one cancellation scope per invocation tree (inherited by nested
|
|
18
|
+
invokes via a kernel-internal `AsyncLocalStorage`, always passed to controllers
|
|
19
|
+
as the explicit argument), refuses a not-yet-dispatched invoke whose tree was
|
|
20
|
+
cancelled with `ERR_INVOKE_CANCELLED`, and emits a scoped `InvokeCancelled`
|
|
21
|
+
event. `Kernel.invoke(ref, inputs, opts?)` accepts `{ signal, deadlineAt }`.
|
|
22
|
+
Sources are allocated lazily, so invokes that never touch cancellation pay no
|
|
23
|
+
extra allocation.
|
|
24
|
+
|
|
25
|
+
The boot `targets` run is also cancellable: `Runnable.run(ctx?)` now receives
|
|
26
|
+
the token, `Kernel.cancel(reason?)` cancels the boot scope, and the CLI's
|
|
27
|
+
SIGINT/SIGTERM handler calls it so Ctrl-C cooperatively stops honoring targets
|
|
28
|
+
and in-flight invoke trees (then unblocks graceful exit via `forceIdle`).
|
|
29
|
+
|
|
30
|
+
Honoring leaves: `Ai.Text` / `Ai.TextStream` / `Ai.Agent` forward the token's
|
|
31
|
+
signal into the model (aborting a live LLM stream on cancel); `http-client`
|
|
32
|
+
merges it with its request timeout. Triggers: `http-server` cancels on client
|
|
33
|
+
disconnect and returns 499; `lambda` arms cancellation at the AWS deadline.
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- @telorun/http-dispatch@0.4.1
|
|
38
|
+
|
|
3
39
|
## 0.7.0
|
|
4
40
|
|
|
5
41
|
### 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,25 @@ 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.
|
|
111
|
+
const cancellation = this.ctx.createCancellationSource();
|
|
112
|
+
request.raw.on("close", () => {
|
|
113
|
+
if (!reply.sent)
|
|
114
|
+
cancellation.cancel("client-disconnect");
|
|
115
|
+
});
|
|
109
116
|
let result;
|
|
110
117
|
try {
|
|
111
118
|
result = handler
|
|
112
|
-
? await this.ctx.invokeResolved(handlerKind, handlerName, handler, invokeInput)
|
|
119
|
+
? await this.ctx.invokeResolved(handlerKind, handlerName, handler, invokeInput, cancellation.context)
|
|
113
120
|
: undefined;
|
|
114
121
|
}
|
|
115
122
|
catch (err) {
|
|
123
|
+
if (isCancellationError(err)) {
|
|
124
|
+
if (!reply.sent)
|
|
125
|
+
reply.code(499).send();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
116
128
|
if (!isInvokeError(err))
|
|
117
129
|
throw err;
|
|
118
130
|
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.0",
|
|
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,29 @@ 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.
|
|
152
|
+
const cancellation = this.ctx.createCancellationSource();
|
|
153
|
+
request.raw.on("close", () => {
|
|
154
|
+
if (!reply.sent) cancellation.cancel("client-disconnect");
|
|
155
|
+
});
|
|
156
|
+
|
|
149
157
|
let result: unknown;
|
|
150
158
|
try {
|
|
151
159
|
result = handler
|
|
152
|
-
? await this.ctx.invokeResolved(
|
|
160
|
+
? await this.ctx.invokeResolved(
|
|
161
|
+
handlerKind,
|
|
162
|
+
handlerName,
|
|
163
|
+
handler,
|
|
164
|
+
invokeInput,
|
|
165
|
+
cancellation.context,
|
|
166
|
+
)
|
|
153
167
|
: undefined;
|
|
154
168
|
} catch (err) {
|
|
169
|
+
if (isCancellationError(err)) {
|
|
170
|
+
if (!reply.sent) reply.code(499).send();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
155
173
|
if (!isInvokeError(err)) throw err;
|
|
156
174
|
return dispatchCatches(
|
|
157
175
|
route.catches,
|