@astrale-os/kernel-server 0.5.0-beta.7 → 0.5.0-beta.71
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 +8 -4
- package/dist/index.js +7 -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/legacy/index.d.ts +20 -0
- package/dist/invocation/legacy/index.js +20 -0
- package/dist/invocation/{publication-route.d.ts → legacy/publication-route.d.ts} +7 -5
- package/dist/invocation/{publication-route.js → legacy/publication-route.js} +7 -13
- package/dist/invocation/release-route.d.ts +26 -0
- package/dist/invocation/release-route.js +55 -0
- package/dist/invocation/types.d.ts +20 -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 +33 -5
- 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 +13 -0
- package/dist/{publication → release}/delivery/handler.js +42 -15
- package/dist/{publication → release}/delivery/index.d.ts +0 -1
- package/dist/{publication → release}/delivery/index.js +0 -1
- package/dist/release/delivery/types.d.ts +21 -0
- 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/legacy/delivery.d.ts +18 -0
- package/dist/release/legacy/delivery.js +2 -0
- package/dist/release/legacy/index.d.ts +39 -0
- package/dist/release/legacy/index.js +38 -0
- package/dist/release/legacy/snapshot.d.ts +26 -0
- package/dist/release/legacy/snapshot.js +55 -0
- package/dist/release/legacy/table.d.ts +5 -0
- package/dist/{publication → release/legacy}/table.js +5 -6
- package/dist/release/legacy/types.d.ts +31 -0
- package/dist/release/legacy/types.js +1 -0
- package/dist/release/served.d.ts +30 -0
- package/dist/release/served.js +14 -0
- package/dist/release/snapshot.d.ts +26 -0
- package/dist/release/snapshot.js +54 -0
- package/dist/release/table.d.ts +4 -0
- package/dist/release/table.js +11 -0
- package/dist/release/types.d.ts +33 -0
- package/dist/release/types.js +1 -0
- package/dist/routes/index.d.ts +1 -2
- package/dist/routes/index.js +1 -2
- package/dist/routes/routes.d.ts +9 -7
- package/dist/routes/routes.js +314 -123
- package/dist/server.d.ts +0 -1
- package/dist/server.js +110 -48
- 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/handler.d.ts +8 -3
- package/dist/transport/http/handler.js +95 -45
- package/dist/transport/http/index.d.ts +0 -1
- package/dist/transport/http/index.js +0 -1
- package/dist/transport/http/render.d.ts +9 -4
- package/dist/transport/http/render.js +150 -5
- package/dist/transport/index.d.ts +0 -1
- package/dist/transport/index.js +0 -1
- 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 +32 -6
- package/dist/types.js +0 -1
- package/package.json +12 -15
- 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.map +0 -1
- 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.map +0 -1
- package/dist/publication/delivery/index.js.map +0 -1
- package/dist/publication/delivery/types.d.ts +0 -12
- 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.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,12 +1,14 @@
|
|
|
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 { binaryRequestBody } from './binary-request.js';
|
|
11
|
+
import { isStreamingBinary, renderDeploymentRetired, renderHttpError, renderHttpResult, } from './render.js';
|
|
10
12
|
export function createHttpHandler(config) {
|
|
11
13
|
return async (request, host) => {
|
|
12
14
|
const cors = (exposedHeaders) => config.cors.response(request, exposedHeaders);
|
|
@@ -21,6 +23,8 @@ export function createHttpHandler(config) {
|
|
|
21
23
|
headers: [
|
|
22
24
|
'authorization',
|
|
23
25
|
'content-type',
|
|
26
|
+
protocol.http.REQUEST_ENVELOPE_FIELD,
|
|
27
|
+
protocol.http.BINARY_LENGTH_FIELD,
|
|
24
28
|
protocol.http.TRACEPARENT_FIELD,
|
|
25
29
|
protocol.http.TRACESTATE_FIELD,
|
|
26
30
|
],
|
|
@@ -37,24 +41,34 @@ export function createHttpHandler(config) {
|
|
|
37
41
|
...options,
|
|
38
42
|
maximumResponseBytes: config.maximumResponseBytes,
|
|
39
43
|
});
|
|
40
|
-
const
|
|
44
|
+
const binaryRequest = request.headers.get('content-type')?.trim().toLowerCase() ===
|
|
45
|
+
protocol.http.BINARY_REQUEST_CONTENT_TYPE;
|
|
46
|
+
const active = config.active.begin(binaryRequest ? config.binaryRequestTimeoutMs : config.invocationTimeoutMs, request.signal);
|
|
41
47
|
if (active === undefined) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
if (binaryRequest && request.body !== null && !request.body.locked) {
|
|
49
|
+
void request.body.cancel('binary request rejected').catch(() => undefined);
|
|
50
|
+
}
|
|
51
|
+
return renderError(null, admissionRefusal(config.active.isAccepting), codecs.json, {
|
|
52
|
+
status: 503,
|
|
53
|
+
negotiated: false,
|
|
54
|
+
});
|
|
46
55
|
}
|
|
47
56
|
let handedOff = false;
|
|
57
|
+
let binary;
|
|
48
58
|
try {
|
|
49
|
-
const supported =
|
|
59
|
+
const supported = loadCodecs();
|
|
50
60
|
const contentType = request.headers.get('content-type');
|
|
51
|
-
|
|
61
|
+
// No default representation: a body without a declared Astrale type is never decoded, so a
|
|
62
|
+
// browser cannot send an admissible invocation from another origin without a CORS preflight.
|
|
63
|
+
const incoming = binaryRequest
|
|
52
64
|
? codecs.json
|
|
53
65
|
: protocol.http.codecForContentType(contentType, supported);
|
|
54
66
|
if (incoming === undefined) {
|
|
55
67
|
return renderError(null, {
|
|
56
68
|
code: errors.CODES.UNSUPPORTED,
|
|
57
|
-
message:
|
|
69
|
+
message: contentType === null || contentType.trim() === ''
|
|
70
|
+
? 'Missing request Content-Type.'
|
|
71
|
+
: 'Unsupported request Content-Type.',
|
|
58
72
|
}, codecs.json, { status: 415, negotiated: false });
|
|
59
73
|
}
|
|
60
74
|
const outgoing = protocol.http.negotiateResponseCodec(request.headers.get('accept'), incoming, supported);
|
|
@@ -66,7 +80,15 @@ export function createHttpHandler(config) {
|
|
|
66
80
|
}
|
|
67
81
|
let body;
|
|
68
82
|
try {
|
|
69
|
-
|
|
83
|
+
if (binaryRequest)
|
|
84
|
+
binary = binaryRequestBody(request, config.maximumBinaryRequestBytes, active.signal, config.binaryRequestIdleTimeoutMs === undefined
|
|
85
|
+
? undefined
|
|
86
|
+
: {
|
|
87
|
+
timeoutMs: config.binaryRequestIdleTimeoutMs,
|
|
88
|
+
cancel: () => active.cancel('binary request idle timeout exceeded'),
|
|
89
|
+
});
|
|
90
|
+
else
|
|
91
|
+
body = await readBoundedBody(request, config.maximumRequestBytes, active.signal);
|
|
70
92
|
}
|
|
71
93
|
catch (input) {
|
|
72
94
|
const payload = input instanceof RequestBodyTooLargeError
|
|
@@ -87,37 +109,54 @@ export function createHttpHandler(config) {
|
|
|
87
109
|
status: input instanceof RequestBodyTooLargeError ? 413 : errors.httpStatusOf(payload),
|
|
88
110
|
});
|
|
89
111
|
}
|
|
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
112
|
let envelope;
|
|
100
113
|
try {
|
|
101
|
-
|
|
114
|
+
if (binaryRequest) {
|
|
115
|
+
envelope = protocol.http.decodeBinaryRequestEnvelope(request.headers.get(protocol.http.REQUEST_ENVELOPE_FIELD));
|
|
116
|
+
if (envelope.idempotencyKey !== undefined) {
|
|
117
|
+
return renderError(envelope.requestId, {
|
|
118
|
+
code: errors.CODES.INVALID_REQUEST,
|
|
119
|
+
message: 'Binary requests forbid idempotencyKey.',
|
|
120
|
+
}, outgoing);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
let decoded;
|
|
125
|
+
try {
|
|
126
|
+
decoded = incoming.decode(body);
|
|
127
|
+
}
|
|
128
|
+
catch (input) {
|
|
129
|
+
if (!(input instanceof codecs.DecodeError))
|
|
130
|
+
throw input;
|
|
131
|
+
return renderError(null, { code: errors.CODES.PARSE_ERROR, message: 'Request body is invalid.' }, outgoing);
|
|
132
|
+
}
|
|
133
|
+
envelope = protocol.decodeRequest(decoded);
|
|
134
|
+
}
|
|
102
135
|
}
|
|
103
136
|
catch (input) {
|
|
104
137
|
return renderError(null, classifyError(input), outgoing);
|
|
105
138
|
}
|
|
106
|
-
|
|
107
|
-
|
|
139
|
+
if (!binaryRequest &&
|
|
140
|
+
config.binaryDownloadTarget !== undefined &&
|
|
141
|
+
protocol.targetPath(envelope.call.target) === config.binaryDownloadTarget) {
|
|
142
|
+
active.extend(config.binaryDownloadTimeoutMs);
|
|
143
|
+
}
|
|
144
|
+
if (active.signal.aborted) {
|
|
145
|
+
return renderError(envelope.requestId, classifyRequestError(active.signal.reason, active), outgoing);
|
|
146
|
+
}
|
|
147
|
+
const serving = config.serving?.(request);
|
|
148
|
+
const served = serving?.current();
|
|
149
|
+
if (serving !== undefined && served === undefined) {
|
|
108
150
|
return renderError(envelope.requestId, {
|
|
109
151
|
code: errors.CODES.BACKEND_UNAVAILABLE,
|
|
110
|
-
message: 'Publication is unavailable
|
|
152
|
+
message: `${serving.kind === 'release' ? 'Release' : 'Publication'} is unavailable.`,
|
|
111
153
|
}, outgoing, { status: 503 });
|
|
112
154
|
}
|
|
113
155
|
if (envelope.routing?.kind === 'via') {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
message: 'Publication routing requires a Publication-backed destination.',
|
|
118
|
-
}, outgoing);
|
|
156
|
+
const decision = admitVia(served, envelope.routing);
|
|
157
|
+
if (decision.kind === 'retired') {
|
|
158
|
+
return renderDeploymentRetired(serving.deployment(), decision.release, cors);
|
|
119
159
|
}
|
|
120
|
-
const decision = publicationRouteDecision(publication.publication, envelope.routing.publication);
|
|
121
160
|
if (decision.kind === 'rejected') {
|
|
122
161
|
return renderError(envelope.requestId, decision.error, outgoing);
|
|
123
162
|
}
|
|
@@ -135,9 +174,20 @@ export function createHttpHandler(config) {
|
|
|
135
174
|
...(envelope.delegate === undefined ? {} : { delegate: envelope.delegate }),
|
|
136
175
|
...(envelope.routing?.kind === 'via' ? { via: envelope.routing } : {}),
|
|
137
176
|
...(envelope.schema === undefined ? {} : { schema: envelope.schema }),
|
|
138
|
-
...(
|
|
139
|
-
context: invocationContext(assigned, active, config.active, host, undefined, envelope.idempotencyKey),
|
|
177
|
+
...(served === undefined ? {} : pinned(served)),
|
|
178
|
+
context: invocationContext(assigned, active, config.active, host, undefined, envelope.idempotencyKey, binary),
|
|
140
179
|
}));
|
|
180
|
+
if (binary !== undefined && result.kind !== 'redirect' && result.kind !== 'miss') {
|
|
181
|
+
if (!binary.completed()) {
|
|
182
|
+
if (result.kind === 'stream') {
|
|
183
|
+
await result.stream.cancel('Binary request body was not fully consumed.');
|
|
184
|
+
}
|
|
185
|
+
else if (result.kind === 'binary' && isStreamingBinary(result.value)) {
|
|
186
|
+
await result.value.body[Symbol.asyncIterator]().return?.();
|
|
187
|
+
}
|
|
188
|
+
throw new Error('Binary Function returned before consuming its request body.');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
141
191
|
const response = renderHttpResult(result, {
|
|
142
192
|
requestId: envelope.requestId,
|
|
143
193
|
codec: outgoing,
|
|
@@ -146,20 +196,12 @@ export function createHttpHandler(config) {
|
|
|
146
196
|
active,
|
|
147
197
|
maximumResponseBytes: config.maximumResponseBytes,
|
|
148
198
|
});
|
|
149
|
-
handedOff =
|
|
199
|
+
handedOff =
|
|
200
|
+
result.kind === 'stream' || (result.kind === 'binary' && isStreamingBinary(result.value));
|
|
150
201
|
return response;
|
|
151
202
|
}
|
|
152
203
|
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);
|
|
204
|
+
return renderError(envelope.requestId, classifyRequestError(input, active), outgoing);
|
|
163
205
|
}
|
|
164
206
|
}
|
|
165
207
|
catch (input) {
|
|
@@ -168,6 +210,15 @@ export function createHttpHandler(config) {
|
|
|
168
210
|
});
|
|
169
211
|
}
|
|
170
212
|
finally {
|
|
213
|
+
if (binary !== undefined) {
|
|
214
|
+
binary.cancel('binary invocation completed');
|
|
215
|
+
}
|
|
216
|
+
else if (binaryRequest &&
|
|
217
|
+
binary === undefined &&
|
|
218
|
+
request.body !== null &&
|
|
219
|
+
!request.body.locked) {
|
|
220
|
+
void request.body.cancel('binary request rejected').catch(() => undefined);
|
|
221
|
+
}
|
|
171
222
|
if (!handedOff)
|
|
172
223
|
active.settle();
|
|
173
224
|
}
|
|
@@ -178,4 +229,3 @@ function noStore(response) {
|
|
|
178
229
|
headers.set('cache-control', 'no-store');
|
|
179
230
|
return new Response(response.body, { status: response.status, headers });
|
|
180
231
|
}
|
|
181
|
-
//# sourceMappingURL=handler.js.map
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
export { createHttpHandler, type HttpHandlerConfig } from './handler.js';
|
|
2
2
|
export { encodeEnvelope, renderBinary, renderHttpError, renderHttpResult, renderHttpStale, renderRedirect, type CorsHeaders, type ErrorRenderOptions, type RenderContext, } from './render.js';
|
|
3
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
|
+
import type { BufferedBinaryResult, StreamingBinaryResult } from '@astrale-os/kernel-core/callable';
|
|
1
3
|
import type { Codec } from '@astrale-os/kernel-protocol/codecs';
|
|
4
|
+
import type { ReleaseDigest } from '@astrale-os/kernel-protocol/release';
|
|
2
5
|
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
3
6
|
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
4
7
|
import type { Handler } from '../../invocation/index.js';
|
|
@@ -19,14 +22,16 @@ export interface ErrorRenderOptions {
|
|
|
19
22
|
}
|
|
20
23
|
type HandlerResult = Awaited<ReturnType<Handler['dispatch']>>;
|
|
21
24
|
export declare function renderHttpResult(result: HandlerResult, context: RenderContext): Response;
|
|
22
|
-
export declare function renderBinary(requestId: protocol.RequestId, invocation: protocol.InvocationId, binary:
|
|
23
|
-
readonly kind: 'binary';
|
|
24
|
-
}>['value']): protocol.BinaryResponse;
|
|
25
|
+
export declare function renderBinary(requestId: protocol.RequestId, invocation: protocol.InvocationId, binary: BufferedBinaryResult): protocol.BinaryResponse;
|
|
25
26
|
export declare function renderRedirect(requestId: protocol.RequestId, invocation: protocol.InvocationId, redirect: Extract<HandlerResult, {
|
|
26
27
|
readonly kind: 'redirect';
|
|
27
28
|
}>['redirect']): protocol.RedirectResponse;
|
|
28
29
|
export declare function renderHttpError(requestId: protocol.RequestId, invocation: protocol.InvocationId, error: errors.ErrorPayload, codec: Codec, cors: CorsHeaders, options?: ErrorRenderOptions): Response;
|
|
29
30
|
export declare function renderHttpStale(requestId: protocol.RequestId, invocation: protocol.InvocationId, current: protocol.Stale['current'], codec: Codec, cors: CorsHeaders, maximumResponseBytes?: number): Response;
|
|
31
|
+
/** Retirement controls always use JSON, regardless of the negotiated request codec. */
|
|
32
|
+
export declare function renderDeploymentRetired(deployment: IssuerId, release: ReleaseDigest | undefined, cors: CorsHeaders): Response;
|
|
30
33
|
export declare function encodeEnvelope(codec: Codec, input: unknown): string | Uint8Array;
|
|
34
|
+
export declare function isStreamingBinary(input: Extract<HandlerResult, {
|
|
35
|
+
readonly kind: 'binary';
|
|
36
|
+
}>['value']): input is StreamingBinaryResult;
|
|
31
37
|
export {};
|
|
32
|
-
//# sourceMappingURL=render.d.ts.map
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { json } from '@astrale-os/kernel-protocol/codecs';
|
|
1
2
|
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
2
3
|
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
4
|
+
import { deploymentRetiredBody, deploymentRetiredHeaders, HTTP_DEPLOYMENT_RETIRED, HTTP_DEPLOYMENT_RETIRED_STATUS, } from '@astrale-os/kernel-protocol/routes/deployment-retired';
|
|
3
5
|
import { HTTP_ROUTE_MISS, HTTP_ROUTE_STALE } from '@astrale-os/kernel-protocol/routes/routing';
|
|
4
|
-
import { classifyError } from '../../errors/index.js';
|
|
6
|
+
import { classifyError, retryAfterHeaders } from '../../errors/index.js';
|
|
5
7
|
import { ResponseBodyTooLargeError } from '../../errors/index.js';
|
|
6
8
|
import { interruptedStreamError } from '../stream-error.js';
|
|
7
9
|
export function renderHttpResult(result, context) {
|
|
@@ -19,6 +21,8 @@ export function renderHttpResult(result, context) {
|
|
|
19
21
|
case 'miss':
|
|
20
22
|
return settled(context.active, envelopeResponse(context.codec, protocol.miss(context.requestId, context.invocation), context.cors(), 200, true, context.maximumResponseBytes));
|
|
21
23
|
case 'binary': {
|
|
24
|
+
if (isStreamingBinary(result.value))
|
|
25
|
+
return streamingBinaryHttpResponse(result.value, context);
|
|
22
26
|
const binary = renderBinary(context.requestId, context.invocation, result.value);
|
|
23
27
|
return settled(context.active, binaryHttpResponse(binary, context.cors, context.maximumResponseBytes));
|
|
24
28
|
}
|
|
@@ -41,6 +45,7 @@ export function renderBinary(requestId, invocation, binary) {
|
|
|
41
45
|
invocation,
|
|
42
46
|
body: binary.body,
|
|
43
47
|
mediaType: binary.mediaType,
|
|
48
|
+
status: binary.status ?? 200,
|
|
44
49
|
...(binary.headers === undefined ? {} : { headers: binary.headers }),
|
|
45
50
|
});
|
|
46
51
|
}
|
|
@@ -54,12 +59,20 @@ export function renderRedirect(requestId, invocation, redirect) {
|
|
|
54
59
|
}
|
|
55
60
|
export function renderHttpError(requestId, invocation, error, codec, cors, options = {}) {
|
|
56
61
|
const status = options.status ?? errors.httpStatusOf(error);
|
|
57
|
-
const
|
|
58
|
-
|
|
62
|
+
const retry = retryAfterHeaders(error);
|
|
63
|
+
const extra = status === 401 ? { 'www-authenticate': 'Bearer realm="astrale-invocation"' } : retry;
|
|
64
|
+
return errorEnvelopeResponse(codec, { error, requestId, invocation }, { ...cors(Object.keys(retry)), ...extra }, status, options.negotiated ?? true, options.maximumResponseBytes);
|
|
59
65
|
}
|
|
60
66
|
export function renderHttpStale(requestId, invocation, current, codec, cors, maximumResponseBytes) {
|
|
61
67
|
return envelopeResponse(codec, protocol.stale(requestId, invocation, current), cors(), 200, true, maximumResponseBytes);
|
|
62
68
|
}
|
|
69
|
+
/** Retirement controls always use JSON, regardless of the negotiated request codec. */
|
|
70
|
+
export function renderDeploymentRetired(deployment, release, cors) {
|
|
71
|
+
return new Response(toBody(json.encode(deploymentRetiredBody(deployment, release))), {
|
|
72
|
+
status: HTTP_DEPLOYMENT_RETIRED_STATUS,
|
|
73
|
+
headers: { ...cors([HTTP_DEPLOYMENT_RETIRED]), ...deploymentRetiredHeaders() },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
63
76
|
export function encodeEnvelope(codec, input) {
|
|
64
77
|
return codec.encode(input);
|
|
65
78
|
}
|
|
@@ -75,11 +88,141 @@ function envelopeResponse(codec, input, cors, status = 200, negotiated = true, m
|
|
|
75
88
|
}
|
|
76
89
|
function binaryHttpResponse(binary, cors, maximumResponseBytes) {
|
|
77
90
|
requireResponseBound(binary.body, maximumResponseBytes);
|
|
91
|
+
const headers = binaryHttpHeaders(binary, cors, false);
|
|
92
|
+
return new Response(toBody(binary.body), { status: binary.status, headers });
|
|
93
|
+
}
|
|
94
|
+
function streamingBinaryHttpResponse(binary, context) {
|
|
95
|
+
if (binary.length !== undefined && (!Number.isSafeInteger(binary.length) || binary.length < 0)) {
|
|
96
|
+
throw new TypeError('Streaming binary length must be a non-negative safe integer.');
|
|
97
|
+
}
|
|
98
|
+
const delivery = binary.delivery;
|
|
99
|
+
if (delivery !== undefined) {
|
|
100
|
+
if (!Number.isSafeInteger(delivery.maximumDurationMs) ||
|
|
101
|
+
delivery.maximumDurationMs <= 0 ||
|
|
102
|
+
delivery.maximumDurationMs > 2_147_483_647 ||
|
|
103
|
+
!Number.isSafeInteger(delivery.idleTimeoutMs) ||
|
|
104
|
+
delivery.idleTimeoutMs <= 0 ||
|
|
105
|
+
delivery.idleTimeoutMs > 2_147_483_647) {
|
|
106
|
+
throw new TypeError('Streaming binary delivery budget is invalid.');
|
|
107
|
+
}
|
|
108
|
+
context.active.extend(delivery.maximumDurationMs);
|
|
109
|
+
}
|
|
110
|
+
const iterator = binary.body[Symbol.asyncIterator]();
|
|
111
|
+
const headers = binaryHttpHeaders(Object.freeze({
|
|
112
|
+
requestId: context.requestId,
|
|
113
|
+
invocation: context.invocation,
|
|
114
|
+
mediaType: binary.mediaType,
|
|
115
|
+
headers: binary.headers,
|
|
116
|
+
}), context.cors, true);
|
|
117
|
+
if (binary.length !== undefined) {
|
|
118
|
+
headers.set('content-length', String(binary.length));
|
|
119
|
+
// Content-Length belongs to HTTP and an intermediary may rewrite it; the Client counts the
|
|
120
|
+
// delivered bytes against this protocol field instead.
|
|
121
|
+
headers.set(protocol.http.BINARY_LENGTH_FIELD, String(binary.length));
|
|
122
|
+
}
|
|
123
|
+
if (delivery !== undefined)
|
|
124
|
+
headers.set('cache-control', 'private, no-store');
|
|
125
|
+
let terminal = false;
|
|
126
|
+
let delivered = 0;
|
|
127
|
+
let controller;
|
|
128
|
+
let stopping;
|
|
129
|
+
let idleTimer;
|
|
130
|
+
const clearIdle = () => {
|
|
131
|
+
if (idleTimer !== undefined)
|
|
132
|
+
clearTimeout(idleTimer);
|
|
133
|
+
};
|
|
134
|
+
const resetIdle = () => {
|
|
135
|
+
if (delivery === undefined || terminal)
|
|
136
|
+
return;
|
|
137
|
+
clearIdle();
|
|
138
|
+
idleTimer = setTimeout(() => context.active.cancel('binary response idle timeout exceeded'), delivery.idleTimeoutMs);
|
|
139
|
+
};
|
|
140
|
+
const stop = (reason = 'Binary response was interrupted.') => {
|
|
141
|
+
if (terminal)
|
|
142
|
+
return stopping ?? Promise.resolve();
|
|
143
|
+
terminal = true;
|
|
144
|
+
clearIdle();
|
|
145
|
+
controller?.error(new Error(reason));
|
|
146
|
+
stopping = Promise.resolve().then(async () => {
|
|
147
|
+
try {
|
|
148
|
+
await iterator.return?.();
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
context.active.settle();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return stopping;
|
|
155
|
+
};
|
|
156
|
+
const fail = (cause) => {
|
|
157
|
+
if (terminal)
|
|
158
|
+
return;
|
|
159
|
+
terminal = true;
|
|
160
|
+
clearIdle();
|
|
161
|
+
controller?.error(cause);
|
|
162
|
+
stopping = Promise.resolve()
|
|
163
|
+
.then(() => iterator.return?.())
|
|
164
|
+
.catch(() => undefined)
|
|
165
|
+
.then(() => context.active.settle());
|
|
166
|
+
};
|
|
167
|
+
const body = new ReadableStream({
|
|
168
|
+
start(output) {
|
|
169
|
+
controller = output;
|
|
170
|
+
resetIdle();
|
|
171
|
+
},
|
|
172
|
+
async pull(controller) {
|
|
173
|
+
if (terminal)
|
|
174
|
+
return;
|
|
175
|
+
try {
|
|
176
|
+
const next = await iterator.next();
|
|
177
|
+
if (terminal)
|
|
178
|
+
return;
|
|
179
|
+
if (next.done) {
|
|
180
|
+
if (binary.length !== undefined && delivered !== binary.length) {
|
|
181
|
+
fail(new Error('OUTCOME_UNKNOWN: streaming binary ended before its declared length.'));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
terminal = true;
|
|
185
|
+
clearIdle();
|
|
186
|
+
controller.close();
|
|
187
|
+
context.active.settle();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (!(next.value instanceof Uint8Array)) {
|
|
191
|
+
throw new TypeError('Streaming binary yielded a non-byte chunk.');
|
|
192
|
+
}
|
|
193
|
+
delivered += next.value.byteLength;
|
|
194
|
+
if (binary.length !== undefined && delivered > binary.length) {
|
|
195
|
+
fail(new Error('OUTCOME_UNKNOWN: streaming binary exceeded its declared length.'));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
controller.enqueue(next.value);
|
|
199
|
+
resetIdle();
|
|
200
|
+
}
|
|
201
|
+
catch (cause) {
|
|
202
|
+
fail(cause);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
async cancel(reason) {
|
|
206
|
+
const message = typeof reason === 'string' ? reason : 'binary response consumer cancelled';
|
|
207
|
+
context.active.cancel(message);
|
|
208
|
+
await stop();
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
context.active.onCancel(stop);
|
|
212
|
+
return new Response(body, { status: binary.status ?? 200, headers });
|
|
213
|
+
}
|
|
214
|
+
export function isStreamingBinary(input) {
|
|
215
|
+
return !(input.body instanceof Uint8Array);
|
|
216
|
+
}
|
|
217
|
+
function binaryHttpHeaders(binary, cors, streaming) {
|
|
78
218
|
const applicationHeaders = Object.keys(binary.headers ?? {}).filter((name) => protocol.http.isApplicationBinaryHeader(name));
|
|
79
219
|
const headers = new Headers(cors([
|
|
80
220
|
protocol.http.REQUEST_ID_FIELD,
|
|
81
221
|
protocol.http.INVOCATION_FIELD,
|
|
82
222
|
protocol.http.BINARY_HEADERS_FIELD,
|
|
223
|
+
...(streaming
|
|
224
|
+
? [protocol.http.BINARY_DELIVERY_FIELD, protocol.http.BINARY_LENGTH_FIELD]
|
|
225
|
+
: []),
|
|
83
226
|
...applicationHeaders,
|
|
84
227
|
]));
|
|
85
228
|
for (const [name, value] of Object.entries(binary.headers ?? {})) {
|
|
@@ -90,9 +233,12 @@ function binaryHttpResponse(binary, cors, maximumResponseBytes) {
|
|
|
90
233
|
headers.set(protocol.http.BINARY_HEADERS_FIELD, protocol.http.encodeBinaryHeaderNames(binary.headers));
|
|
91
234
|
headers.set(protocol.http.REQUEST_ID_FIELD, String(binary.requestId));
|
|
92
235
|
headers.set(protocol.http.INVOCATION_FIELD, protocol.http.encodeInvocationId(binary.invocation));
|
|
236
|
+
if (streaming) {
|
|
237
|
+
headers.set(protocol.http.BINARY_DELIVERY_FIELD, protocol.http.STREAMING_BINARY_DELIVERY);
|
|
238
|
+
}
|
|
93
239
|
headers.set('cache-control', 'no-store');
|
|
94
240
|
appendVary(headers, 'accept');
|
|
95
|
-
return
|
|
241
|
+
return headers;
|
|
96
242
|
}
|
|
97
243
|
function streamHttpResponse(stream, context) {
|
|
98
244
|
const iterator = stream[Symbol.asyncIterator]();
|
|
@@ -286,4 +432,3 @@ function toBody(input) {
|
|
|
286
432
|
const copy = new Uint8Array(input);
|
|
287
433
|
return copy.buffer;
|
|
288
434
|
}
|
|
289
|
-
//# sourceMappingURL=render.js.map
|
|
@@ -7,4 +7,3 @@ export { interruptedStreamError } from './stream-error.js';
|
|
|
7
7
|
export { createServerRouter, type ServerRouter } from './router.js';
|
|
8
8
|
export { createWebSocketSession, handleWebSocketUpgrade, type WebSocketHandlerConfig, type WebSocketSessionConfig, } from './websocket/index.js';
|
|
9
9
|
export type { Application, WebSocketAdapter, WebSocketHandlers, WebSocketLike, WebSocketUpgradeOptions, } from './types.js';
|
|
10
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/transport/index.js
CHANGED
|
@@ -6,4 +6,3 @@ export { encodeEnvelope, renderBinary, renderHttpError, renderHttpResult, render
|
|
|
6
6
|
export { interruptedStreamError } from './stream-error.js';
|
|
7
7
|
export { createServerRouter } from './router.js';
|
|
8
8
|
export { createWebSocketSession, handleWebSocketUpgrade, } from './websocket/index.js';
|
|
9
|
-
//# sourceMappingURL=index.js.map
|
package/dist/transport/router.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import type { ErrorPayload } from '@astrale-os/kernel-protocol/errors';
|
|
2
2
|
/** A transport cancellation after stream.start necessarily follows the Function effect boundary. */
|
|
3
3
|
export declare function interruptedStreamError(timedOut: boolean): ErrorPayload;
|
|
4
|
-
//# sourceMappingURL=stream-error.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
2
2
|
import type { ApplicationHost } from '../invocation/index.js';
|
|
3
3
|
/** Portable Fetch application; framework and listener APIs are adapter-owned. */
|
|
4
4
|
export interface Application {
|
|
@@ -26,4 +26,3 @@ export interface WebSocketUpgradeOptions {
|
|
|
26
26
|
export interface WebSocketAdapter {
|
|
27
27
|
upgrade(request: Request, options: WebSocketUpgradeOptions): Response | Promise<Response>;
|
|
28
28
|
}
|
|
29
|
-
//# sourceMappingURL=types.d.ts.map
|
package/dist/transport/types.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Codec } from '@astrale-os/kernel-protocol/codecs';
|
|
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 { WebSocketHandlers } from '../types.js';
|
|
6
6
|
import { type ApplicationHost, type Handler } from '../../invocation/index.js';
|
|
7
7
|
export interface WebSocketSessionConfig {
|
|
@@ -13,9 +13,9 @@ export interface WebSocketSessionConfig {
|
|
|
13
13
|
readonly maximumRequestBytes: number;
|
|
14
14
|
readonly maximumResponseBytes: number;
|
|
15
15
|
readonly invocationTimeoutMs: number;
|
|
16
|
-
|
|
16
|
+
/** Present exactly when the Server serves a release or a Publication; bound to the upgrade. */
|
|
17
|
+
readonly serving?: RequestServing;
|
|
17
18
|
readonly host?: ApplicationHost;
|
|
18
19
|
}
|
|
19
20
|
/** Build one socket-pinned transport session. */
|
|
20
21
|
export declare function createWebSocketSession(config: WebSocketSessionConfig): WebSocketHandlers;
|
|
21
|
-
//# sourceMappingURL=session.d.ts.map
|