@astrale-os/kernel-server 0.5.0-beta.9 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +10 -1
- package/dist/errors/classify.js +25 -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 +2 -2
- package/dist/invocation/context.js +2 -2
- package/dist/invocation/identity.d.ts +0 -1
- package/dist/invocation/identity.js +0 -1
- 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 +9 -6
- 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 +6 -2
- package/dist/lifecycle/active.js +55 -14
- 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 +18 -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 +136 -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 +121 -53
- 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 +98 -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 +25 -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 +0 -1
- 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 +38 -30
- package/dist/transport/websocket/upgrade.d.ts +2 -3
- package/dist/transport/websocket/upgrade.js +7 -7
- package/dist/types.d.ts +43 -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 -17
- package/dist/publication/snapshot.d.ts.map +0 -1
- package/dist/publication/snapshot.js +0 -38
- package/dist/publication/snapshot.js.map +0 -1
- package/dist/publication/table.d.ts +0 -5
- 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/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
package/dist/server.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { accept as acceptIssuer } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
|
+
import { Path } from '@astrale-os/kernel-core/path';
|
|
3
|
+
import { PATH as RELEASE_PATH, url as releaseUrl } from '@astrale-os/kernel-protocol/release';
|
|
2
4
|
import { captureMethod } from './capture.js';
|
|
3
5
|
import { prepareCors } from './cors/index.js';
|
|
4
6
|
import { captureInvocationSource } from './invocation/index.js';
|
|
5
7
|
import { capture as captureExchangeProvider } from './issuer/exchange.js';
|
|
6
8
|
import { admitIssuer, issuerRouteReservations, mountIssuer } from './issuer/index.js';
|
|
7
9
|
import { ActiveOperations } from './lifecycle/index.js';
|
|
8
|
-
import {
|
|
10
|
+
import { currentServed, mountRoutes } from './routes/index.js';
|
|
9
11
|
import { createHttpHandler } from './transport/http/handler.js';
|
|
10
12
|
import { createServerRouter } from './transport/router.js';
|
|
11
13
|
import { handleWebSocketUpgrade } from './transport/websocket/upgrade.js';
|
|
@@ -26,7 +28,23 @@ function createPreparedServer(input, config) {
|
|
|
26
28
|
const app = createServerRouter();
|
|
27
29
|
const hosts = new WeakMap();
|
|
28
30
|
const externalIssuer = config.issuer?.issuer;
|
|
29
|
-
const externalOrigin =
|
|
31
|
+
const externalOrigin = config.publicOrigin ??
|
|
32
|
+
(externalIssuer === undefined ? undefined : new URL(externalIssuer).origin);
|
|
33
|
+
const source = config.serving;
|
|
34
|
+
const serving = source === undefined
|
|
35
|
+
? undefined
|
|
36
|
+
: (request) => {
|
|
37
|
+
const origin = externalOrigin ?? new URL(request.url).origin;
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
kind: source.kind,
|
|
40
|
+
current: () => currentServed(source, config.limits, {
|
|
41
|
+
origin,
|
|
42
|
+
...(externalIssuer === undefined ? {} : { issuer: externalIssuer }),
|
|
43
|
+
reservations: config.routeReservations,
|
|
44
|
+
}),
|
|
45
|
+
deployment: () => acceptIssuer(origin),
|
|
46
|
+
});
|
|
47
|
+
};
|
|
30
48
|
const handleHttp = createHttpHandler({
|
|
31
49
|
handler,
|
|
32
50
|
active,
|
|
@@ -34,15 +52,7 @@ function createPreparedServer(input, config) {
|
|
|
34
52
|
...config.limits,
|
|
35
53
|
cors: config.cors,
|
|
36
54
|
websocket: config.websocket !== undefined,
|
|
37
|
-
...(
|
|
38
|
-
? {}
|
|
39
|
-
: {
|
|
40
|
-
currentPublication: (request) => currentPublicationSnapshot(config.publication, config.limits, {
|
|
41
|
-
origin: externalOrigin ?? new URL(request.url).origin,
|
|
42
|
-
...(externalIssuer === undefined ? {} : { issuer: externalIssuer }),
|
|
43
|
-
reservations: config.routeReservations,
|
|
44
|
-
})?.snapshot,
|
|
45
|
-
}),
|
|
55
|
+
...(serving === undefined ? {} : { serving }),
|
|
46
56
|
});
|
|
47
57
|
if (config.websocket !== undefined) {
|
|
48
58
|
app.get(config.path, (context) => handleWebSocketUpgrade({
|
|
@@ -53,23 +63,21 @@ function createPreparedServer(input, config) {
|
|
|
53
63
|
invocation: config.invocation,
|
|
54
64
|
...config.limits,
|
|
55
65
|
cors: config.cors,
|
|
56
|
-
...(
|
|
57
|
-
? {}
|
|
58
|
-
: {
|
|
59
|
-
currentPublication: () => currentPublicationSnapshot(config.publication, config.limits, {
|
|
60
|
-
origin: externalOrigin ?? new URL(context.req.raw.url).origin,
|
|
61
|
-
...(externalIssuer === undefined ? {} : { issuer: externalIssuer }),
|
|
62
|
-
reservations: config.routeReservations,
|
|
63
|
-
})?.snapshot,
|
|
64
|
-
}),
|
|
66
|
+
...(serving === undefined ? {} : { serving: serving(context.req.raw) }),
|
|
65
67
|
host: hosts.get(context.req.raw),
|
|
66
68
|
}));
|
|
67
69
|
}
|
|
68
70
|
app.all(config.path, (context) => handleHttp(context.req.raw, hosts.get(context.req.raw)));
|
|
69
71
|
if (config.health !== undefined) {
|
|
70
72
|
app.get(config.health.path, (context) => {
|
|
71
|
-
const ready = active.
|
|
72
|
-
|
|
73
|
+
const ready = active.isAvailable && config.health.probe.check();
|
|
74
|
+
const maintenance = config.health.probe.maintenance?.();
|
|
75
|
+
const uncertified = config.health.probe.uncertified?.() === true;
|
|
76
|
+
return context.json({
|
|
77
|
+
status: ready ? 'ready' : 'not-ready',
|
|
78
|
+
...(maintenance === undefined ? {} : { maintenance }),
|
|
79
|
+
...(uncertified ? { certification: 'uncertified' } : {}),
|
|
80
|
+
}, ready ? 200 : 503, {
|
|
73
81
|
...config.cors.response(context.req.raw),
|
|
74
82
|
'cache-control': 'no-store',
|
|
75
83
|
});
|
|
@@ -81,8 +89,8 @@ function createPreparedServer(input, config) {
|
|
|
81
89
|
if (config.issuer !== undefined) {
|
|
82
90
|
mountIssuer(app, config.issuer, config.cors, active, config.limits.invocationTimeoutMs, config.tokenExchange);
|
|
83
91
|
}
|
|
84
|
-
if (config.
|
|
85
|
-
mountRoutes(app, config.
|
|
92
|
+
if (config.serving !== undefined) {
|
|
93
|
+
mountRoutes(app, config.serving, config.delivery, handler, active, config.invocation, config.limits, config.cors, (request) => hosts.get(request), config.routeReservations, externalIssuer, externalOrigin);
|
|
86
94
|
}
|
|
87
95
|
app.onError((_error, context) => new Response('{"error":"Internal server error."}', {
|
|
88
96
|
status: 500,
|
|
@@ -131,6 +139,7 @@ function captureHandler(input) {
|
|
|
131
139
|
return Object.freeze({ dispatch });
|
|
132
140
|
}
|
|
133
141
|
function admitOptions(config) {
|
|
142
|
+
const publicOrigin = config.publicOrigin === undefined ? undefined : admitPublicOrigin(config.publicOrigin);
|
|
134
143
|
const invocation = captureInvocationSource(config.invocation);
|
|
135
144
|
const limits = admitLimits(config.limits);
|
|
136
145
|
const path = admitRoute(config.path ?? '/', 'invocation path');
|
|
@@ -146,23 +155,23 @@ function admitOptions(config) {
|
|
|
146
155
|
probe: captureHealthProbe(config.health.probe),
|
|
147
156
|
});
|
|
148
157
|
const websocket = config.websocket === undefined ? undefined : captureWebSocket(config.websocket);
|
|
149
|
-
const
|
|
158
|
+
const serving = config.release === undefined
|
|
159
|
+
? undefined
|
|
160
|
+
: Object.freeze({ kind: 'release', source: captureReleaseSource(config.release) });
|
|
150
161
|
const delivery = config.delivery === undefined ? undefined : captureDeliveryProvider(config.delivery);
|
|
151
|
-
if (delivery !== undefined &&
|
|
152
|
-
throw new TypeError('
|
|
162
|
+
if (delivery !== undefined && serving === undefined) {
|
|
163
|
+
throw new TypeError('Delivery requires a release source.');
|
|
153
164
|
}
|
|
154
|
-
const
|
|
155
|
-
? undefined
|
|
156
|
-
: issuer === undefined
|
|
157
|
-
? protocolPublicationPath()
|
|
158
|
-
: protocolPublicationPath(issuer.issuer);
|
|
165
|
+
const documentPaths = serving === undefined ? [] : servedDocumentPaths(serving.kind, issuer?.issuer);
|
|
159
166
|
if (health?.path === path)
|
|
160
167
|
throw new TypeError('Health and invocation paths must be distinct.');
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
for (const { name, path: documentPath } of documentPaths) {
|
|
169
|
+
if (documentPath === path) {
|
|
170
|
+
throw new TypeError(`${name} and invocation paths must be distinct.`);
|
|
171
|
+
}
|
|
172
|
+
if (health !== undefined && documentPath === health.path) {
|
|
173
|
+
throw new TypeError(`Health and ${name} paths must be distinct.`);
|
|
174
|
+
}
|
|
166
175
|
}
|
|
167
176
|
if (issuer !== undefined) {
|
|
168
177
|
const issuerReservations = issuerRouteReservations(issuer, tokenExchange !== undefined);
|
|
@@ -180,19 +189,20 @@ function admitOptions(config) {
|
|
|
180
189
|
}
|
|
181
190
|
return Object.freeze({
|
|
182
191
|
invocation,
|
|
192
|
+
...(publicOrigin === undefined ? {} : { publicOrigin }),
|
|
183
193
|
limits,
|
|
184
194
|
path,
|
|
185
195
|
cors: prepareCors(config.cors),
|
|
186
196
|
...(health === undefined ? {} : { health }),
|
|
187
197
|
...(issuer === undefined ? {} : { issuer }),
|
|
188
198
|
...(tokenExchange === undefined ? {} : { tokenExchange }),
|
|
189
|
-
...(
|
|
199
|
+
...(serving === undefined ? {} : { serving }),
|
|
190
200
|
...(delivery === undefined ? {} : { delivery }),
|
|
191
|
-
routeReservations: routeReservations(path, health?.path, issuer, websocket !== undefined, tokenExchange !== undefined,
|
|
201
|
+
routeReservations: routeReservations(path, health?.path, issuer, websocket !== undefined, tokenExchange !== undefined, documentPaths.map(({ path: documentPath }) => documentPath)),
|
|
192
202
|
...(websocket === undefined ? {} : { websocket }),
|
|
193
203
|
});
|
|
194
204
|
}
|
|
195
|
-
function routeReservations(invocationPath, healthPath, issuer, websocket, tokenExchange,
|
|
205
|
+
function routeReservations(invocationPath, healthPath, issuer, websocket, tokenExchange, documentPaths) {
|
|
196
206
|
const reservations = [
|
|
197
207
|
Object.freeze({
|
|
198
208
|
path: invocationPath,
|
|
@@ -205,30 +215,51 @@ function routeReservations(invocationPath, healthPath, issuer, websocket, tokenE
|
|
|
205
215
|
if (issuer !== undefined) {
|
|
206
216
|
reservations.push(...issuerRouteReservations(issuer, tokenExchange));
|
|
207
217
|
}
|
|
208
|
-
|
|
209
|
-
reservations.push(Object.freeze({ path:
|
|
218
|
+
for (const documentPath of documentPaths) {
|
|
219
|
+
reservations.push(Object.freeze({ path: documentPath, methods: Object.freeze(['GET']) }));
|
|
210
220
|
}
|
|
211
221
|
return Object.freeze(reservations);
|
|
212
222
|
}
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
function servedDocumentPaths(_kind, issuer) {
|
|
224
|
+
return [
|
|
225
|
+
{
|
|
226
|
+
name: 'Release',
|
|
227
|
+
path: issuer === undefined ? RELEASE_PATH : new URL(releaseUrl(issuer)).pathname,
|
|
228
|
+
},
|
|
229
|
+
];
|
|
217
230
|
}
|
|
218
231
|
function captureHealthProbe(probe) {
|
|
219
232
|
const check = captureMethod(probe, 'check', 'Health probe is invalid.');
|
|
220
|
-
|
|
233
|
+
const maintenance = probe.maintenance === undefined
|
|
234
|
+
? undefined
|
|
235
|
+
: captureMethod(probe, 'maintenance', 'Health probe is invalid.');
|
|
236
|
+
const uncertified = probe.uncertified === undefined
|
|
237
|
+
? undefined
|
|
238
|
+
: captureMethod(probe, 'uncertified', 'Health probe is invalid.');
|
|
239
|
+
return Object.freeze({
|
|
240
|
+
check,
|
|
241
|
+
...(maintenance === undefined
|
|
242
|
+
? {}
|
|
243
|
+
: {
|
|
244
|
+
// A mode that is not a short token is not reported rather than echoed.
|
|
245
|
+
maintenance: () => {
|
|
246
|
+
const mode = maintenance();
|
|
247
|
+
return typeof mode === 'string' && /^[a-z]{1,32}$/u.test(mode) ? mode : undefined;
|
|
248
|
+
},
|
|
249
|
+
}),
|
|
250
|
+
...(uncertified === undefined ? {} : { uncertified: () => uncertified() === true }),
|
|
251
|
+
});
|
|
221
252
|
}
|
|
222
253
|
function captureWebSocket(adapter) {
|
|
223
254
|
const upgrade = captureMethod(adapter, 'upgrade', 'WebSocket adapter is invalid.');
|
|
224
255
|
return Object.freeze({ upgrade });
|
|
225
256
|
}
|
|
226
|
-
function
|
|
227
|
-
const current = captureMethod(input, 'current', '
|
|
257
|
+
function captureReleaseSource(input) {
|
|
258
|
+
const current = captureMethod(input, 'current', 'Release source is invalid.');
|
|
228
259
|
return Object.freeze({ current });
|
|
229
260
|
}
|
|
230
261
|
function captureDeliveryProvider(input) {
|
|
231
|
-
const create = captureMethod(input, 'create', '
|
|
262
|
+
const create = captureMethod(input, 'create', 'Delivery provider is invalid.');
|
|
232
263
|
return Object.freeze({ create });
|
|
233
264
|
}
|
|
234
265
|
function admitLimits(limits) {
|
|
@@ -241,16 +272,42 @@ function admitLimits(limits) {
|
|
|
241
272
|
if (limits.invocationTimeoutMs > 2_147_483_647) {
|
|
242
273
|
throw new TypeError('invocationTimeoutMs exceeds the platform timer range.');
|
|
243
274
|
}
|
|
275
|
+
if ((limits.binaryDownloadTarget === undefined) !==
|
|
276
|
+
(limits.binaryDownloadTimeoutMs === undefined)) {
|
|
277
|
+
throw new TypeError('binaryDownloadTarget and binaryDownloadTimeoutMs must be configured together.');
|
|
278
|
+
}
|
|
279
|
+
const binaryDownloadTarget = limits.binaryDownloadTarget === undefined
|
|
280
|
+
? undefined
|
|
281
|
+
: Path.parse(limits.binaryDownloadTarget).raw;
|
|
282
|
+
const binaryDownloadTimeoutMs = limits.binaryDownloadTimeoutMs === undefined
|
|
283
|
+
? undefined
|
|
284
|
+
: admitTimerLimit(limits.binaryDownloadTimeoutMs, 'binaryDownloadTimeoutMs');
|
|
244
285
|
return Object.freeze({
|
|
245
286
|
maximumRequestBytes: limits.maximumRequestBytes,
|
|
287
|
+
maximumBinaryRequestBytes: admitOptionalLimit(limits.maximumBinaryRequestBytes, limits.maximumRequestBytes, 'maximumBinaryRequestBytes'),
|
|
246
288
|
maximumResponseBytes: admitOptionalLimit(limits.maximumResponseBytes, 16 * 1024 * 1024, 'maximumResponseBytes'),
|
|
247
289
|
maximumConcurrentOperations: admitOptionalLimit(limits.maximumConcurrentOperations, 1_024, 'maximumConcurrentOperations'),
|
|
248
|
-
|
|
290
|
+
maximumReleaseBytes: admitOptionalLimit(limits.maximumReleaseBytes, limits.maximumRequestBytes, 'maximumReleaseBytes'),
|
|
249
291
|
maximumBundleBytes: admitOptionalLimit(limits.maximumBundleBytes, 16 * 1024 * 1024, 'maximumBundleBytes'),
|
|
250
292
|
maximumDeliveryBytes: admitOptionalLimit(limits.maximumDeliveryBytes, limits.maximumRequestBytes, 'maximumDeliveryBytes'),
|
|
251
293
|
invocationTimeoutMs: limits.invocationTimeoutMs,
|
|
294
|
+
binaryRequestTimeoutMs: admitTimerLimit(limits.binaryRequestTimeoutMs ?? limits.invocationTimeoutMs, 'binaryRequestTimeoutMs'),
|
|
295
|
+
...(limits.binaryRequestIdleTimeoutMs === undefined
|
|
296
|
+
? {}
|
|
297
|
+
: {
|
|
298
|
+
binaryRequestIdleTimeoutMs: admitTimerLimit(limits.binaryRequestIdleTimeoutMs, 'binaryRequestIdleTimeoutMs'),
|
|
299
|
+
}),
|
|
300
|
+
...(binaryDownloadTarget === undefined
|
|
301
|
+
? {}
|
|
302
|
+
: { binaryDownloadTarget, binaryDownloadTimeoutMs }),
|
|
252
303
|
});
|
|
253
304
|
}
|
|
305
|
+
function admitTimerLimit(input, label) {
|
|
306
|
+
if (!Number.isSafeInteger(input) || input <= 0 || input > 2_147_483_647) {
|
|
307
|
+
throw new TypeError(`${label} must be a positive platform timer duration.`);
|
|
308
|
+
}
|
|
309
|
+
return input;
|
|
310
|
+
}
|
|
254
311
|
function admitOptionalLimit(input, fallback, label) {
|
|
255
312
|
const value = input ?? fallback;
|
|
256
313
|
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
@@ -264,4 +321,15 @@ function admitRoute(path, label) {
|
|
|
264
321
|
}
|
|
265
322
|
return path;
|
|
266
323
|
}
|
|
267
|
-
|
|
324
|
+
function admitPublicOrigin(input) {
|
|
325
|
+
const url = new URL(input);
|
|
326
|
+
if (!['http:', 'https:'].includes(url.protocol) ||
|
|
327
|
+
url.username !== '' ||
|
|
328
|
+
url.password !== '' ||
|
|
329
|
+
url.pathname !== '/' ||
|
|
330
|
+
url.search !== '' ||
|
|
331
|
+
url.hash !== '') {
|
|
332
|
+
throw new TypeError('Server publicOrigin must be an HTTP(S) origin.');
|
|
333
|
+
}
|
|
334
|
+
return url.origin;
|
|
335
|
+
}
|
package/dist/transport/body.d.ts
CHANGED
|
@@ -2,4 +2,3 @@ export { RequestBodyInvalidError, RequestBodyTooLargeError } from '../errors/ind
|
|
|
2
2
|
/** Read a request body while enforcing the limit before retaining excess bytes. */
|
|
3
3
|
export declare function readBoundedBody(request: Request, maximumBytes: number, signal: AbortSignal): Promise<Uint8Array>;
|
|
4
4
|
export declare function byteLength(input: string | Uint8Array): number;
|
|
5
|
-
//# sourceMappingURL=body.d.ts.map
|
package/dist/transport/body.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as codecs from '@astrale-os/kernel-protocol/codecs';
|
|
2
2
|
export type LoadedCodec = codecs.Codec;
|
|
3
|
-
/**
|
|
4
|
-
export declare function loadCodecs():
|
|
5
|
-
//# sourceMappingURL=codecs.d.ts.map
|
|
3
|
+
/** The built-in Server representation set before pure Protocol binding selection. */
|
|
4
|
+
export declare function loadCodecs(): readonly [codecs.Codec];
|
package/dist/transport/codecs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as codecs from '@astrale-os/kernel-protocol/codecs';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
const SUPPORTED = Object.freeze([codecs.json]);
|
|
3
|
+
/** The built-in Server representation set before pure Protocol binding selection. */
|
|
4
|
+
export function loadCodecs() {
|
|
5
|
+
return SUPPORTED;
|
|
5
6
|
}
|
|
6
|
-
//# sourceMappingURL=codecs.js.map
|
|
@@ -8,4 +8,3 @@ export type CredentialResult = {
|
|
|
8
8
|
};
|
|
9
9
|
/** Resolve exactly zero or one untrusted credential channel without authenticating it. */
|
|
10
10
|
export declare function resolveCredential(envelope: string | undefined, authorization: string | null): CredentialResult;
|
|
11
|
-
//# sourceMappingURL=credential.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type BinaryRequestBody } from '@astrale-os/kernel-core/callable';
|
|
2
|
+
interface IdleBudget {
|
|
3
|
+
readonly timeoutMs: number;
|
|
4
|
+
readonly cancel: () => void;
|
|
5
|
+
}
|
|
6
|
+
/** Admit a declared length before dispatch and expose one counted, non-buffering body. */
|
|
7
|
+
export declare function binaryRequestBody(request: Request, maximumBytes: number, signal: AbortSignal, idle?: IdleBudget): BinaryRequestBody & {
|
|
8
|
+
readonly completed: () => boolean;
|
|
9
|
+
readonly cancel: (reason: unknown) => void;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { BinaryBodyLengthError } from '@astrale-os/kernel-core/callable';
|
|
2
|
+
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
3
|
+
import { RequestBodyInvalidError, RequestBodyTooLargeError } from '../../errors/index.js';
|
|
4
|
+
/** Admit a declared length before dispatch and expose one counted, non-buffering body. */
|
|
5
|
+
export function binaryRequestBody(request, maximumBytes, signal, idle) {
|
|
6
|
+
const declared = request.headers.get(protocol.http.BINARY_LENGTH_FIELD);
|
|
7
|
+
if (declared === null) {
|
|
8
|
+
throw new RequestBodyInvalidError('Binary request length is required and must be exact.');
|
|
9
|
+
}
|
|
10
|
+
const length = protocol.http.decodeBinaryLength(declared);
|
|
11
|
+
if (length === undefined) {
|
|
12
|
+
throw new RequestBodyInvalidError('Binary request length is invalid.');
|
|
13
|
+
}
|
|
14
|
+
const contentLength = request.headers.get('content-length');
|
|
15
|
+
if (contentLength !== null && contentLength !== declared) {
|
|
16
|
+
throw new RequestBodyInvalidError('Binary request length differs from Content-Length.');
|
|
17
|
+
}
|
|
18
|
+
if (length > maximumBytes)
|
|
19
|
+
throw new RequestBodyTooLargeError(maximumBytes);
|
|
20
|
+
if (request.body === null) {
|
|
21
|
+
if (length !== 0)
|
|
22
|
+
throw new RequestBodyInvalidError('Binary request body is missing.');
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
body: new ReadableStream({ start: (controller) => controller.close() }),
|
|
25
|
+
length,
|
|
26
|
+
completed: () => true,
|
|
27
|
+
cancel: () => undefined,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const reader = request.body.getReader();
|
|
31
|
+
let size = 0;
|
|
32
|
+
let finished = false;
|
|
33
|
+
let completed = false;
|
|
34
|
+
let idleTimer;
|
|
35
|
+
const clearIdle = () => {
|
|
36
|
+
if (idleTimer !== undefined)
|
|
37
|
+
clearTimeout(idleTimer);
|
|
38
|
+
idleTimer = undefined;
|
|
39
|
+
};
|
|
40
|
+
const release = () => {
|
|
41
|
+
if (finished)
|
|
42
|
+
return;
|
|
43
|
+
finished = true;
|
|
44
|
+
clearIdle();
|
|
45
|
+
signal.removeEventListener('abort', abort);
|
|
46
|
+
reader.releaseLock();
|
|
47
|
+
};
|
|
48
|
+
const cancelSource = (reason) => {
|
|
49
|
+
if (finished)
|
|
50
|
+
return;
|
|
51
|
+
clearIdle();
|
|
52
|
+
void reader.cancel(reason).then(release, release);
|
|
53
|
+
};
|
|
54
|
+
const abort = () => cancelSource(signal.reason);
|
|
55
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
56
|
+
if (signal.aborted)
|
|
57
|
+
abort();
|
|
58
|
+
const startIdle = () => {
|
|
59
|
+
if (idle !== undefined && idleTimer === undefined) {
|
|
60
|
+
idleTimer = setTimeout(idle.cancel, idle.timeoutMs);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const body = new ReadableStream({
|
|
64
|
+
async pull(controller) {
|
|
65
|
+
try {
|
|
66
|
+
if (signal.aborted)
|
|
67
|
+
throw signal.reason;
|
|
68
|
+
startIdle();
|
|
69
|
+
const next = await reader.read();
|
|
70
|
+
if (signal.aborted)
|
|
71
|
+
throw signal.reason;
|
|
72
|
+
if (next.done) {
|
|
73
|
+
if (size !== length)
|
|
74
|
+
throw new BinaryBodyLengthError();
|
|
75
|
+
completed = true;
|
|
76
|
+
release();
|
|
77
|
+
controller.close();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!(next.value instanceof Uint8Array)) {
|
|
81
|
+
throw new RequestBodyInvalidError('Binary request yielded a non-byte chunk.');
|
|
82
|
+
}
|
|
83
|
+
size += next.value.byteLength;
|
|
84
|
+
if (size > length)
|
|
85
|
+
throw new BinaryBodyLengthError();
|
|
86
|
+
if (next.value.byteLength > 0)
|
|
87
|
+
clearIdle();
|
|
88
|
+
controller.enqueue(next.value);
|
|
89
|
+
}
|
|
90
|
+
catch (cause) {
|
|
91
|
+
cancelSource(cause);
|
|
92
|
+
release();
|
|
93
|
+
controller.error(cause);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
cancel(reason) {
|
|
97
|
+
cancelSource(reason);
|
|
98
|
+
release();
|
|
99
|
+
},
|
|
100
|
+
}, { highWaterMark: 0 });
|
|
101
|
+
return Object.freeze({ body, length, completed: () => completed, cancel: cancelSource });
|
|
102
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
2
|
+
import type { CorsHeaders } from './model.js';
|
|
3
|
+
/** Present admitted buffered bytes under the response budget and binary manifest. */
|
|
4
|
+
export declare function binaryHttpResponse(binary: protocol.BinaryResponse, cors: CorsHeaders, maximumResponseBytes: number): Response;
|
|
5
|
+
/** Bind binary correlation and application headers to the HTTP response. */
|
|
6
|
+
export declare function binaryHttpHeaders(binary: Pick<protocol.BinaryResponse, 'headers' | 'invocation' | 'mediaType' | 'requestId'>, cors: CorsHeaders, streaming: boolean): Headers;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
2
|
+
import { appendVary, requireResponseBound, toBody } from './response.js';
|
|
3
|
+
/** Present admitted buffered bytes under the response budget and binary manifest. */
|
|
4
|
+
export function binaryHttpResponse(binary, cors, maximumResponseBytes) {
|
|
5
|
+
requireResponseBound(binary.body, maximumResponseBytes);
|
|
6
|
+
const headers = binaryHttpHeaders(binary, cors, false);
|
|
7
|
+
return new Response(toBody(binary.body), { status: binary.status, headers });
|
|
8
|
+
}
|
|
9
|
+
/** Bind binary correlation and application headers to the HTTP response. */
|
|
10
|
+
export function binaryHttpHeaders(binary, cors, streaming) {
|
|
11
|
+
const applicationHeaders = Object.keys(binary.headers ?? {}).filter((name) => protocol.http.isApplicationBinaryHeader(name));
|
|
12
|
+
const headers = new Headers(cors([
|
|
13
|
+
protocol.http.REQUEST_ID_FIELD,
|
|
14
|
+
protocol.http.INVOCATION_FIELD,
|
|
15
|
+
protocol.http.BINARY_HEADERS_FIELD,
|
|
16
|
+
...(streaming
|
|
17
|
+
? [protocol.http.BINARY_DELIVERY_FIELD, protocol.http.BINARY_LENGTH_FIELD]
|
|
18
|
+
: []),
|
|
19
|
+
...applicationHeaders,
|
|
20
|
+
]));
|
|
21
|
+
for (const [name, value] of Object.entries(binary.headers ?? {})) {
|
|
22
|
+
if (protocol.http.isApplicationBinaryHeader(name))
|
|
23
|
+
headers.set(name, value);
|
|
24
|
+
}
|
|
25
|
+
headers.set('content-type', binary.mediaType);
|
|
26
|
+
headers.set(protocol.http.BINARY_HEADERS_FIELD, protocol.http.encodeBinaryHeaderNames(binary.headers));
|
|
27
|
+
headers.set(protocol.http.REQUEST_ID_FIELD, String(binary.requestId));
|
|
28
|
+
headers.set(protocol.http.INVOCATION_FIELD, protocol.http.encodeInvocationId(binary.invocation));
|
|
29
|
+
if (streaming) {
|
|
30
|
+
headers.set(protocol.http.BINARY_DELIVERY_FIELD, protocol.http.STREAMING_BINARY_DELIVERY);
|
|
31
|
+
}
|
|
32
|
+
headers.set('cache-control', 'no-store');
|
|
33
|
+
appendVary(headers, 'accept');
|
|
34
|
+
return headers;
|
|
35
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { StreamingBinaryResult } from '@astrale-os/kernel-core/callable';
|
|
2
|
+
import type { RenderContext } from './model.js';
|
|
3
|
+
/** Deliver raw binary chunks with exact length, idle budget and producer cleanup. */
|
|
4
|
+
export declare function streamingBinaryHttpResponse(binary: StreamingBinaryResult, context: RenderContext): Response;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
2
|
+
import { binaryHttpHeaders } from './binary.js';
|
|
3
|
+
/** Deliver raw binary chunks with exact length, idle budget and producer cleanup. */
|
|
4
|
+
export function streamingBinaryHttpResponse(binary, context) {
|
|
5
|
+
if (binary.length !== undefined && (!Number.isSafeInteger(binary.length) || binary.length < 0)) {
|
|
6
|
+
throw new TypeError('Streaming binary length must be a non-negative safe integer.');
|
|
7
|
+
}
|
|
8
|
+
const delivery = binary.delivery;
|
|
9
|
+
if (delivery !== undefined) {
|
|
10
|
+
if (!Number.isSafeInteger(delivery.maximumDurationMs) ||
|
|
11
|
+
delivery.maximumDurationMs <= 0 ||
|
|
12
|
+
delivery.maximumDurationMs > 2_147_483_647 ||
|
|
13
|
+
!Number.isSafeInteger(delivery.idleTimeoutMs) ||
|
|
14
|
+
delivery.idleTimeoutMs <= 0 ||
|
|
15
|
+
delivery.idleTimeoutMs > 2_147_483_647) {
|
|
16
|
+
throw new TypeError('Streaming binary delivery budget is invalid.');
|
|
17
|
+
}
|
|
18
|
+
context.active.extend(delivery.maximumDurationMs);
|
|
19
|
+
}
|
|
20
|
+
const iterator = binary.body[Symbol.asyncIterator]();
|
|
21
|
+
const headers = binaryHttpHeaders(Object.freeze({
|
|
22
|
+
requestId: context.requestId,
|
|
23
|
+
invocation: context.invocation,
|
|
24
|
+
mediaType: binary.mediaType,
|
|
25
|
+
headers: binary.headers,
|
|
26
|
+
}), context.cors, true);
|
|
27
|
+
if (binary.length !== undefined) {
|
|
28
|
+
headers.set('content-length', String(binary.length));
|
|
29
|
+
// Content-Length belongs to HTTP and an intermediary may rewrite it; the Client counts the
|
|
30
|
+
// delivered bytes against this protocol field instead.
|
|
31
|
+
headers.set(protocol.http.BINARY_LENGTH_FIELD, String(binary.length));
|
|
32
|
+
}
|
|
33
|
+
if (delivery !== undefined)
|
|
34
|
+
headers.set('cache-control', 'private, no-store');
|
|
35
|
+
let terminal = false;
|
|
36
|
+
let delivered = 0;
|
|
37
|
+
let controller;
|
|
38
|
+
let stopping;
|
|
39
|
+
let idleTimer;
|
|
40
|
+
const clearIdle = () => {
|
|
41
|
+
if (idleTimer !== undefined)
|
|
42
|
+
clearTimeout(idleTimer);
|
|
43
|
+
};
|
|
44
|
+
const resetIdle = () => {
|
|
45
|
+
if (delivery === undefined || terminal)
|
|
46
|
+
return;
|
|
47
|
+
clearIdle();
|
|
48
|
+
idleTimer = setTimeout(() => context.active.cancel('binary response idle timeout exceeded'), delivery.idleTimeoutMs);
|
|
49
|
+
};
|
|
50
|
+
const stop = (reason = 'Binary response was interrupted.') => {
|
|
51
|
+
if (terminal)
|
|
52
|
+
return stopping ?? Promise.resolve();
|
|
53
|
+
terminal = true;
|
|
54
|
+
clearIdle();
|
|
55
|
+
controller?.error(new Error(reason));
|
|
56
|
+
stopping = Promise.resolve().then(async () => {
|
|
57
|
+
try {
|
|
58
|
+
await iterator.return?.();
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
context.active.settle();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
return stopping;
|
|
65
|
+
};
|
|
66
|
+
const fail = (cause) => {
|
|
67
|
+
if (terminal)
|
|
68
|
+
return;
|
|
69
|
+
terminal = true;
|
|
70
|
+
clearIdle();
|
|
71
|
+
controller?.error(cause);
|
|
72
|
+
stopping = Promise.resolve()
|
|
73
|
+
.then(() => iterator.return?.())
|
|
74
|
+
.catch(() => undefined)
|
|
75
|
+
.then(() => context.active.settle());
|
|
76
|
+
};
|
|
77
|
+
const body = new ReadableStream({
|
|
78
|
+
start(output) {
|
|
79
|
+
controller = output;
|
|
80
|
+
resetIdle();
|
|
81
|
+
},
|
|
82
|
+
async pull(controller) {
|
|
83
|
+
if (terminal)
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
const next = await iterator.next();
|
|
87
|
+
if (terminal)
|
|
88
|
+
return;
|
|
89
|
+
if (next.done) {
|
|
90
|
+
if (binary.length !== undefined && delivered !== binary.length) {
|
|
91
|
+
fail(new Error('OUTCOME_UNKNOWN: streaming binary ended before its declared length.'));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
terminal = true;
|
|
95
|
+
clearIdle();
|
|
96
|
+
controller.close();
|
|
97
|
+
context.active.settle();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (!(next.value instanceof Uint8Array)) {
|
|
101
|
+
throw new TypeError('Streaming binary yielded a non-byte chunk.');
|
|
102
|
+
}
|
|
103
|
+
delivered += next.value.byteLength;
|
|
104
|
+
if (binary.length !== undefined && delivered > binary.length) {
|
|
105
|
+
fail(new Error('OUTCOME_UNKNOWN: streaming binary exceeded its declared length.'));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
controller.enqueue(next.value);
|
|
109
|
+
resetIdle();
|
|
110
|
+
}
|
|
111
|
+
catch (cause) {
|
|
112
|
+
fail(cause);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
async cancel(reason) {
|
|
116
|
+
const message = typeof reason === 'string' ? reason : 'binary response consumer cancelled';
|
|
117
|
+
context.active.cancel(message);
|
|
118
|
+
await stop();
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
context.active.onCancel(stop);
|
|
122
|
+
return new Response(body, { status: binary.status ?? 200, headers });
|
|
123
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
|
+
import type { Codec } from '@astrale-os/kernel-protocol/codecs';
|
|
3
|
+
import type { ReleaseDigest } from '@astrale-os/kernel-protocol/release';
|
|
4
|
+
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
5
|
+
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
6
|
+
import type { CorsHeaders, ErrorRenderOptions } from './model.js';
|
|
7
|
+
/** Render a correlated error with its status, authentication challenge and retry delay. */
|
|
8
|
+
export declare function renderHttpError(requestId: protocol.RequestId, invocation: protocol.InvocationId, error: errors.ErrorPayload, codec: Codec, cors: CorsHeaders, options?: ErrorRenderOptions): Response;
|
|
9
|
+
/** Present stale routing as a bounded status-200 Invocation envelope. */
|
|
10
|
+
export declare function renderHttpStale(requestId: protocol.RequestId, invocation: protocol.InvocationId, current: protocol.Stale['current'], codec: Codec, cors: CorsHeaders, maximumResponseBytes?: number): Response;
|
|
11
|
+
/** Retirement controls always use JSON, regardless of the negotiated request codec. */
|
|
12
|
+
export declare function renderDeploymentRetired(deployment: IssuerId, release: ReleaseDigest | undefined, cors: CorsHeaders): Response;
|