@astrale-os/kernel-server 0.5.0-beta.8 → 1.0.0-beta.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/dist/capture.d.ts +0 -1
- package/dist/capture.js +0 -1
- package/dist/cors/cors.d.ts +0 -1
- package/dist/cors/cors.js +11 -4
- package/dist/cors/index.d.ts +0 -1
- package/dist/cors/index.js +0 -1
- package/dist/cors/types.d.ts +0 -1
- package/dist/cors/types.js +0 -1
- package/dist/errors/admission.d.ts +7 -0
- package/dist/errors/admission.js +22 -0
- package/dist/errors/availability.d.ts +0 -1
- package/dist/errors/availability.js +0 -1
- package/dist/errors/classify.d.ts +8 -1
- package/dist/errors/classify.js +15 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +2 -2
- package/dist/errors/request-body.d.ts +0 -1
- package/dist/errors/request-body.js +0 -1
- package/dist/errors/response-body.d.ts +0 -1
- package/dist/errors/response-body.js +0 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +2 -3
- package/dist/invocation/context.d.ts +3 -3
- package/dist/invocation/context.js +2 -2
- package/dist/invocation/identity.d.ts +1 -2
- package/dist/invocation/identity.js +1 -2
- package/dist/invocation/index.d.ts +1 -2
- package/dist/invocation/index.js +1 -2
- package/dist/invocation/release-route.d.ts +22 -0
- package/dist/invocation/release-route.js +32 -0
- package/dist/invocation/types.d.ts +8 -7
- package/dist/invocation/types.js +0 -1
- package/dist/issuer/exchange.d.ts +0 -1
- package/dist/issuer/exchange.js +20 -10
- package/dist/issuer/index.d.ts +0 -1
- package/dist/issuer/index.js +0 -1
- package/dist/issuer/routes.d.ts +0 -1
- package/dist/issuer/routes.js +57 -48
- package/dist/issuer/types.d.ts +5 -1
- package/dist/issuer/types.js +0 -1
- package/dist/lifecycle/active.d.ts +4 -1
- package/dist/lifecycle/active.js +48 -11
- package/dist/lifecycle/index.d.ts +0 -1
- package/dist/lifecycle/index.js +0 -1
- package/dist/release/delivery/handler.d.ts +9 -0
- package/dist/{publication → release}/delivery/handler.js +19 -13
- package/dist/release/delivery/index.d.ts +1 -0
- package/dist/release/delivery/index.js +1 -0
- package/dist/{publication → release}/delivery/types.d.ts +4 -5
- package/dist/release/delivery/types.js +1 -0
- package/dist/release/index.d.ts +3 -0
- package/dist/release/index.js +2 -0
- package/dist/release/served.d.ts +21 -0
- package/dist/release/served.js +10 -0
- package/dist/release/snapshot.d.ts +22 -0
- package/dist/release/snapshot.js +49 -0
- package/dist/release/table.d.ts +4 -0
- package/dist/release/table.js +11 -0
- package/dist/release/types.d.ts +23 -0
- package/dist/release/types.js +1 -0
- package/dist/routes/address.d.ts +2 -0
- package/dist/routes/address.js +6 -0
- package/dist/routes/binary.d.ts +9 -0
- package/dist/routes/binary.js +110 -0
- package/dist/routes/bundle.d.ts +5 -0
- package/dist/routes/bundle.js +50 -0
- package/dist/routes/csrf.d.ts +16 -0
- package/dist/routes/csrf.js +35 -0
- package/dist/routes/document.d.ts +6 -0
- package/dist/routes/document.js +57 -0
- package/dist/routes/index.d.ts +3 -2
- package/dist/routes/index.js +2 -2
- package/dist/routes/model.d.ts +15 -0
- package/dist/routes/model.js +1 -0
- package/dist/routes/native.d.ts +17 -0
- package/dist/routes/native.js +126 -0
- package/dist/routes/preflight.d.ts +4 -0
- package/dist/routes/preflight.js +55 -0
- package/dist/routes/response.d.ts +14 -0
- package/dist/routes/response.js +33 -0
- package/dist/routes/routes.d.ts +7 -24
- package/dist/routes/routes.js +24 -439
- package/dist/routes/snapshot.d.ts +13 -0
- package/dist/routes/snapshot.js +39 -0
- package/dist/routes/stream.d.ts +6 -0
- package/dist/routes/stream.js +122 -0
- package/dist/server.d.ts +0 -1
- package/dist/server.js +95 -51
- package/dist/transport/body.d.ts +0 -1
- package/dist/transport/body.js +0 -1
- package/dist/transport/codecs.d.ts +2 -3
- package/dist/transport/codecs.js +4 -4
- package/dist/transport/credential.d.ts +0 -1
- package/dist/transport/credential.js +0 -1
- package/dist/transport/http/binary-request.d.ts +11 -0
- package/dist/transport/http/binary-request.js +102 -0
- package/dist/transport/http/binary.d.ts +6 -0
- package/dist/transport/http/binary.js +35 -0
- package/dist/transport/http/byte-stream.d.ts +4 -0
- package/dist/transport/http/byte-stream.js +123 -0
- package/dist/transport/http/control.d.ts +12 -0
- package/dist/transport/http/control.js +24 -0
- package/dist/transport/http/handler.d.ts +8 -3
- package/dist/transport/http/handler.js +97 -45
- package/dist/transport/http/index.d.ts +3 -2
- package/dist/transport/http/index.js +2 -2
- package/dist/transport/http/model.d.ts +20 -0
- package/dist/transport/http/model.js +1 -0
- package/dist/transport/http/render.d.ts +3 -31
- package/dist/transport/http/render.js +18 -269
- package/dist/transport/http/response.d.ts +14 -0
- package/dist/transport/http/response.js +77 -0
- package/dist/transport/http/stream.d.ts +5 -0
- package/dist/transport/http/stream.js +134 -0
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js +2 -2
- package/dist/transport/result.d.ts +13 -0
- package/dist/transport/result.js +35 -0
- package/dist/transport/router.d.ts +0 -1
- package/dist/transport/router.js +0 -1
- package/dist/transport/stream-error.d.ts +0 -1
- package/dist/transport/stream-error.js +0 -1
- package/dist/transport/types.d.ts +1 -2
- package/dist/transport/types.js +0 -1
- package/dist/transport/websocket/index.d.ts +0 -1
- package/dist/transport/websocket/index.js +0 -1
- package/dist/transport/websocket/session.d.ts +3 -3
- package/dist/transport/websocket/session.js +30 -30
- package/dist/transport/websocket/upgrade.d.ts +2 -3
- package/dist/transport/websocket/upgrade.js +7 -7
- package/dist/types.d.ts +33 -10
- package/dist/types.js +0 -1
- package/package.json +11 -22
- package/dist/capture.d.ts.map +0 -1
- package/dist/capture.js.map +0 -1
- package/dist/cors/cors.d.ts.map +0 -1
- package/dist/cors/cors.js.map +0 -1
- package/dist/cors/index.d.ts.map +0 -1
- package/dist/cors/index.js.map +0 -1
- package/dist/cors/types.d.ts.map +0 -1
- package/dist/cors/types.js.map +0 -1
- package/dist/errors/availability.d.ts.map +0 -1
- package/dist/errors/availability.js.map +0 -1
- package/dist/errors/classify.d.ts.map +0 -1
- package/dist/errors/classify.js.map +0 -1
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/index.js.map +0 -1
- package/dist/errors/request-body.d.ts.map +0 -1
- package/dist/errors/request-body.js.map +0 -1
- package/dist/errors/response-body.d.ts.map +0 -1
- package/dist/errors/response-body.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invocation/context.d.ts.map +0 -1
- package/dist/invocation/context.js.map +0 -1
- package/dist/invocation/identity.d.ts.map +0 -1
- package/dist/invocation/identity.js.map +0 -1
- package/dist/invocation/index.d.ts.map +0 -1
- package/dist/invocation/index.js.map +0 -1
- package/dist/invocation/publication-route.d.ts +0 -14
- package/dist/invocation/publication-route.d.ts.map +0 -1
- package/dist/invocation/publication-route.js +0 -45
- package/dist/invocation/publication-route.js.map +0 -1
- package/dist/invocation/types.d.ts.map +0 -1
- package/dist/invocation/types.js.map +0 -1
- package/dist/issuer/exchange.d.ts.map +0 -1
- package/dist/issuer/exchange.js.map +0 -1
- package/dist/issuer/index.d.ts.map +0 -1
- package/dist/issuer/index.js.map +0 -1
- package/dist/issuer/routes.d.ts.map +0 -1
- package/dist/issuer/routes.js.map +0 -1
- package/dist/issuer/types.d.ts.map +0 -1
- package/dist/issuer/types.js.map +0 -1
- package/dist/lifecycle/active.d.ts.map +0 -1
- package/dist/lifecycle/active.js.map +0 -1
- package/dist/lifecycle/index.d.ts.map +0 -1
- package/dist/lifecycle/index.js.map +0 -1
- package/dist/publication/delivery/handler.d.ts +0 -10
- package/dist/publication/delivery/handler.d.ts.map +0 -1
- package/dist/publication/delivery/handler.js.map +0 -1
- package/dist/publication/delivery/index.d.ts +0 -3
- package/dist/publication/delivery/index.d.ts.map +0 -1
- package/dist/publication/delivery/index.js +0 -2
- package/dist/publication/delivery/index.js.map +0 -1
- package/dist/publication/delivery/types.d.ts.map +0 -1
- package/dist/publication/delivery/types.js +0 -2
- package/dist/publication/delivery/types.js.map +0 -1
- package/dist/publication/index.d.ts +0 -4
- package/dist/publication/index.d.ts.map +0 -1
- package/dist/publication/index.js +0 -3
- package/dist/publication/index.js.map +0 -1
- package/dist/publication/snapshot.d.ts +0 -16
- package/dist/publication/snapshot.d.ts.map +0 -1
- package/dist/publication/snapshot.js +0 -39
- package/dist/publication/snapshot.js.map +0 -1
- package/dist/publication/table.d.ts +0 -4
- package/dist/publication/table.d.ts.map +0 -1
- package/dist/publication/table.js +0 -17
- package/dist/publication/table.js.map +0 -1
- package/dist/publication/types.d.ts +0 -23
- package/dist/publication/types.d.ts.map +0 -1
- package/dist/publication/types.js +0 -2
- package/dist/publication/types.js.map +0 -1
- package/dist/publication/validator.d.ts +0 -4
- package/dist/publication/validator.d.ts.map +0 -1
- package/dist/publication/validator.js +0 -17
- package/dist/publication/validator.js.map +0 -1
- package/dist/routes/index.d.ts.map +0 -1
- package/dist/routes/index.js.map +0 -1
- package/dist/routes/routes.d.ts.map +0 -1
- package/dist/routes/routes.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/transport/body.d.ts.map +0 -1
- package/dist/transport/body.js.map +0 -1
- package/dist/transport/codecs.d.ts.map +0 -1
- package/dist/transport/codecs.js.map +0 -1
- package/dist/transport/credential.d.ts.map +0 -1
- package/dist/transport/credential.js.map +0 -1
- package/dist/transport/http/handler.d.ts.map +0 -1
- package/dist/transport/http/handler.js.map +0 -1
- package/dist/transport/http/index.d.ts.map +0 -1
- package/dist/transport/http/index.js.map +0 -1
- package/dist/transport/http/render.d.ts.map +0 -1
- package/dist/transport/http/render.js.map +0 -1
- package/dist/transport/index.d.ts.map +0 -1
- package/dist/transport/index.js.map +0 -1
- package/dist/transport/router.d.ts.map +0 -1
- package/dist/transport/router.js.map +0 -1
- package/dist/transport/stream-error.d.ts.map +0 -1
- package/dist/transport/stream-error.js.map +0 -1
- package/dist/transport/types.d.ts.map +0 -1
- package/dist/transport/types.js.map +0 -1
- package/dist/transport/websocket/index.d.ts.map +0 -1
- package/dist/transport/websocket/index.js.map +0 -1
- package/dist/transport/websocket/session.d.ts.map +0 -1
- package/dist/transport/websocket/session.js.map +0 -1
- package/dist/transport/websocket/upgrade.d.ts.map +0 -1
- package/dist/transport/websocket/upgrade.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import type { CorsPolicy } from '../../cors/index.js';
|
|
2
2
|
import type { NextInvocationId } from '../../invocation/index.js';
|
|
3
3
|
import type { ActiveOperations } from '../../lifecycle/index.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { RequestServing } from '../../release/served.js';
|
|
5
5
|
import { type ApplicationHost, type Handler } from '../../invocation/index.js';
|
|
6
6
|
export interface HttpHandlerConfig {
|
|
7
7
|
readonly handler: Handler;
|
|
8
8
|
readonly active: ActiveOperations;
|
|
9
9
|
readonly invocation: NextInvocationId;
|
|
10
10
|
readonly maximumRequestBytes: number;
|
|
11
|
+
readonly maximumBinaryRequestBytes: number;
|
|
11
12
|
readonly maximumResponseBytes: number;
|
|
12
13
|
readonly invocationTimeoutMs: number;
|
|
14
|
+
readonly binaryRequestTimeoutMs: number;
|
|
15
|
+
readonly binaryRequestIdleTimeoutMs?: number;
|
|
16
|
+
readonly binaryDownloadTarget?: string;
|
|
17
|
+
readonly binaryDownloadTimeoutMs?: number;
|
|
13
18
|
readonly cors: CorsPolicy;
|
|
14
19
|
readonly websocket: boolean;
|
|
15
|
-
|
|
20
|
+
/** Present exactly when the Server serves a Release. */
|
|
21
|
+
readonly serving?: (request: Request) => RequestServing;
|
|
16
22
|
}
|
|
17
23
|
export declare function createHttpHandler(config: HttpHandlerConfig): (request: Request, host?: ApplicationHost) => Promise<Response>;
|
|
18
|
-
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import * as codecs from '@astrale-os/kernel-protocol/codecs';
|
|
2
2
|
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
3
3
|
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
4
|
-
import { classifyError, RequestBodyInvalidError, RequestBodyTooLargeError, } from '../../errors/index.js';
|
|
5
|
-
import {
|
|
4
|
+
import { admissionRefusal, classifyError, classifyRequestError, RequestBodyInvalidError, RequestBodyTooLargeError, } from '../../errors/index.js';
|
|
5
|
+
import { admitVia, invocationContext, } from '../../invocation/index.js';
|
|
6
|
+
import { pinned } from '../../release/served.js';
|
|
6
7
|
import { readBoundedBody } from '../body.js';
|
|
7
8
|
import { loadCodecs } from '../codecs.js';
|
|
8
9
|
import { resolveCredential } from '../credential.js';
|
|
9
|
-
import {
|
|
10
|
+
import { isStreamingBinary } from '../result.js';
|
|
11
|
+
import { binaryRequestBody } from './binary-request.js';
|
|
12
|
+
import { renderDeploymentRetired, renderHttpError } from './control.js';
|
|
13
|
+
import { renderHttpResult } from './render.js';
|
|
10
14
|
export function createHttpHandler(config) {
|
|
11
15
|
return async (request, host) => {
|
|
12
16
|
const cors = (exposedHeaders) => config.cors.response(request, exposedHeaders);
|
|
@@ -21,6 +25,8 @@ export function createHttpHandler(config) {
|
|
|
21
25
|
headers: [
|
|
22
26
|
'authorization',
|
|
23
27
|
'content-type',
|
|
28
|
+
protocol.http.REQUEST_ENVELOPE_FIELD,
|
|
29
|
+
protocol.http.BINARY_LENGTH_FIELD,
|
|
24
30
|
protocol.http.TRACEPARENT_FIELD,
|
|
25
31
|
protocol.http.TRACESTATE_FIELD,
|
|
26
32
|
],
|
|
@@ -37,24 +43,34 @@ export function createHttpHandler(config) {
|
|
|
37
43
|
...options,
|
|
38
44
|
maximumResponseBytes: config.maximumResponseBytes,
|
|
39
45
|
});
|
|
40
|
-
const
|
|
46
|
+
const binaryRequest = request.headers.get('content-type')?.trim().toLowerCase() ===
|
|
47
|
+
protocol.http.BINARY_REQUEST_CONTENT_TYPE;
|
|
48
|
+
const active = config.active.begin(binaryRequest ? config.binaryRequestTimeoutMs : config.invocationTimeoutMs, request.signal);
|
|
41
49
|
if (active === undefined) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
if (binaryRequest && request.body !== null && !request.body.locked) {
|
|
51
|
+
void request.body.cancel('binary request rejected').catch(() => undefined);
|
|
52
|
+
}
|
|
53
|
+
return renderError(null, admissionRefusal(config.active.isAccepting), codecs.json, {
|
|
54
|
+
status: 503,
|
|
55
|
+
negotiated: false,
|
|
56
|
+
});
|
|
46
57
|
}
|
|
47
58
|
let handedOff = false;
|
|
59
|
+
let binary;
|
|
48
60
|
try {
|
|
49
|
-
const supported =
|
|
61
|
+
const supported = loadCodecs();
|
|
50
62
|
const contentType = request.headers.get('content-type');
|
|
51
|
-
|
|
63
|
+
// No default representation: a body without a declared Astrale type is never decoded, so a
|
|
64
|
+
// browser cannot send an admissible invocation from another origin without a CORS preflight.
|
|
65
|
+
const incoming = binaryRequest
|
|
52
66
|
? codecs.json
|
|
53
67
|
: protocol.http.codecForContentType(contentType, supported);
|
|
54
68
|
if (incoming === undefined) {
|
|
55
69
|
return renderError(null, {
|
|
56
70
|
code: errors.CODES.UNSUPPORTED,
|
|
57
|
-
message:
|
|
71
|
+
message: contentType === null || contentType.trim() === ''
|
|
72
|
+
? 'Missing request Content-Type.'
|
|
73
|
+
: 'Unsupported request Content-Type.',
|
|
58
74
|
}, codecs.json, { status: 415, negotiated: false });
|
|
59
75
|
}
|
|
60
76
|
const outgoing = protocol.http.negotiateResponseCodec(request.headers.get('accept'), incoming, supported);
|
|
@@ -66,7 +82,15 @@ export function createHttpHandler(config) {
|
|
|
66
82
|
}
|
|
67
83
|
let body;
|
|
68
84
|
try {
|
|
69
|
-
|
|
85
|
+
if (binaryRequest)
|
|
86
|
+
binary = binaryRequestBody(request, config.maximumBinaryRequestBytes, active.signal, config.binaryRequestIdleTimeoutMs === undefined
|
|
87
|
+
? undefined
|
|
88
|
+
: {
|
|
89
|
+
timeoutMs: config.binaryRequestIdleTimeoutMs,
|
|
90
|
+
cancel: () => active.cancel('binary request idle timeout exceeded'),
|
|
91
|
+
});
|
|
92
|
+
else
|
|
93
|
+
body = await readBoundedBody(request, config.maximumRequestBytes, active.signal);
|
|
70
94
|
}
|
|
71
95
|
catch (input) {
|
|
72
96
|
const payload = input instanceof RequestBodyTooLargeError
|
|
@@ -87,37 +111,54 @@ export function createHttpHandler(config) {
|
|
|
87
111
|
status: input instanceof RequestBodyTooLargeError ? 413 : errors.httpStatusOf(payload),
|
|
88
112
|
});
|
|
89
113
|
}
|
|
90
|
-
let decoded;
|
|
91
|
-
try {
|
|
92
|
-
decoded = incoming.decode(body);
|
|
93
|
-
}
|
|
94
|
-
catch (input) {
|
|
95
|
-
if (!(input instanceof codecs.DecodeError))
|
|
96
|
-
throw input;
|
|
97
|
-
return renderError(null, { code: errors.CODES.PARSE_ERROR, message: 'Request body is invalid.' }, outgoing);
|
|
98
|
-
}
|
|
99
114
|
let envelope;
|
|
100
115
|
try {
|
|
101
|
-
|
|
116
|
+
if (binaryRequest) {
|
|
117
|
+
envelope = protocol.http.decodeBinaryRequestEnvelope(request.headers.get(protocol.http.REQUEST_ENVELOPE_FIELD));
|
|
118
|
+
if (envelope.idempotencyKey !== undefined) {
|
|
119
|
+
return renderError(envelope.requestId, {
|
|
120
|
+
code: errors.CODES.INVALID_REQUEST,
|
|
121
|
+
message: 'Binary requests forbid idempotencyKey.',
|
|
122
|
+
}, outgoing);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
let decoded;
|
|
127
|
+
try {
|
|
128
|
+
decoded = incoming.decode(body);
|
|
129
|
+
}
|
|
130
|
+
catch (input) {
|
|
131
|
+
if (!(input instanceof codecs.DecodeError))
|
|
132
|
+
throw input;
|
|
133
|
+
return renderError(null, { code: errors.CODES.PARSE_ERROR, message: 'Request body is invalid.' }, outgoing);
|
|
134
|
+
}
|
|
135
|
+
envelope = protocol.decodeRequest(decoded);
|
|
136
|
+
}
|
|
102
137
|
}
|
|
103
138
|
catch (input) {
|
|
104
139
|
return renderError(null, classifyError(input), outgoing);
|
|
105
140
|
}
|
|
106
|
-
|
|
107
|
-
|
|
141
|
+
if (!binaryRequest &&
|
|
142
|
+
config.binaryDownloadTarget !== undefined &&
|
|
143
|
+
protocol.targetPath(envelope.call.target) === config.binaryDownloadTarget) {
|
|
144
|
+
active.extend(config.binaryDownloadTimeoutMs);
|
|
145
|
+
}
|
|
146
|
+
if (active.signal.aborted) {
|
|
147
|
+
return renderError(envelope.requestId, classifyRequestError(active.signal.reason, active), outgoing);
|
|
148
|
+
}
|
|
149
|
+
const serving = config.serving?.(request);
|
|
150
|
+
const served = serving?.current();
|
|
151
|
+
if (serving !== undefined && served === undefined) {
|
|
108
152
|
return renderError(envelope.requestId, {
|
|
109
153
|
code: errors.CODES.BACKEND_UNAVAILABLE,
|
|
110
|
-
message: '
|
|
154
|
+
message: 'Release is unavailable.',
|
|
111
155
|
}, outgoing, { status: 503 });
|
|
112
156
|
}
|
|
113
157
|
if (envelope.routing?.kind === 'via') {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
message: 'Publication routing requires a Publication-backed destination.',
|
|
118
|
-
}, outgoing);
|
|
158
|
+
const decision = admitVia(served, envelope.routing);
|
|
159
|
+
if (decision.kind === 'retired') {
|
|
160
|
+
return renderDeploymentRetired(serving.deployment(), decision.release, cors);
|
|
119
161
|
}
|
|
120
|
-
const decision = publicationRouteDecision(publication.publication, envelope.routing.publication);
|
|
121
162
|
if (decision.kind === 'rejected') {
|
|
122
163
|
return renderError(envelope.requestId, decision.error, outgoing);
|
|
123
164
|
}
|
|
@@ -135,9 +176,20 @@ export function createHttpHandler(config) {
|
|
|
135
176
|
...(envelope.delegate === undefined ? {} : { delegate: envelope.delegate }),
|
|
136
177
|
...(envelope.routing?.kind === 'via' ? { via: envelope.routing } : {}),
|
|
137
178
|
...(envelope.schema === undefined ? {} : { schema: envelope.schema }),
|
|
138
|
-
...(
|
|
139
|
-
context: invocationContext(assigned, active, config.active, host, undefined, envelope.idempotencyKey),
|
|
179
|
+
...(served === undefined ? {} : pinned(served)),
|
|
180
|
+
context: invocationContext(assigned, active, config.active, host, undefined, envelope.idempotencyKey, binary),
|
|
140
181
|
}));
|
|
182
|
+
if (binary !== undefined && result.kind !== 'redirect' && result.kind !== 'miss') {
|
|
183
|
+
if (!binary.completed()) {
|
|
184
|
+
if (result.kind === 'stream') {
|
|
185
|
+
await result.stream.cancel('Binary request body was not fully consumed.');
|
|
186
|
+
}
|
|
187
|
+
else if (result.kind === 'binary' && isStreamingBinary(result.value)) {
|
|
188
|
+
await result.value.body[Symbol.asyncIterator]().return?.();
|
|
189
|
+
}
|
|
190
|
+
throw new Error('Binary Function returned before consuming its request body.');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
141
193
|
const response = renderHttpResult(result, {
|
|
142
194
|
requestId: envelope.requestId,
|
|
143
195
|
codec: outgoing,
|
|
@@ -146,20 +198,12 @@ export function createHttpHandler(config) {
|
|
|
146
198
|
active,
|
|
147
199
|
maximumResponseBytes: config.maximumResponseBytes,
|
|
148
200
|
});
|
|
149
|
-
handedOff =
|
|
201
|
+
handedOff =
|
|
202
|
+
result.kind === 'stream' || (result.kind === 'binary' && isStreamingBinary(result.value));
|
|
150
203
|
return response;
|
|
151
204
|
}
|
|
152
205
|
catch (input) {
|
|
153
|
-
|
|
154
|
-
return renderError(envelope.requestId, {
|
|
155
|
-
code: errors.CODES.DEADLINE_EXCEEDED,
|
|
156
|
-
message: 'Invocation deadline exceeded.',
|
|
157
|
-
}, outgoing, { status: 504 });
|
|
158
|
-
}
|
|
159
|
-
if (active.signal.aborted) {
|
|
160
|
-
return renderError(envelope.requestId, { code: errors.CODES.CANCELLED, message: 'Invocation was cancelled.' }, outgoing, { status: 408 });
|
|
161
|
-
}
|
|
162
|
-
return renderError(envelope.requestId, classifyError(input), outgoing);
|
|
206
|
+
return renderError(envelope.requestId, classifyRequestError(input, active), outgoing);
|
|
163
207
|
}
|
|
164
208
|
}
|
|
165
209
|
catch (input) {
|
|
@@ -168,6 +212,15 @@ export function createHttpHandler(config) {
|
|
|
168
212
|
});
|
|
169
213
|
}
|
|
170
214
|
finally {
|
|
215
|
+
if (binary !== undefined) {
|
|
216
|
+
binary.cancel('binary invocation completed');
|
|
217
|
+
}
|
|
218
|
+
else if (binaryRequest &&
|
|
219
|
+
binary === undefined &&
|
|
220
|
+
request.body !== null &&
|
|
221
|
+
!request.body.locked) {
|
|
222
|
+
void request.body.cancel('binary request rejected').catch(() => undefined);
|
|
223
|
+
}
|
|
171
224
|
if (!handedOff)
|
|
172
225
|
active.settle();
|
|
173
226
|
}
|
|
@@ -178,4 +231,3 @@ function noStore(response) {
|
|
|
178
231
|
headers.set('cache-control', 'no-store');
|
|
179
232
|
return new Response(response.body, { status: response.status, headers });
|
|
180
233
|
}
|
|
181
|
-
//# sourceMappingURL=handler.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { createHttpHandler, type HttpHandlerConfig } from './handler.js';
|
|
2
|
-
export {
|
|
3
|
-
|
|
2
|
+
export { renderHttpResult } from './render.js';
|
|
3
|
+
export { renderHttpError, renderHttpStale } from './control.js';
|
|
4
|
+
export type { CorsHeaders, ErrorRenderOptions, RenderContext } from './model.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { createHttpHandler } from './handler.js';
|
|
2
|
-
export {
|
|
3
|
-
|
|
2
|
+
export { renderHttpResult } from './render.js';
|
|
3
|
+
export { renderHttpError, renderHttpStale } from './control.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Codec } from '@astrale-os/kernel-protocol/codecs';
|
|
2
|
+
import type * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
3
|
+
import type { ActiveOperation } from '../../lifecycle/index.js';
|
|
4
|
+
/** Correlation, binding and active lifetime retained while presenting one result. */
|
|
5
|
+
export interface RenderContext {
|
|
6
|
+
readonly requestId: protocol.RequestId;
|
|
7
|
+
readonly codec: Codec;
|
|
8
|
+
readonly cors: CorsHeaders;
|
|
9
|
+
readonly invocation: protocol.InvocationId;
|
|
10
|
+
readonly active: ActiveOperation;
|
|
11
|
+
readonly maximumResponseBytes: number;
|
|
12
|
+
}
|
|
13
|
+
/** Derive response headers with the transport fields exposed to the browser. */
|
|
14
|
+
export type CorsHeaders = (exposedHeaders?: readonly string[]) => Readonly<Record<string, string>>;
|
|
15
|
+
/** Bound and negotiate an error using the admitted request presentation. */
|
|
16
|
+
export interface ErrorRenderOptions {
|
|
17
|
+
readonly status?: number;
|
|
18
|
+
readonly negotiated?: boolean;
|
|
19
|
+
readonly maximumResponseBytes?: number;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,32 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import type { Handler } from '../../invocation/index.js';
|
|
5
|
-
import type { ActiveOperation } from '../../lifecycle/index.js';
|
|
6
|
-
export interface RenderContext {
|
|
7
|
-
readonly requestId: protocol.RequestId;
|
|
8
|
-
readonly codec: Codec;
|
|
9
|
-
readonly cors: CorsHeaders;
|
|
10
|
-
readonly invocation: protocol.InvocationId;
|
|
11
|
-
readonly active: ActiveOperation;
|
|
12
|
-
readonly maximumResponseBytes: number;
|
|
13
|
-
}
|
|
14
|
-
export type CorsHeaders = (exposedHeaders?: readonly string[]) => Readonly<Record<string, string>>;
|
|
15
|
-
export interface ErrorRenderOptions {
|
|
16
|
-
readonly status?: number;
|
|
17
|
-
readonly negotiated?: boolean;
|
|
18
|
-
readonly maximumResponseBytes?: number;
|
|
19
|
-
}
|
|
20
|
-
type HandlerResult = Awaited<ReturnType<Handler['dispatch']>>;
|
|
1
|
+
import type { HandlerResult } from '../../invocation/index.js';
|
|
2
|
+
import type { RenderContext } from './model.js';
|
|
3
|
+
/** Render one admitted Handler result and retain only live producers in the active operation. */
|
|
21
4
|
export declare function renderHttpResult(result: HandlerResult, context: RenderContext): Response;
|
|
22
|
-
export declare function renderBinary(requestId: protocol.RequestId, invocation: protocol.InvocationId, binary: Extract<HandlerResult, {
|
|
23
|
-
readonly kind: 'binary';
|
|
24
|
-
}>['value']): protocol.BinaryResponse;
|
|
25
|
-
export declare function renderRedirect(requestId: protocol.RequestId, invocation: protocol.InvocationId, redirect: Extract<HandlerResult, {
|
|
26
|
-
readonly kind: 'redirect';
|
|
27
|
-
}>['redirect']): protocol.RedirectResponse;
|
|
28
|
-
export declare function renderHttpError(requestId: protocol.RequestId, invocation: protocol.InvocationId, error: errors.ErrorPayload, codec: Codec, cors: CorsHeaders, options?: ErrorRenderOptions): Response;
|
|
29
|
-
export declare function renderHttpStale(requestId: protocol.RequestId, invocation: protocol.InvocationId, current: protocol.Stale['current'], codec: Codec, cors: CorsHeaders, maximumResponseBytes?: number): Response;
|
|
30
|
-
export declare function encodeEnvelope(codec: Codec, input: unknown): string | Uint8Array;
|
|
31
|
-
export {};
|
|
32
|
-
//# sourceMappingURL=render.d.ts.map
|
|
@@ -1,289 +1,38 @@
|
|
|
1
|
-
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
2
1
|
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { isStreamingBinary, renderBinary, renderRedirect } from '../result.js';
|
|
3
|
+
import { binaryHttpResponse } from './binary.js';
|
|
4
|
+
import { streamingBinaryHttpResponse } from './byte-stream.js';
|
|
5
|
+
import { envelopeResponse } from './response.js';
|
|
6
|
+
import { streamHttpResponse } from './stream.js';
|
|
7
|
+
/** Render one admitted Handler result and retain only live producers in the active operation. */
|
|
7
8
|
export function renderHttpResult(result, context) {
|
|
9
|
+
let envelope;
|
|
8
10
|
switch (result.kind) {
|
|
9
11
|
case 'value':
|
|
10
|
-
|
|
12
|
+
envelope = {
|
|
11
13
|
result: result.value,
|
|
12
14
|
requestId: context.requestId,
|
|
13
15
|
invocation: context.invocation,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
};
|
|
17
|
+
break;
|
|
18
|
+
case 'redirect':
|
|
19
|
+
envelope = renderRedirect(context.requestId, context.invocation, result.redirect);
|
|
20
|
+
break;
|
|
19
21
|
case 'miss':
|
|
20
|
-
|
|
22
|
+
envelope = protocol.miss(context.requestId, context.invocation);
|
|
23
|
+
break;
|
|
21
24
|
case 'binary': {
|
|
25
|
+
if (isStreamingBinary(result.value))
|
|
26
|
+
return streamingBinaryHttpResponse(result.value, context);
|
|
22
27
|
const binary = renderBinary(context.requestId, context.invocation, result.value);
|
|
23
28
|
return settled(context.active, binaryHttpResponse(binary, context.cors, context.maximumResponseBytes));
|
|
24
29
|
}
|
|
25
30
|
case 'stream':
|
|
26
31
|
return streamHttpResponse(result.stream, context);
|
|
27
32
|
}
|
|
28
|
-
|
|
29
|
-
export function renderBinary(requestId, invocation, binary) {
|
|
30
|
-
const reserved = Object.keys(binary.headers ?? {}).find((name) => {
|
|
31
|
-
const normalized = name.toLowerCase();
|
|
32
|
-
return (normalized === protocol.http.INVOCATION_FIELD ||
|
|
33
|
-
normalized === HTTP_ROUTE_STALE ||
|
|
34
|
-
normalized === HTTP_ROUTE_MISS);
|
|
35
|
-
});
|
|
36
|
-
if (reserved !== undefined)
|
|
37
|
-
throw new TypeError(`${reserved} is reserved by Protocol.`);
|
|
38
|
-
return protocol.decodeBinaryResponse({
|
|
39
|
-
kind: 'binary',
|
|
40
|
-
requestId,
|
|
41
|
-
invocation,
|
|
42
|
-
body: binary.body,
|
|
43
|
-
mediaType: binary.mediaType,
|
|
44
|
-
...(binary.headers === undefined ? {} : { headers: binary.headers }),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
export function renderRedirect(requestId, invocation, redirect) {
|
|
48
|
-
return protocol.decodeRedirectResponse({
|
|
49
|
-
kind: 'redirect',
|
|
50
|
-
requestId,
|
|
51
|
-
invocation,
|
|
52
|
-
redirect,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
export function renderHttpError(requestId, invocation, error, codec, cors, options = {}) {
|
|
56
|
-
const status = options.status ?? errors.httpStatusOf(error);
|
|
57
|
-
const extra = status === 401 ? { 'www-authenticate': 'Bearer realm="astrale-invocation"' } : {};
|
|
58
|
-
return errorEnvelopeResponse(codec, { error, requestId, invocation }, { ...cors(), ...extra }, status, options.negotiated ?? true, options.maximumResponseBytes);
|
|
59
|
-
}
|
|
60
|
-
export function renderHttpStale(requestId, invocation, current, codec, cors, maximumResponseBytes) {
|
|
61
|
-
return envelopeResponse(codec, protocol.stale(requestId, invocation, current), cors(), 200, true, maximumResponseBytes);
|
|
62
|
-
}
|
|
63
|
-
export function encodeEnvelope(codec, input) {
|
|
64
|
-
return codec.encode(input);
|
|
65
|
-
}
|
|
66
|
-
function envelopeResponse(codec, input, cors, status = 200, negotiated = true, maximumResponseBytes) {
|
|
67
|
-
const encoded = codec.encode(input);
|
|
68
|
-
requireResponseBound(encoded, maximumResponseBytes);
|
|
69
|
-
const headers = bindingHeaders(cors, negotiated);
|
|
70
|
-
headers.set('content-type', codec.contentType);
|
|
71
|
-
return new Response(toBody(encoded), {
|
|
72
|
-
status,
|
|
73
|
-
headers,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
function binaryHttpResponse(binary, cors, maximumResponseBytes) {
|
|
77
|
-
requireResponseBound(binary.body, maximumResponseBytes);
|
|
78
|
-
const applicationHeaders = Object.keys(binary.headers ?? {}).filter((name) => protocol.http.isApplicationBinaryHeader(name));
|
|
79
|
-
const headers = new Headers(cors([
|
|
80
|
-
protocol.http.REQUEST_ID_FIELD,
|
|
81
|
-
protocol.http.INVOCATION_FIELD,
|
|
82
|
-
protocol.http.BINARY_HEADERS_FIELD,
|
|
83
|
-
...applicationHeaders,
|
|
84
|
-
]));
|
|
85
|
-
for (const [name, value] of Object.entries(binary.headers ?? {})) {
|
|
86
|
-
if (protocol.http.isApplicationBinaryHeader(name))
|
|
87
|
-
headers.set(name, value);
|
|
88
|
-
}
|
|
89
|
-
headers.set('content-type', binary.mediaType);
|
|
90
|
-
headers.set(protocol.http.BINARY_HEADERS_FIELD, protocol.http.encodeBinaryHeaderNames(binary.headers));
|
|
91
|
-
headers.set(protocol.http.REQUEST_ID_FIELD, String(binary.requestId));
|
|
92
|
-
headers.set(protocol.http.INVOCATION_FIELD, protocol.http.encodeInvocationId(binary.invocation));
|
|
93
|
-
headers.set('cache-control', 'no-store');
|
|
94
|
-
appendVary(headers, 'accept');
|
|
95
|
-
return new Response(toBody(binary.body), { status: 200, headers });
|
|
96
|
-
}
|
|
97
|
-
function streamHttpResponse(stream, context) {
|
|
98
|
-
const iterator = stream[Symbol.asyncIterator]();
|
|
99
|
-
const startFrame = encodeBoundedStreamFrame(context.codec, { kind: 'stream.start', requestId: context.requestId, invocation: context.invocation }, context.maximumResponseBytes);
|
|
100
|
-
let count = 0;
|
|
101
|
-
let terminal = false;
|
|
102
|
-
let controller;
|
|
103
|
-
let stopping;
|
|
104
|
-
const stop = (reason) => {
|
|
105
|
-
stopping ??= stopStream(reason);
|
|
106
|
-
return stopping;
|
|
107
|
-
};
|
|
108
|
-
async function stopStream(reason) {
|
|
109
|
-
if (terminal)
|
|
110
|
-
return;
|
|
111
|
-
terminal = true;
|
|
112
|
-
// End the wire response immediately. Producer cleanup remains part of the active operation,
|
|
113
|
-
// but an uncooperative producer must not leave the client waiting for a terminal frame.
|
|
114
|
-
if (controller !== undefined) {
|
|
115
|
-
try {
|
|
116
|
-
controller.enqueue(encodeBoundedStreamFrame(context.codec, cancellationFrame(context), context.maximumResponseBytes));
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
// The transport consumer may already have disconnected.
|
|
120
|
-
}
|
|
121
|
-
try {
|
|
122
|
-
controller.close();
|
|
123
|
-
}
|
|
124
|
-
catch {
|
|
125
|
-
// The stream may already be closed or cancelled by the consumer.
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
await Promise.allSettled([
|
|
130
|
-
Promise.resolve().then(() => stream.cancel(reason)),
|
|
131
|
-
Promise.resolve().then(() => iterator.return?.()),
|
|
132
|
-
]);
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
context.active.settle();
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
const body = new ReadableStream({
|
|
139
|
-
start(output) {
|
|
140
|
-
controller = output;
|
|
141
|
-
output.enqueue(startFrame);
|
|
142
|
-
},
|
|
143
|
-
async pull(output) {
|
|
144
|
-
if (terminal)
|
|
145
|
-
return;
|
|
146
|
-
try {
|
|
147
|
-
const next = await iterator.next();
|
|
148
|
-
if (terminal)
|
|
149
|
-
return;
|
|
150
|
-
if (next.done) {
|
|
151
|
-
terminal = true;
|
|
152
|
-
output.enqueue(encodeBoundedStreamFrame(context.codec, {
|
|
153
|
-
kind: 'stream.end',
|
|
154
|
-
requestId: context.requestId,
|
|
155
|
-
count,
|
|
156
|
-
}, context.maximumResponseBytes));
|
|
157
|
-
output.close();
|
|
158
|
-
context.active.settle();
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const frame = protocol.decodeStreamFrame({
|
|
162
|
-
kind: 'stream.item',
|
|
163
|
-
requestId: context.requestId,
|
|
164
|
-
index: count,
|
|
165
|
-
value: next.value,
|
|
166
|
-
});
|
|
167
|
-
output.enqueue(encodeBoundedStreamFrame(context.codec, frame, context.maximumResponseBytes));
|
|
168
|
-
count += 1;
|
|
169
|
-
}
|
|
170
|
-
catch (input) {
|
|
171
|
-
if (terminal)
|
|
172
|
-
return;
|
|
173
|
-
terminal = true;
|
|
174
|
-
if (!context.active.signal.aborted) {
|
|
175
|
-
try {
|
|
176
|
-
output.enqueue(encodeBoundedStreamFrame(context.codec, {
|
|
177
|
-
kind: 'stream.error',
|
|
178
|
-
requestId: context.requestId,
|
|
179
|
-
error: classifyError(input),
|
|
180
|
-
}, context.maximumResponseBytes));
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
// No valid terminal frame can be formed; closing is the only remaining signal.
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
output.close();
|
|
187
|
-
await Promise.allSettled([
|
|
188
|
-
Promise.resolve().then(() => stream.cancel('stream failed')),
|
|
189
|
-
Promise.resolve().then(() => iterator.return?.()),
|
|
190
|
-
]);
|
|
191
|
-
context.active.settle();
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
async cancel(reason) {
|
|
195
|
-
const message = typeof reason === 'string' ? reason : 'response consumer cancelled';
|
|
196
|
-
context.active.cancel(message);
|
|
197
|
-
await stop(message);
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
-
context.active.onCancel(stop);
|
|
201
|
-
return new Response(body, {
|
|
202
|
-
status: 200,
|
|
203
|
-
headers: {
|
|
204
|
-
...context.cors([protocol.http.STREAM_FRAMING_FIELD]),
|
|
205
|
-
'cache-control': 'no-store',
|
|
206
|
-
'content-type': context.codec.contentType,
|
|
207
|
-
vary: mergeVary(context.cors(), 'accept'),
|
|
208
|
-
[protocol.http.STREAM_FRAMING_FIELD]: protocol.http.LENGTH_PREFIX_V1,
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
function bindingHeaders(input, negotiated) {
|
|
213
|
-
const headers = new Headers(input);
|
|
214
|
-
headers.set('cache-control', 'no-store');
|
|
215
|
-
if (negotiated)
|
|
216
|
-
appendVary(headers, 'accept');
|
|
217
|
-
return headers;
|
|
218
|
-
}
|
|
219
|
-
function errorEnvelopeResponse(codec, response, cors, status, negotiated, maximumResponseBytes) {
|
|
220
|
-
try {
|
|
221
|
-
return envelopeResponse(codec, response, cors, status, negotiated, maximumResponseBytes);
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
if (!(error instanceof ResponseBodyTooLargeError))
|
|
225
|
-
throw error;
|
|
226
|
-
const fallback = {
|
|
227
|
-
requestId: response.requestId,
|
|
228
|
-
invocation: response.invocation,
|
|
229
|
-
error: {
|
|
230
|
-
code: errors.CODES.LIMIT_EXCEEDED,
|
|
231
|
-
message: 'Response body exceeds the configured limit.',
|
|
232
|
-
},
|
|
233
|
-
};
|
|
234
|
-
try {
|
|
235
|
-
return envelopeResponse(codec, fallback, cors, errors.httpStatusOf(fallback.error), negotiated, maximumResponseBytes);
|
|
236
|
-
}
|
|
237
|
-
catch (fallbackError) {
|
|
238
|
-
if (!(fallbackError instanceof ResponseBodyTooLargeError))
|
|
239
|
-
throw fallbackError;
|
|
240
|
-
return new Response(null, {
|
|
241
|
-
status: 500,
|
|
242
|
-
headers: bindingHeaders(cors, negotiated),
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
function encodeBoundedStreamFrame(codec, frame, maximumResponseBytes) {
|
|
248
|
-
const encoded = protocol.http.encodeStreamFrame(codec, frame);
|
|
249
|
-
if (encoded.byteLength - 4 > maximumResponseBytes) {
|
|
250
|
-
throw new ResponseBodyTooLargeError(maximumResponseBytes);
|
|
251
|
-
}
|
|
252
|
-
return encoded;
|
|
253
|
-
}
|
|
254
|
-
function requireResponseBound(input, maximumResponseBytes) {
|
|
255
|
-
if (maximumResponseBytes === undefined)
|
|
256
|
-
return;
|
|
257
|
-
const bytes = typeof input === 'string' ? new TextEncoder().encode(input).byteLength : input.byteLength;
|
|
258
|
-
if (bytes > maximumResponseBytes)
|
|
259
|
-
throw new ResponseBodyTooLargeError(maximumResponseBytes);
|
|
33
|
+
return settled(context.active, envelopeResponse(context.codec, envelope, context.cors(), 200, true, context.maximumResponseBytes));
|
|
260
34
|
}
|
|
261
35
|
function settled(operation, response) {
|
|
262
36
|
operation.settle();
|
|
263
37
|
return response;
|
|
264
38
|
}
|
|
265
|
-
function appendVary(headers, value) {
|
|
266
|
-
headers.set('vary', mergeVary(Object.fromEntries(headers), value));
|
|
267
|
-
}
|
|
268
|
-
function mergeVary(headers, value) {
|
|
269
|
-
const names = new Set((headers.vary ?? '')
|
|
270
|
-
.split(',')
|
|
271
|
-
.map((name) => name.trim().toLowerCase())
|
|
272
|
-
.filter(Boolean));
|
|
273
|
-
names.add(value.toLowerCase());
|
|
274
|
-
return [...names].join(', ');
|
|
275
|
-
}
|
|
276
|
-
function cancellationFrame(context) {
|
|
277
|
-
return {
|
|
278
|
-
kind: 'stream.error',
|
|
279
|
-
requestId: context.requestId,
|
|
280
|
-
error: interruptedStreamError(context.active.timedOut),
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
function toBody(input) {
|
|
284
|
-
if (typeof input === 'string')
|
|
285
|
-
return input;
|
|
286
|
-
const copy = new Uint8Array(input);
|
|
287
|
-
return copy.buffer;
|
|
288
|
-
}
|
|
289
|
-
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Codec } from '@astrale-os/kernel-protocol/codecs';
|
|
2
|
+
import type * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
3
|
+
/** Encode a bounded envelope with its binding content type and cache policy. */
|
|
4
|
+
export declare function envelopeResponse(codec: Codec, input: unknown, cors: Readonly<Record<string, string>>, status?: number, negotiated?: boolean, maximumResponseBytes?: number): Response;
|
|
5
|
+
/** Bound an error, falling back to a correlated limit refusal or an empty response. */
|
|
6
|
+
export declare function errorEnvelopeResponse(codec: Codec, response: protocol.ErrorResponse, cors: Readonly<Record<string, string>>, status: number, negotiated: boolean, maximumResponseBytes: number | undefined): Response;
|
|
7
|
+
/** Enforce the configured byte budget before a buffered response is handed off. */
|
|
8
|
+
export declare function requireResponseBound(input: string | Uint8Array, maximumResponseBytes: number | undefined): void;
|
|
9
|
+
/** Retain existing Vary fields while declaring another response selector. */
|
|
10
|
+
export declare function appendVary(headers: Headers, value: string): void;
|
|
11
|
+
/** Merge one case-insensitive selector into the response Vary field. */
|
|
12
|
+
export declare function mergeVary(headers: Readonly<Record<string, string>>, value: string): string;
|
|
13
|
+
/** Detach encoded bytes for Fetch while retaining encoded text as text. */
|
|
14
|
+
export declare function toBody(input: string | Uint8Array): BodyInit;
|