@astrale-os/kernel-server 0.5.0-beta.9 → 1.0.0-beta.2
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 +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 +7 -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 +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 +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 +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 +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 +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 -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,22 +63,14 @@ 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.
|
|
73
|
+
const ready = active.isAvailable && config.health.probe.check();
|
|
72
74
|
return context.json({ status: ready ? 'ready' : 'not-ready' }, ready ? 200 : 503, {
|
|
73
75
|
...config.cors.response(context.req.raw),
|
|
74
76
|
'cache-control': 'no-store',
|
|
@@ -81,8 +83,8 @@ function createPreparedServer(input, config) {
|
|
|
81
83
|
if (config.issuer !== undefined) {
|
|
82
84
|
mountIssuer(app, config.issuer, config.cors, active, config.limits.invocationTimeoutMs, config.tokenExchange);
|
|
83
85
|
}
|
|
84
|
-
if (config.
|
|
85
|
-
mountRoutes(app, config.
|
|
86
|
+
if (config.serving !== undefined) {
|
|
87
|
+
mountRoutes(app, config.serving, config.delivery, handler, active, config.invocation, config.limits, config.cors, (request) => hosts.get(request), config.routeReservations, externalIssuer, externalOrigin);
|
|
86
88
|
}
|
|
87
89
|
app.onError((_error, context) => new Response('{"error":"Internal server error."}', {
|
|
88
90
|
status: 500,
|
|
@@ -131,6 +133,7 @@ function captureHandler(input) {
|
|
|
131
133
|
return Object.freeze({ dispatch });
|
|
132
134
|
}
|
|
133
135
|
function admitOptions(config) {
|
|
136
|
+
const publicOrigin = config.publicOrigin === undefined ? undefined : admitPublicOrigin(config.publicOrigin);
|
|
134
137
|
const invocation = captureInvocationSource(config.invocation);
|
|
135
138
|
const limits = admitLimits(config.limits);
|
|
136
139
|
const path = admitRoute(config.path ?? '/', 'invocation path');
|
|
@@ -146,23 +149,23 @@ function admitOptions(config) {
|
|
|
146
149
|
probe: captureHealthProbe(config.health.probe),
|
|
147
150
|
});
|
|
148
151
|
const websocket = config.websocket === undefined ? undefined : captureWebSocket(config.websocket);
|
|
149
|
-
const
|
|
152
|
+
const serving = config.release === undefined
|
|
153
|
+
? undefined
|
|
154
|
+
: Object.freeze({ kind: 'release', source: captureReleaseSource(config.release) });
|
|
150
155
|
const delivery = config.delivery === undefined ? undefined : captureDeliveryProvider(config.delivery);
|
|
151
|
-
if (delivery !== undefined &&
|
|
152
|
-
throw new TypeError('
|
|
156
|
+
if (delivery !== undefined && serving === undefined) {
|
|
157
|
+
throw new TypeError('Delivery requires a release source.');
|
|
153
158
|
}
|
|
154
|
-
const
|
|
155
|
-
? undefined
|
|
156
|
-
: issuer === undefined
|
|
157
|
-
? protocolPublicationPath()
|
|
158
|
-
: protocolPublicationPath(issuer.issuer);
|
|
159
|
+
const documentPaths = serving === undefined ? [] : servedDocumentPaths(serving.kind, issuer?.issuer);
|
|
159
160
|
if (health?.path === path)
|
|
160
161
|
throw new TypeError('Health and invocation paths must be distinct.');
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
for (const { name, path: documentPath } of documentPaths) {
|
|
163
|
+
if (documentPath === path) {
|
|
164
|
+
throw new TypeError(`${name} and invocation paths must be distinct.`);
|
|
165
|
+
}
|
|
166
|
+
if (health !== undefined && documentPath === health.path) {
|
|
167
|
+
throw new TypeError(`Health and ${name} paths must be distinct.`);
|
|
168
|
+
}
|
|
166
169
|
}
|
|
167
170
|
if (issuer !== undefined) {
|
|
168
171
|
const issuerReservations = issuerRouteReservations(issuer, tokenExchange !== undefined);
|
|
@@ -180,19 +183,20 @@ function admitOptions(config) {
|
|
|
180
183
|
}
|
|
181
184
|
return Object.freeze({
|
|
182
185
|
invocation,
|
|
186
|
+
...(publicOrigin === undefined ? {} : { publicOrigin }),
|
|
183
187
|
limits,
|
|
184
188
|
path,
|
|
185
189
|
cors: prepareCors(config.cors),
|
|
186
190
|
...(health === undefined ? {} : { health }),
|
|
187
191
|
...(issuer === undefined ? {} : { issuer }),
|
|
188
192
|
...(tokenExchange === undefined ? {} : { tokenExchange }),
|
|
189
|
-
...(
|
|
193
|
+
...(serving === undefined ? {} : { serving }),
|
|
190
194
|
...(delivery === undefined ? {} : { delivery }),
|
|
191
|
-
routeReservations: routeReservations(path, health?.path, issuer, websocket !== undefined, tokenExchange !== undefined,
|
|
195
|
+
routeReservations: routeReservations(path, health?.path, issuer, websocket !== undefined, tokenExchange !== undefined, documentPaths.map(({ path: documentPath }) => documentPath)),
|
|
192
196
|
...(websocket === undefined ? {} : { websocket }),
|
|
193
197
|
});
|
|
194
198
|
}
|
|
195
|
-
function routeReservations(invocationPath, healthPath, issuer, websocket, tokenExchange,
|
|
199
|
+
function routeReservations(invocationPath, healthPath, issuer, websocket, tokenExchange, documentPaths) {
|
|
196
200
|
const reservations = [
|
|
197
201
|
Object.freeze({
|
|
198
202
|
path: invocationPath,
|
|
@@ -205,15 +209,18 @@ function routeReservations(invocationPath, healthPath, issuer, websocket, tokenE
|
|
|
205
209
|
if (issuer !== undefined) {
|
|
206
210
|
reservations.push(...issuerRouteReservations(issuer, tokenExchange));
|
|
207
211
|
}
|
|
208
|
-
|
|
209
|
-
reservations.push(Object.freeze({ path:
|
|
212
|
+
for (const documentPath of documentPaths) {
|
|
213
|
+
reservations.push(Object.freeze({ path: documentPath, methods: Object.freeze(['GET']) }));
|
|
210
214
|
}
|
|
211
215
|
return Object.freeze(reservations);
|
|
212
216
|
}
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
+
function servedDocumentPaths(_kind, issuer) {
|
|
218
|
+
return [
|
|
219
|
+
{
|
|
220
|
+
name: 'Release',
|
|
221
|
+
path: issuer === undefined ? RELEASE_PATH : new URL(releaseUrl(issuer)).pathname,
|
|
222
|
+
},
|
|
223
|
+
];
|
|
217
224
|
}
|
|
218
225
|
function captureHealthProbe(probe) {
|
|
219
226
|
const check = captureMethod(probe, 'check', 'Health probe is invalid.');
|
|
@@ -223,12 +230,12 @@ function captureWebSocket(adapter) {
|
|
|
223
230
|
const upgrade = captureMethod(adapter, 'upgrade', 'WebSocket adapter is invalid.');
|
|
224
231
|
return Object.freeze({ upgrade });
|
|
225
232
|
}
|
|
226
|
-
function
|
|
227
|
-
const current = captureMethod(input, 'current', '
|
|
233
|
+
function captureReleaseSource(input) {
|
|
234
|
+
const current = captureMethod(input, 'current', 'Release source is invalid.');
|
|
228
235
|
return Object.freeze({ current });
|
|
229
236
|
}
|
|
230
237
|
function captureDeliveryProvider(input) {
|
|
231
|
-
const create = captureMethod(input, 'create', '
|
|
238
|
+
const create = captureMethod(input, 'create', 'Delivery provider is invalid.');
|
|
232
239
|
return Object.freeze({ create });
|
|
233
240
|
}
|
|
234
241
|
function admitLimits(limits) {
|
|
@@ -241,16 +248,42 @@ function admitLimits(limits) {
|
|
|
241
248
|
if (limits.invocationTimeoutMs > 2_147_483_647) {
|
|
242
249
|
throw new TypeError('invocationTimeoutMs exceeds the platform timer range.');
|
|
243
250
|
}
|
|
251
|
+
if ((limits.binaryDownloadTarget === undefined) !==
|
|
252
|
+
(limits.binaryDownloadTimeoutMs === undefined)) {
|
|
253
|
+
throw new TypeError('binaryDownloadTarget and binaryDownloadTimeoutMs must be configured together.');
|
|
254
|
+
}
|
|
255
|
+
const binaryDownloadTarget = limits.binaryDownloadTarget === undefined
|
|
256
|
+
? undefined
|
|
257
|
+
: Path.parse(limits.binaryDownloadTarget).raw;
|
|
258
|
+
const binaryDownloadTimeoutMs = limits.binaryDownloadTimeoutMs === undefined
|
|
259
|
+
? undefined
|
|
260
|
+
: admitTimerLimit(limits.binaryDownloadTimeoutMs, 'binaryDownloadTimeoutMs');
|
|
244
261
|
return Object.freeze({
|
|
245
262
|
maximumRequestBytes: limits.maximumRequestBytes,
|
|
263
|
+
maximumBinaryRequestBytes: admitOptionalLimit(limits.maximumBinaryRequestBytes, limits.maximumRequestBytes, 'maximumBinaryRequestBytes'),
|
|
246
264
|
maximumResponseBytes: admitOptionalLimit(limits.maximumResponseBytes, 16 * 1024 * 1024, 'maximumResponseBytes'),
|
|
247
265
|
maximumConcurrentOperations: admitOptionalLimit(limits.maximumConcurrentOperations, 1_024, 'maximumConcurrentOperations'),
|
|
248
|
-
|
|
266
|
+
maximumReleaseBytes: admitOptionalLimit(limits.maximumReleaseBytes, limits.maximumRequestBytes, 'maximumReleaseBytes'),
|
|
249
267
|
maximumBundleBytes: admitOptionalLimit(limits.maximumBundleBytes, 16 * 1024 * 1024, 'maximumBundleBytes'),
|
|
250
268
|
maximumDeliveryBytes: admitOptionalLimit(limits.maximumDeliveryBytes, limits.maximumRequestBytes, 'maximumDeliveryBytes'),
|
|
251
269
|
invocationTimeoutMs: limits.invocationTimeoutMs,
|
|
270
|
+
binaryRequestTimeoutMs: admitTimerLimit(limits.binaryRequestTimeoutMs ?? limits.invocationTimeoutMs, 'binaryRequestTimeoutMs'),
|
|
271
|
+
...(limits.binaryRequestIdleTimeoutMs === undefined
|
|
272
|
+
? {}
|
|
273
|
+
: {
|
|
274
|
+
binaryRequestIdleTimeoutMs: admitTimerLimit(limits.binaryRequestIdleTimeoutMs, 'binaryRequestIdleTimeoutMs'),
|
|
275
|
+
}),
|
|
276
|
+
...(binaryDownloadTarget === undefined
|
|
277
|
+
? {}
|
|
278
|
+
: { binaryDownloadTarget, binaryDownloadTimeoutMs }),
|
|
252
279
|
});
|
|
253
280
|
}
|
|
281
|
+
function admitTimerLimit(input, label) {
|
|
282
|
+
if (!Number.isSafeInteger(input) || input <= 0 || input > 2_147_483_647) {
|
|
283
|
+
throw new TypeError(`${label} must be a positive platform timer duration.`);
|
|
284
|
+
}
|
|
285
|
+
return input;
|
|
286
|
+
}
|
|
254
287
|
function admitOptionalLimit(input, fallback, label) {
|
|
255
288
|
const value = input ?? fallback;
|
|
256
289
|
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
@@ -264,4 +297,15 @@ function admitRoute(path, label) {
|
|
|
264
297
|
}
|
|
265
298
|
return path;
|
|
266
299
|
}
|
|
267
|
-
|
|
300
|
+
function admitPublicOrigin(input) {
|
|
301
|
+
const url = new URL(input);
|
|
302
|
+
if (!['http:', 'https:'].includes(url.protocol) ||
|
|
303
|
+
url.username !== '' ||
|
|
304
|
+
url.password !== '' ||
|
|
305
|
+
url.pathname !== '/' ||
|
|
306
|
+
url.search !== '' ||
|
|
307
|
+
url.hash !== '') {
|
|
308
|
+
throw new TypeError('Server publicOrigin must be an HTTP(S) origin.');
|
|
309
|
+
}
|
|
310
|
+
return url.origin;
|
|
311
|
+
}
|
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;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { json } from '@astrale-os/kernel-protocol/codecs';
|
|
2
|
+
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
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';
|
|
5
|
+
import { retryAfterHeaders } from '../../errors/index.js';
|
|
6
|
+
import { envelopeResponse, errorEnvelopeResponse, toBody } from './response.js';
|
|
7
|
+
/** Render a correlated error with its status, authentication challenge and retry delay. */
|
|
8
|
+
export function renderHttpError(requestId, invocation, error, codec, cors, options = {}) {
|
|
9
|
+
const status = options.status ?? errors.httpStatusOf(error);
|
|
10
|
+
const retry = retryAfterHeaders(error);
|
|
11
|
+
const extra = status === 401 ? { 'www-authenticate': 'Bearer realm="astrale-invocation"' } : retry;
|
|
12
|
+
return errorEnvelopeResponse(codec, { error, requestId, invocation }, { ...cors(Object.keys(retry)), ...extra }, status, options.negotiated ?? true, options.maximumResponseBytes);
|
|
13
|
+
}
|
|
14
|
+
/** Present stale routing as a bounded status-200 Invocation envelope. */
|
|
15
|
+
export function renderHttpStale(requestId, invocation, current, codec, cors, maximumResponseBytes) {
|
|
16
|
+
return envelopeResponse(codec, protocol.stale(requestId, invocation, current), cors(), 200, true, maximumResponseBytes);
|
|
17
|
+
}
|
|
18
|
+
/** Retirement controls always use JSON, regardless of the negotiated request codec. */
|
|
19
|
+
export function renderDeploymentRetired(deployment, release, cors) {
|
|
20
|
+
return new Response(toBody(json.encode(deploymentRetiredBody(deployment, release))), {
|
|
21
|
+
status: HTTP_DEPLOYMENT_RETIRED_STATUS,
|
|
22
|
+
headers: { ...cors([HTTP_DEPLOYMENT_RETIRED]), ...deploymentRetiredHeaders() },
|
|
23
|
+
});
|
|
24
|
+
}
|