@telorun/http-server 0.2.4 → 0.3.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 +60 -0
- package/dist/http-api-controller.d.ts +102 -26
- package/dist/http-api-controller.js +390 -41
- package/dist/http-server-controller.js +3 -2
- package/package.json +2 -2
- package/src/http-api-controller.ts +462 -46
- package/src/http-server-controller.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# @telorun/http-server
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d3ed5a5: Tighten `Http.Api.routes[].request.headers` to declare `additionalProperties: { type: "string" }`. Header values are matched as strings against the incoming request, so the schema now reflects what the runtime actually accepts. The telo editor renders this field as a key/value map editor instead of the JSON Schema designer.
|
|
8
|
+
|
|
9
|
+
## 1.0.0
|
|
10
|
+
|
|
11
|
+
### Major Changes
|
|
12
|
+
|
|
13
|
+
- b62e535: Streaming-Invocable convention, format-codec packages, and `Http.Api` `content:` map rewrite.
|
|
14
|
+
|
|
15
|
+
**Breaking** (`@telorun/http-server`, `@telorun/ai`):
|
|
16
|
+
|
|
17
|
+
- `Http.Api.routes[].returns[]` and `routes[].catches[]` (and the equivalent `Http.Server.notFoundHandler` lists) drop top-level `body` / `schema` in favour of a per-MIME `content:` map. Buffer-mode entries use `content[<mime>].body` / `content[<mime>].schema`; stream-mode entries use `content[<mime>].encoder` (ref to any `Codec.Encoder`). The map key is the canonical `Content-Type` — declaring `Content-Type` in `headers:` is rejected at load time. Multi-key `content:` maps are negotiated against the request's `Accept` header (RFC 9110 §12.5.1). Mismatch → `406 Not Acceptable`.
|
|
18
|
+
- `mode: stream` is forbidden in `catches:` (catches fire pre-stream; no upstream iterable to feed an encoder).
|
|
19
|
+
- Migration: every existing `returns: [..., body: ..., schema: ..., headers: { Content-Type: ... }]` rewrites mechanically to `returns: [..., content: { <mime>: { body, schema } }]`. In-tree manifests (`apps/registry`, `examples/*`, `tests/*`, `benchmarks/*`) migrated.
|
|
20
|
+
- `Ai.TextStream`: `format` field removed; controller no longer encodes the wire — it returns `{ output: Stream<StreamPart> }`. Pair with a format-codec encoder (`Ndjson.Encoder`, `Sse.Encoder`, `PlainText.Encoder`) for HTTP responses or other byte transports. `text-stream-drain-controller.ts` removed (replaced by inline source → encoder → decoder steps).
|
|
21
|
+
- `StreamPart.error` shape changed from native `Error` to `{ message, code?, data? }` so generic encoders can JSON-serialize error frames without bespoke translation.
|
|
22
|
+
|
|
23
|
+
**New** (`@telorun/codec`, `@telorun/plain-text-codec`, `@telorun/ndjson-codec`, `@telorun/sse-codec`, `@telorun/octet-codec`):
|
|
24
|
+
|
|
25
|
+
- `@telorun/codec` ships the `Encoder` and `Decoder` abstracts (no controllers — pure contracts).
|
|
26
|
+
- Format-codec packages each carry one or both directions: `PlainText.Encoder/.Decoder` (UTF-8 collect + emit), `Ndjson.Encoder` (one JSON record per line), `Sse.Encoder` (Server-Sent Events frames), `Octet.Encoder/.Decoder` (raw bytes pass-through and collect).
|
|
27
|
+
- All encoders implement `invoke({input}): Promise<{output: Stream<Uint8Array>}>` per the streaming-Invocable convention.
|
|
28
|
+
|
|
29
|
+
**New** (`@telorun/sdk`):
|
|
30
|
+
|
|
31
|
+
- `Stream<T>` class wrapping `AsyncIterable<T>`. Producers wrap their iterables in `new Stream(...)` so the value's constructor is recognized by CEL's runtime type-checker (which rejects unrecognized constructors like `AsyncGenerator` and Node `Readable`). The analyzer registers `Stream` as a CEL object type.
|
|
32
|
+
|
|
33
|
+
**Annotation** (`@telorun/kernel`, `@telorun/analyzer`):
|
|
34
|
+
|
|
35
|
+
- `x-telo-stream: true` schema annotation on input/output properties marks them as carrying a `Stream<T>`. CEL passes the value through by reference; analyzer's chain validator rejects `.field` / `[index]` access past a stream-marked property. Convention: streaming Invocables put the stream on `input` (inputs) and `output` (result).
|
|
36
|
+
- `Self.<Abstract>` magic alias auto-registered for every Telo.Library/Application — lets concrete kinds in the same library use `extends: Self.<Abstract>` without a self-import that would loop the loader.
|
|
37
|
+
- Analyzer's `buildReferenceFieldMap`, `resolveFieldValues`, `extractInlinesAtPath`, and `injectAtPath` (Phase 5) now recurse into `additionalProperties` via a `{}` path-segment marker. Required for refs nested inside open-keyed maps like `content[<mime>].encoder`.
|
|
38
|
+
- `isInlineResource` widened: bare-kind refs (`{kind: X}` with no `name` and no extra config) are now treated as inline-singleton definitions and Phase 2 extracts them as fresh stateless resources. Previously `{kind: X}` raised `INVALID_REFERENCE` (treated as a malformed named ref). This matches the runtime-side `resolveChildren` semantics already documented for `Run.Throw`-style stateless inlines, and lets `encoder: {kind: Ndjson.Encoder}` work without boilerplate. Manifests that had `{kind: X}` with the (broken) intent of resolving to an existing named resource will now silently extract a fresh resource — extremely unlikely in practice (those refs were already failing analysis), but worth flagging for downstream consumers.
|
|
39
|
+
|
|
40
|
+
**Behaviour changes worth flagging** (`@telorun/http-server`):
|
|
41
|
+
|
|
42
|
+
- **Single-key `content:` maps now do `Accept` negotiation.** A route declaring only `content: { application/json: ... }` returns `406 Not Acceptable` for `Accept: image/png` — RFC 9110 §15.5.7 compliant. Pre-PR, the legacy top-level `body:` shape ignored `Accept` entirely. To preserve "always send" behaviour, declare `*/*` as an explicit key.
|
|
43
|
+
- **Accept matching ignores media-type parameters** beyond the first `;`. `Accept: text/plain; charset=ascii` matches `content: { 'text/plain; charset=utf-8': ... }`. Q-values are still parsed for ranking; only the matching predicate ignores params. Authors needing parameter-level preference must declare distinct keys per parameter combo.
|
|
44
|
+
- **Load-time validators reject misconfigured `content:` shapes.** `validateContentEntryShape` rejects `body+encoder` together (mutually exclusive), missing `encoder` under `mode: stream`, `body` under `mode: stream`, and `encoder` under `mode: buffer`. Previously some of these slipped through to runtime where they manifested as 500-on-negotiation.
|
|
45
|
+
- **Mid-stream `pipeline()` failures emit `Http.Api.streamFailed` events.** Once `reply.hijack()` runs, mid-stream errors (encoder throws, broken pipe) bypass `catches:` by design (response is committed). They now emit a structured event with `path`, `method`, `status`, `mime`, and the error so operators can observe failures that would otherwise be silent.
|
|
46
|
+
|
|
47
|
+
**Other** (`@telorun/http-client`, `@telorun/javascript`):
|
|
48
|
+
|
|
49
|
+
- `HttpClient.Request` `mode: stream` returns `{ output: Stream<Uint8Array> }` instead of a bare `Readable` — fits the streaming-Invocable convention, pairs with `Octet.Encoder` for HTTP pass-through.
|
|
50
|
+
- `JS.Script` injects `Stream` into every script's scope (via the second function argument, destructured at the top of the wrapper). User code can `new Stream(asyncGen)` directly.
|
|
51
|
+
|
|
52
|
+
**Tests**:
|
|
53
|
+
|
|
54
|
+
- New Layer 1 hermetic streaming-contract test (`modules/ai/tests/text-stream-streaming-contract.yaml`) — three sub-targets, byte-exact NDJSON / SSE / PlainText.
|
|
55
|
+
- New Layer 2 live OpenAI streaming smoke (`modules/ai-openai/tests/openai-live-text-stream.yaml`) — env-gated; exercises `Ai.TextStream → Ndjson.Encoder → PlainText.Decoder` against the real provider.
|
|
56
|
+
- New http-server integration test (`modules/http-server/tests/text-stream-via-http.yaml`) — exercises three single-format routes plus a four-format negotiated route with five Accept variants.
|
|
57
|
+
|
|
58
|
+
### Patch Changes
|
|
59
|
+
|
|
60
|
+
- Updated dependencies [b62e535]
|
|
61
|
+
- @telorun/sdk@0.7.0
|
|
62
|
+
|
|
3
63
|
## 0.2.4
|
|
4
64
|
|
|
5
65
|
### Patch Changes
|
|
@@ -1,28 +1,42 @@
|
|
|
1
1
|
import { Static } from "@sinclair/typebox";
|
|
2
2
|
import { ControllerContext, Invocable, KindRef, ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
3
3
|
import { FastifyInstance, FastifyReply } from "fastify";
|
|
4
|
+
/** Per-MIME content-map entry. Buffer-mode responses use `body` (with optional
|
|
5
|
+
* `schema` for AJV validation); stream-mode responses use `encoder` (a ref to
|
|
6
|
+
* any `Codec.Encoder` implementation). The two are mutually exclusive per
|
|
7
|
+
* value — see dispatch logic. `headers` here merge over the entry-level
|
|
8
|
+
* `headers` (per-MIME wins on conflict). `Content-Type` is forbidden in
|
|
9
|
+
* headers — the map key IS the canonical Content-Type. */
|
|
10
|
+
declare const ContentEntry: import("@sinclair/typebox").TObject<{
|
|
11
|
+
body: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
12
|
+
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
13
|
+
encoder: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<KindRef<Invocable<Record<string, any>, any>>>>;
|
|
14
|
+
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
|
|
15
|
+
}>;
|
|
16
|
+
type ContentEntry = Static<typeof ContentEntry>;
|
|
4
17
|
declare const ReturnEntry: import("@sinclair/typebox").TObject<{
|
|
5
18
|
status: import("@sinclair/typebox").TInteger;
|
|
6
19
|
when: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
7
20
|
mode: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"buffer">, import("@sinclair/typebox").TLiteral<"stream">]>>;
|
|
8
|
-
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
9
|
-
query: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
10
|
-
body: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
11
|
-
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
12
|
-
}>>;
|
|
13
21
|
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
|
|
14
|
-
|
|
22
|
+
content: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
|
|
23
|
+
body: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
24
|
+
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
25
|
+
encoder: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<KindRef<Invocable<Record<string, any>, any>>>>;
|
|
26
|
+
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
|
|
27
|
+
}>>>;
|
|
15
28
|
}>;
|
|
16
29
|
type ReturnEntry = Static<typeof ReturnEntry>;
|
|
17
30
|
declare const CatchEntry: import("@sinclair/typebox").TObject<{
|
|
18
31
|
status: import("@sinclair/typebox").TInteger;
|
|
19
32
|
when: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
20
|
-
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
21
|
-
body: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
22
|
-
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
23
|
-
}>>;
|
|
24
33
|
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
|
|
25
|
-
|
|
34
|
+
content: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
|
|
35
|
+
body: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
36
|
+
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
37
|
+
encoder: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<KindRef<Invocable<Record<string, any>, any>>>>;
|
|
38
|
+
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
|
|
39
|
+
}>>>;
|
|
26
40
|
}>;
|
|
27
41
|
type CatchEntry = Static<typeof CatchEntry>;
|
|
28
42
|
declare const HttpApiManifest: import("@sinclair/typebox").TObject<{
|
|
@@ -43,46 +57,60 @@ declare const HttpApiManifest: import("@sinclair/typebox").TObject<{
|
|
|
43
57
|
status: import("@sinclair/typebox").TInteger;
|
|
44
58
|
when: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
45
59
|
mode: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"buffer">, import("@sinclair/typebox").TLiteral<"stream">]>>;
|
|
46
|
-
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
47
|
-
query: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
48
|
-
body: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
49
|
-
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
50
|
-
}>>;
|
|
51
60
|
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
|
|
52
|
-
|
|
61
|
+
content: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
|
|
62
|
+
body: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
63
|
+
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
64
|
+
encoder: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<KindRef<Invocable<Record<string, any>, any>>>>;
|
|
65
|
+
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
|
|
66
|
+
}>>>;
|
|
53
67
|
}>>;
|
|
54
68
|
catches: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
55
69
|
status: import("@sinclair/typebox").TInteger;
|
|
56
70
|
when: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
57
|
-
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
58
|
-
body: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
59
|
-
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
60
|
-
}>>;
|
|
61
71
|
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
|
|
62
|
-
|
|
72
|
+
content: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
|
|
73
|
+
body: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
74
|
+
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
|
|
75
|
+
encoder: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<KindRef<Invocable<Record<string, any>, any>>>>;
|
|
76
|
+
headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
|
|
77
|
+
}>>>;
|
|
63
78
|
}>>>;
|
|
64
79
|
}>>;
|
|
65
80
|
}>;
|
|
66
81
|
type HttpApiManifest = Static<typeof HttpApiManifest>;
|
|
67
82
|
export declare function register(_ctx: ControllerContext): Promise<void>;
|
|
68
|
-
export type { ReturnEntry, CatchEntry };
|
|
83
|
+
export type { ReturnEntry, CatchEntry, ContentEntry };
|
|
69
84
|
type ModuleLikeContext = {
|
|
70
85
|
expandWith: (v: unknown, ctx: Record<string, unknown>) => unknown;
|
|
71
86
|
};
|
|
72
87
|
type ValidateSchema = (value: unknown, schema: unknown) => void;
|
|
88
|
+
/** Hook invoked when `pipeline()` rejects after `reply.hijack()` — at that
|
|
89
|
+
* point headers are flushed, the response is committed, and `catches:`
|
|
90
|
+
* cannot fire. Surfacing the failure here lets operators observe mid-stream
|
|
91
|
+
* failures that are otherwise silent. */
|
|
92
|
+
type StreamErrorHook = (err: unknown, ctx: {
|
|
93
|
+
status: number;
|
|
94
|
+
mime: string;
|
|
95
|
+
}) => Promise<void> | void;
|
|
73
96
|
type HandlerRef = {
|
|
74
97
|
kind: string;
|
|
75
98
|
name: string;
|
|
76
99
|
};
|
|
77
|
-
export declare function dispatchReturns(returns: ReturnEntry[], result: unknown, requestContext: Record<string, unknown>, moduleContext: ModuleLikeContext, validateSchema: ValidateSchema, reply: FastifyReply): Promise<void>;
|
|
100
|
+
export declare function dispatchReturns(returns: ReturnEntry[], result: unknown, requestContext: Record<string, unknown>, acceptHeader: string | undefined, moduleContext: ModuleLikeContext, validateSchema: ValidateSchema, reply: FastifyReply, streamError?: StreamErrorHook): Promise<void>;
|
|
78
101
|
/** Render an InvokeError through a `catches:` list. Falls back to a structured
|
|
79
102
|
* 500 when no entry matches. Plain (non-InvokeError) throws never reach this
|
|
80
|
-
* function — the caller re-throws them to Fastify.
|
|
103
|
+
* function — the caller re-throws them to Fastify.
|
|
104
|
+
*
|
|
105
|
+
* Catches are buffer-mode only by design: by the time a catch fires the
|
|
106
|
+
* response is committed pre-stream and there's no upstream iterable to feed
|
|
107
|
+
* an encoder. content[mime] entries carry body/schema/headers; encoder/mode
|
|
108
|
+
* fields are not part of the catch schema. */
|
|
81
109
|
export declare function dispatchCatches(catches: CatchEntry[] | undefined, error: {
|
|
82
110
|
code: string;
|
|
83
111
|
message: string;
|
|
84
112
|
data?: unknown;
|
|
85
|
-
}, requestContext: Record<string, unknown>, moduleContext: ModuleLikeContext, validateSchema: ValidateSchema, reply: FastifyReply): Promise<void>;
|
|
113
|
+
}, requestContext: Record<string, unknown>, acceptHeader: string | undefined, moduleContext: ModuleLikeContext, validateSchema: ValidateSchema, reply: FastifyReply): Promise<void>;
|
|
86
114
|
export declare class HttpServerApi implements ResourceInstance {
|
|
87
115
|
private readonly ctx;
|
|
88
116
|
readonly manifest: HttpApiManifest;
|
|
@@ -94,3 +122,51 @@ export declare class HttpServerApi implements ResourceInstance {
|
|
|
94
122
|
private registerRoute;
|
|
95
123
|
}
|
|
96
124
|
export declare function create(resource: any, ctx: ResourceContext): Promise<HttpServerApi>;
|
|
125
|
+
/** Rejects `Content-Type` (case-insensitive) anywhere in entry-level or
|
|
126
|
+
* per-MIME `headers:` blocks. The matched `content[mime]` map key IS the
|
|
127
|
+
* canonical Content-Type — declaring it again in `headers:` would either be
|
|
128
|
+
* redundant or contradictory. */
|
|
129
|
+
export declare function validateNoContentTypeHeader(resource: {
|
|
130
|
+
routes?: Array<{
|
|
131
|
+
request?: {
|
|
132
|
+
path?: string;
|
|
133
|
+
};
|
|
134
|
+
returns?: ReturnEntry[];
|
|
135
|
+
catches?: CatchEntry[];
|
|
136
|
+
}>;
|
|
137
|
+
}): void;
|
|
138
|
+
/** Enforces per-mode shape rules on every `content[mime]` value:
|
|
139
|
+
* - `body` and `encoder` are mutually exclusive. Declaring both is rejected
|
|
140
|
+
* because dispatch would silently pick one based on entry `mode:` and the
|
|
141
|
+
* other becomes a no-op — a correctness footgun.
|
|
142
|
+
* - `mode: stream` requires every content[mime] to declare `encoder` (and
|
|
143
|
+
* forbids `body`). Without this check, an entry with one encoder-bearing
|
|
144
|
+
* key plus a body-only key would pass at load and only fail at runtime
|
|
145
|
+
* when the body-only key won negotiation — leaving a half-broken route.
|
|
146
|
+
* - `mode: buffer` (the default) forbids `encoder` (which would never run). */
|
|
147
|
+
export declare function validateContentEntryShape(resource: {
|
|
148
|
+
routes?: Array<{
|
|
149
|
+
request?: {
|
|
150
|
+
path?: string;
|
|
151
|
+
};
|
|
152
|
+
returns?: ReturnEntry[];
|
|
153
|
+
}>;
|
|
154
|
+
}): void;
|
|
155
|
+
/** Rejects `when:` CEL expressions on stream-mode `returns:` entries that
|
|
156
|
+
* reference the root `result` identifier. The handler result in stream mode
|
|
157
|
+
* is an unconsumed `Stream<...>`; iterating it to evaluate the predicate
|
|
158
|
+
* would either fail or consume the stream before bytes flow to the response.
|
|
159
|
+
* References to `request.*` are fine — they don't touch the stream.
|
|
160
|
+
*
|
|
161
|
+
* This is a runtime safety net; the analyzer's static chain validator is
|
|
162
|
+
* authoritative. The check here is intentionally token-aware (skips string
|
|
163
|
+
* literals and `.result` member access) so it doesn't false-positive on
|
|
164
|
+
* benign expressions like `request.headers["x-result"]`. */
|
|
165
|
+
export declare function validateStreamWhenDoesNotReferenceResult(resource: {
|
|
166
|
+
routes?: Array<{
|
|
167
|
+
request?: {
|
|
168
|
+
path?: string;
|
|
169
|
+
};
|
|
170
|
+
returns?: ReturnEntry[];
|
|
171
|
+
}>;
|
|
172
|
+
}): void;
|