@astrale-os/kernel-server 0.5.0-beta.6 → 0.5.0-beta.61
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 +2 -1
- 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/availability.d.ts +0 -1
- package/dist/errors/availability.js +0 -1
- package/dist/errors/classify.d.ts +0 -1
- package/dist/errors/classify.js +0 -1
- package/dist/errors/index.d.ts +0 -1
- package/dist/errors/index.js +0 -1
- 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 +0 -1
- package/dist/index.js +0 -1
- package/dist/invocation/context.d.ts +1 -2
- package/dist/invocation/context.js +0 -1
- package/dist/invocation/identity.d.ts +1 -2
- package/dist/invocation/identity.js +1 -2
- package/dist/invocation/index.d.ts +0 -1
- package/dist/invocation/index.js +0 -1
- package/dist/invocation/publication-route.d.ts +1 -4
- package/dist/invocation/publication-route.js +2 -12
- package/dist/invocation/types.d.ts +3 -4
- package/dist/invocation/types.js +0 -1
- package/dist/issuer/exchange.d.ts +0 -1
- package/dist/issuer/exchange.js +9 -1
- 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 +0 -1
- package/dist/issuer/types.d.ts +0 -1
- package/dist/issuer/types.js +0 -1
- package/dist/lifecycle/active.d.ts +2 -1
- package/dist/lifecycle/active.js +5 -3
- package/dist/lifecycle/index.d.ts +0 -1
- package/dist/lifecycle/index.js +0 -1
- package/dist/publication/delivery/handler.d.ts +0 -1
- package/dist/publication/delivery/handler.js +2 -2
- package/dist/publication/delivery/index.d.ts +0 -1
- package/dist/publication/delivery/index.js +0 -1
- package/dist/publication/delivery/types.d.ts +1 -2
- package/dist/publication/delivery/types.js +0 -1
- package/dist/publication/index.d.ts +0 -1
- package/dist/publication/index.js +0 -1
- package/dist/publication/snapshot.d.ts +4 -4
- package/dist/publication/snapshot.js +15 -8
- package/dist/publication/table.d.ts +4 -4
- package/dist/publication/table.js +3 -4
- package/dist/publication/types.d.ts +4 -5
- package/dist/publication/types.js +0 -1
- package/dist/routes/index.d.ts +0 -1
- package/dist/routes/index.js +0 -1
- package/dist/routes/routes.d.ts +1 -2
- package/dist/routes/routes.js +106 -14
- package/dist/server.d.ts +0 -1
- package/dist/server.js +18 -4
- package/dist/transport/body.d.ts +0 -1
- package/dist/transport/body.js +0 -1
- package/dist/transport/codecs.d.ts +0 -1
- package/dist/transport/codecs.js +0 -1
- package/dist/transport/credential.d.ts +0 -1
- package/dist/transport/credential.js +0 -1
- package/dist/transport/http/handler.d.ts +0 -1
- package/dist/transport/http/handler.js +3 -3
- package/dist/transport/http/index.d.ts +0 -1
- package/dist/transport/http/index.js +0 -1
- package/dist/transport/http/render.d.ts +5 -4
- package/dist/transport/http/render.js +77 -2
- 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 +0 -1
- package/dist/transport/websocket/session.js +12 -2
- package/dist/transport/websocket/upgrade.d.ts +0 -1
- package/dist/transport/websocket/upgrade.js +0 -1
- package/dist/types.d.ts +3 -2
- package/dist/types.js +0 -1
- package/package.json +10 -12
- 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.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.map +0 -1
- package/dist/publication/delivery/types.js.map +0 -1
- package/dist/publication/index.d.ts.map +0 -1
- package/dist/publication/index.js.map +0 -1
- package/dist/publication/snapshot.d.ts.map +0 -1
- package/dist/publication/snapshot.js.map +0 -1
- package/dist/publication/table.d.ts.map +0 -1
- package/dist/publication/table.js.map +0 -1
- package/dist/publication/types.d.ts.map +0 -1
- 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
package/dist/routes/routes.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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 {
|
|
4
|
+
import { bundleUrl } from '@astrale-os/kernel-protocol/publication';
|
|
5
|
+
import { mediaType as publicationMediaType, PATH as PUBLICATION_PATH, url as publicationUrl, } from '@astrale-os/kernel-protocol/publication/publication';
|
|
5
6
|
import { reference as publicationReference } from '@astrale-os/kernel-protocol/publication/reference';
|
|
6
7
|
import * as routes from '@astrale-os/kernel-protocol/routes';
|
|
7
8
|
import { classifyError, RequestBodyInvalidError, RequestBodyTooLargeError, } from '../errors/index.js';
|
|
@@ -11,11 +12,11 @@ import { readPreparedPublicationSnapshot, } from '../publication/index.js';
|
|
|
11
12
|
import { readBoundedBody } from '../transport/body.js';
|
|
12
13
|
import {} from '../transport/router.js';
|
|
13
14
|
import { interruptedStreamError } from '../transport/stream-error.js';
|
|
14
|
-
export function mountRoutes(app, source, deliveryProvider, handler, active, nextInvocation, limits, cors, hostFor, reservations, externalIssuer) {
|
|
15
|
+
export function mountRoutes(app, source, deliveryProvider, handler, active, nextInvocation, limits, cors, hostFor, reservations, externalIssuer, publicOrigin) {
|
|
15
16
|
const publicationPath = externalIssuer === undefined
|
|
16
17
|
? PUBLICATION_PATH
|
|
17
18
|
: new URL(publicationUrl(externalIssuer)).pathname;
|
|
18
|
-
const externalOrigin = externalIssuer === undefined ? undefined : new URL(externalIssuer).origin;
|
|
19
|
+
const externalOrigin = publicOrigin ?? (externalIssuer === undefined ? undefined : new URL(externalIssuer).origin);
|
|
19
20
|
if (cors.enabled) {
|
|
20
21
|
app.options(publicationPath, (context) => cors.preflight(context.req.raw, {
|
|
21
22
|
methods: ['GET'],
|
|
@@ -26,7 +27,7 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
|
|
|
26
27
|
const responseCors = cors.response(context.req.raw, ['etag']);
|
|
27
28
|
if (!active.isAccepting)
|
|
28
29
|
return response({ error: 'Publication is unavailable while draining.' }, 503, responseCors);
|
|
29
|
-
const snapshot = servingSnapshot(source, limits, context.req.raw, reservations, externalIssuer);
|
|
30
|
+
const snapshot = servingSnapshot(source, limits, context.req.raw, reservations, externalIssuer, externalOrigin);
|
|
30
31
|
if (snapshot === undefined)
|
|
31
32
|
return response({ error: 'Publication is not ready.' }, 503, responseCors);
|
|
32
33
|
const body = snapshot.publicationBytes;
|
|
@@ -44,14 +45,14 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
|
|
|
44
45
|
status: 200,
|
|
45
46
|
headers: {
|
|
46
47
|
...responseCors,
|
|
47
|
-
'content-type':
|
|
48
|
+
'content-type': publicationMediaType(snapshot.publication),
|
|
48
49
|
etag: quoteEtag(snapshot.publication.etag),
|
|
49
50
|
'cache-control': 'public, max-age=0, must-revalidate, no-transform',
|
|
50
51
|
},
|
|
51
52
|
});
|
|
52
53
|
});
|
|
53
54
|
app.all('*', async (context) => {
|
|
54
|
-
const snapshot = servingSnapshot(source, limits, context.req.raw, reservations, externalIssuer);
|
|
55
|
+
const snapshot = servingSnapshot(source, limits, context.req.raw, reservations, externalIssuer, externalOrigin);
|
|
55
56
|
if (snapshot === undefined)
|
|
56
57
|
return context.notFound();
|
|
57
58
|
const request = context.req.raw;
|
|
@@ -59,6 +60,9 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
|
|
|
59
60
|
return publicationPreflight(cors, request, snapshot, externalOrigin);
|
|
60
61
|
}
|
|
61
62
|
const responseCors = cors.response(request);
|
|
63
|
+
const descriptor = snapshot.publication.schema.bundle;
|
|
64
|
+
const addressed = bundleUrl(snapshot.publication.identity.issuer, descriptor.ref);
|
|
65
|
+
const immutable = descriptor.href === addressed;
|
|
62
66
|
if (sameRoute(request.url, snapshot.publication.schema.bundle.href, externalOrigin)) {
|
|
63
67
|
if (!active.isAccepting)
|
|
64
68
|
return response({ error: 'Publication artifact is unavailable while draining.' }, 503, responseCors);
|
|
@@ -68,16 +72,32 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
|
|
|
68
72
|
if (body.byteLength !== snapshot.publication.schema.bundle.ref.size) {
|
|
69
73
|
return response({ error: 'Bundle differs from its Publication.' }, 500, responseCors);
|
|
70
74
|
}
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
const etag = quoteEtag(descriptor.ref.digest);
|
|
76
|
+
const unchanged = request.headers.get('if-none-match') === etag;
|
|
77
|
+
return new Response(unchanged ? null : toBody(body), {
|
|
78
|
+
status: unchanged ? 304 : 200,
|
|
73
79
|
headers: {
|
|
74
80
|
...responseCors,
|
|
75
81
|
'content-type': snapshot.publication.schema.bundle.ref.mediaType,
|
|
76
|
-
etag
|
|
77
|
-
'cache-control':
|
|
82
|
+
etag,
|
|
83
|
+
'cache-control': immutable
|
|
84
|
+
? 'public, max-age=31536000, immutable, no-transform'
|
|
85
|
+
: 'public, max-age=0, must-revalidate, no-transform',
|
|
78
86
|
},
|
|
79
87
|
});
|
|
80
88
|
}
|
|
89
|
+
if (request.method === 'GET' && immutable) {
|
|
90
|
+
const pathname = new URL(addressed).pathname;
|
|
91
|
+
const directory = pathname.slice(0, pathname.lastIndexOf('/') + 1);
|
|
92
|
+
const requested = new URL(request.url);
|
|
93
|
+
if (requested.pathname.startsWith(directory) &&
|
|
94
|
+
/^[0-9a-f]{64}\.json$/u.test(requested.pathname.slice(directory.length))) {
|
|
95
|
+
return new Response(null, {
|
|
96
|
+
status: 404,
|
|
97
|
+
headers: { ...responseCors, 'cache-control': 'no-store' },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
81
101
|
const deliveryUrl = snapshot.publication.delivery?.href;
|
|
82
102
|
if (deliveryUrl !== undefined && sameRoute(request.url, deliveryUrl, externalOrigin)) {
|
|
83
103
|
return handleDelivery(request, snapshot, deliveryProvider, active, limits, cors.response(request));
|
|
@@ -117,7 +137,7 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
|
|
|
117
137
|
let handedOff = false;
|
|
118
138
|
try {
|
|
119
139
|
const body = await readBoundedBody(request, limits.maximumRequestBytes, operation.signal);
|
|
120
|
-
const invocation = routes.decode(route, { url: request.url, method: request.method, headers: request.headers, body }, snapshot.
|
|
140
|
+
const invocation = routes.decode(route, { url: request.url, method: request.method, headers: request.headers, body }, snapshot.domain, snapshot.table.publication);
|
|
121
141
|
const idempotencyHeader = request.headers.get(protocol.HTTP_IDEMPOTENCY_KEY);
|
|
122
142
|
const idempotencyKey = idempotencyHeader === null
|
|
123
143
|
? undefined
|
|
@@ -138,8 +158,13 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
|
|
|
138
158
|
});
|
|
139
159
|
}
|
|
140
160
|
if (result.kind === 'binary') {
|
|
161
|
+
if (isStreamingBinary(result.value)) {
|
|
162
|
+
handedOff = true;
|
|
163
|
+
return streamingBinaryResponse(result.value, operation, cors, request);
|
|
164
|
+
}
|
|
141
165
|
const applicationHeaders = applicationBinaryHeaders(result.value.headers);
|
|
142
166
|
return new Response(toBody(result.value.body), {
|
|
167
|
+
status: result.value.status ?? 200,
|
|
143
168
|
headers: {
|
|
144
169
|
...cors.response(request, [
|
|
145
170
|
protocol.http.BINARY_HEADERS_FIELD,
|
|
@@ -197,6 +222,73 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
|
|
|
197
222
|
}
|
|
198
223
|
});
|
|
199
224
|
}
|
|
225
|
+
function streamingBinaryResponse(binary, operation, cors, request) {
|
|
226
|
+
const iterator = binary.body[Symbol.asyncIterator]();
|
|
227
|
+
const applicationHeaders = applicationBinaryHeaders(binary.headers);
|
|
228
|
+
let terminal = false;
|
|
229
|
+
let controller;
|
|
230
|
+
let stopping;
|
|
231
|
+
const stop = (reason = 'Binary response was interrupted.') => {
|
|
232
|
+
if (terminal)
|
|
233
|
+
return stopping ?? Promise.resolve();
|
|
234
|
+
terminal = true;
|
|
235
|
+
controller?.error(new Error(reason));
|
|
236
|
+
stopping = Promise.resolve().then(async () => {
|
|
237
|
+
try {
|
|
238
|
+
await iterator.return?.();
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
operation.settle();
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
return stopping;
|
|
245
|
+
};
|
|
246
|
+
const body = new ReadableStream({
|
|
247
|
+
start(output) {
|
|
248
|
+
controller = output;
|
|
249
|
+
},
|
|
250
|
+
async pull(controller) {
|
|
251
|
+
if (terminal)
|
|
252
|
+
return;
|
|
253
|
+
try {
|
|
254
|
+
const next = await iterator.next();
|
|
255
|
+
if (terminal)
|
|
256
|
+
return;
|
|
257
|
+
if (next.done) {
|
|
258
|
+
terminal = true;
|
|
259
|
+
controller.close();
|
|
260
|
+
operation.settle();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
controller.enqueue(next.value);
|
|
264
|
+
}
|
|
265
|
+
catch (cause) {
|
|
266
|
+
if (terminal)
|
|
267
|
+
return;
|
|
268
|
+
terminal = true;
|
|
269
|
+
controller.error(cause);
|
|
270
|
+
operation.settle();
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
async cancel(reason) {
|
|
274
|
+
const message = typeof reason === 'string' ? reason : 'native binary response cancelled';
|
|
275
|
+
operation.cancel(message);
|
|
276
|
+
await stop();
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
operation.onCancel(stop);
|
|
280
|
+
return new Response(body, {
|
|
281
|
+
status: binary.status ?? 200,
|
|
282
|
+
headers: {
|
|
283
|
+
...cors.response(request, Object.keys(applicationHeaders)),
|
|
284
|
+
...applicationHeaders,
|
|
285
|
+
'content-type': binary.mediaType,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
function isStreamingBinary(input) {
|
|
290
|
+
return !(input.body instanceof Uint8Array);
|
|
291
|
+
}
|
|
200
292
|
function publicationPreflight(cors, request, snapshot, externalOrigin) {
|
|
201
293
|
if (sameRoute(request.url, snapshot.publication.schema.bundle.href, externalOrigin)) {
|
|
202
294
|
return cors.preflight(request, { methods: ['GET'], headers: ['if-none-match'] });
|
|
@@ -260,9 +352,10 @@ export function currentPublicationSnapshot(source, limits, serving) {
|
|
|
260
352
|
}
|
|
261
353
|
return prepared;
|
|
262
354
|
}
|
|
263
|
-
function servingSnapshot(source, limits, request, reservations, externalIssuer) {
|
|
355
|
+
function servingSnapshot(source, limits, request, reservations, externalIssuer, publicOrigin) {
|
|
264
356
|
return currentPublicationSnapshot(source, limits, {
|
|
265
|
-
origin:
|
|
357
|
+
origin: publicOrigin ??
|
|
358
|
+
(externalIssuer === undefined ? new URL(request.url).origin : new URL(externalIssuer).origin),
|
|
266
359
|
...(externalIssuer === undefined ? {} : { issuer: externalIssuer }),
|
|
267
360
|
reservations,
|
|
268
361
|
});
|
|
@@ -446,4 +539,3 @@ function encodedBody(input) {
|
|
|
446
539
|
function toBody(input) {
|
|
447
540
|
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
448
541
|
}
|
|
449
|
-
//# sourceMappingURL=routes.js.map
|
package/dist/server.d.ts
CHANGED
|
@@ -2,4 +2,3 @@ import type { Server, ServerConfig, ServerOptions, PreparedServer } from './type
|
|
|
2
2
|
export declare function createServer(config: ServerConfig): Server;
|
|
3
3
|
/** Validate all static transport configuration before a Host starts Runtime recovery/bootstrap. */
|
|
4
4
|
export declare function prepareServer(options: ServerOptions): PreparedServer;
|
|
5
|
-
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.js
CHANGED
|
@@ -26,7 +26,8 @@ function createPreparedServer(input, config) {
|
|
|
26
26
|
const app = createServerRouter();
|
|
27
27
|
const hosts = new WeakMap();
|
|
28
28
|
const externalIssuer = config.issuer?.issuer;
|
|
29
|
-
const externalOrigin =
|
|
29
|
+
const externalOrigin = config.publicOrigin ??
|
|
30
|
+
(externalIssuer === undefined ? undefined : new URL(externalIssuer).origin);
|
|
30
31
|
const handleHttp = createHttpHandler({
|
|
31
32
|
handler,
|
|
32
33
|
active,
|
|
@@ -68,7 +69,7 @@ function createPreparedServer(input, config) {
|
|
|
68
69
|
app.all(config.path, (context) => handleHttp(context.req.raw, hosts.get(context.req.raw)));
|
|
69
70
|
if (config.health !== undefined) {
|
|
70
71
|
app.get(config.health.path, (context) => {
|
|
71
|
-
const ready = active.
|
|
72
|
+
const ready = active.isAvailable && config.health.probe.check();
|
|
72
73
|
return context.json({ status: ready ? 'ready' : 'not-ready' }, ready ? 200 : 503, {
|
|
73
74
|
...config.cors.response(context.req.raw),
|
|
74
75
|
'cache-control': 'no-store',
|
|
@@ -82,7 +83,7 @@ function createPreparedServer(input, config) {
|
|
|
82
83
|
mountIssuer(app, config.issuer, config.cors, active, config.limits.invocationTimeoutMs, config.tokenExchange);
|
|
83
84
|
}
|
|
84
85
|
if (config.publication !== undefined) {
|
|
85
|
-
mountRoutes(app, config.publication, config.delivery, handler, active, config.invocation, config.limits, config.cors, (request) => hosts.get(request), config.routeReservations, externalIssuer);
|
|
86
|
+
mountRoutes(app, config.publication, config.delivery, handler, active, config.invocation, config.limits, config.cors, (request) => hosts.get(request), config.routeReservations, externalIssuer, externalOrigin);
|
|
86
87
|
}
|
|
87
88
|
app.onError((_error, context) => new Response('{"error":"Internal server error."}', {
|
|
88
89
|
status: 500,
|
|
@@ -131,6 +132,7 @@ function captureHandler(input) {
|
|
|
131
132
|
return Object.freeze({ dispatch });
|
|
132
133
|
}
|
|
133
134
|
function admitOptions(config) {
|
|
135
|
+
const publicOrigin = config.publicOrigin === undefined ? undefined : admitPublicOrigin(config.publicOrigin);
|
|
134
136
|
const invocation = captureInvocationSource(config.invocation);
|
|
135
137
|
const limits = admitLimits(config.limits);
|
|
136
138
|
const path = admitRoute(config.path ?? '/', 'invocation path');
|
|
@@ -180,6 +182,7 @@ function admitOptions(config) {
|
|
|
180
182
|
}
|
|
181
183
|
return Object.freeze({
|
|
182
184
|
invocation,
|
|
185
|
+
...(publicOrigin === undefined ? {} : { publicOrigin }),
|
|
183
186
|
limits,
|
|
184
187
|
path,
|
|
185
188
|
cors: prepareCors(config.cors),
|
|
@@ -264,4 +267,15 @@ function admitRoute(path, label) {
|
|
|
264
267
|
}
|
|
265
268
|
return path;
|
|
266
269
|
}
|
|
267
|
-
|
|
270
|
+
function admitPublicOrigin(input) {
|
|
271
|
+
const url = new URL(input);
|
|
272
|
+
if (!['http:', 'https:'].includes(url.protocol) ||
|
|
273
|
+
url.username !== '' ||
|
|
274
|
+
url.password !== '' ||
|
|
275
|
+
url.pathname !== '/' ||
|
|
276
|
+
url.search !== '' ||
|
|
277
|
+
url.hash !== '') {
|
|
278
|
+
throw new TypeError('Server publicOrigin must be an HTTP(S) origin.');
|
|
279
|
+
}
|
|
280
|
+
return url.origin;
|
|
281
|
+
}
|
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
|
@@ -2,4 +2,3 @@ import * as codecs from '@astrale-os/kernel-protocol/codecs';
|
|
|
2
2
|
export type LoadedCodec = codecs.Codec;
|
|
3
3
|
/** Load the built-in Server representation set before pure Protocol binding selection. */
|
|
4
4
|
export declare function loadCodecs(): Promise<readonly [codecs.Codec, codecs.Codec]>;
|
|
5
|
-
//# sourceMappingURL=codecs.d.ts.map
|
package/dist/transport/codecs.js
CHANGED
|
@@ -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
|
|
@@ -15,4 +15,3 @@ export interface HttpHandlerConfig {
|
|
|
15
15
|
readonly currentPublication?: (request: Request) => PublicationSnapshot | undefined;
|
|
16
16
|
}
|
|
17
17
|
export declare function createHttpHandler(config: HttpHandlerConfig): (request: Request, host?: ApplicationHost) => Promise<Response>;
|
|
18
|
-
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -6,7 +6,7 @@ import { invocationContext, publicationRouteDecision, } from '../../invocation/i
|
|
|
6
6
|
import { readBoundedBody } from '../body.js';
|
|
7
7
|
import { loadCodecs } from '../codecs.js';
|
|
8
8
|
import { resolveCredential } from '../credential.js';
|
|
9
|
-
import { renderHttpError, renderHttpResult } from './render.js';
|
|
9
|
+
import { isStreamingBinary, renderHttpError, renderHttpResult, } from './render.js';
|
|
10
10
|
export function createHttpHandler(config) {
|
|
11
11
|
return async (request, host) => {
|
|
12
12
|
const cors = (exposedHeaders) => config.cors.response(request, exposedHeaders);
|
|
@@ -146,7 +146,8 @@ export function createHttpHandler(config) {
|
|
|
146
146
|
active,
|
|
147
147
|
maximumResponseBytes: config.maximumResponseBytes,
|
|
148
148
|
});
|
|
149
|
-
handedOff =
|
|
149
|
+
handedOff =
|
|
150
|
+
result.kind === 'stream' || (result.kind === 'binary' && isStreamingBinary(result.value));
|
|
150
151
|
return response;
|
|
151
152
|
}
|
|
152
153
|
catch (input) {
|
|
@@ -178,4 +179,3 @@ function noStore(response) {
|
|
|
178
179
|
headers.set('cache-control', 'no-store');
|
|
179
180
|
return new Response(response.body, { status: response.status, headers });
|
|
180
181
|
}
|
|
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,3 +1,4 @@
|
|
|
1
|
+
import type { BufferedBinaryResult, StreamingBinaryResult } from '@astrale-os/kernel-core/callable';
|
|
1
2
|
import type { Codec } from '@astrale-os/kernel-protocol/codecs';
|
|
2
3
|
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
3
4
|
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
@@ -19,14 +20,14 @@ export interface ErrorRenderOptions {
|
|
|
19
20
|
}
|
|
20
21
|
type HandlerResult = Awaited<ReturnType<Handler['dispatch']>>;
|
|
21
22
|
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;
|
|
23
|
+
export declare function renderBinary(requestId: protocol.RequestId, invocation: protocol.InvocationId, binary: BufferedBinaryResult): protocol.BinaryResponse;
|
|
25
24
|
export declare function renderRedirect(requestId: protocol.RequestId, invocation: protocol.InvocationId, redirect: Extract<HandlerResult, {
|
|
26
25
|
readonly kind: 'redirect';
|
|
27
26
|
}>['redirect']): protocol.RedirectResponse;
|
|
28
27
|
export declare function renderHttpError(requestId: protocol.RequestId, invocation: protocol.InvocationId, error: errors.ErrorPayload, codec: Codec, cors: CorsHeaders, options?: ErrorRenderOptions): Response;
|
|
29
28
|
export declare function renderHttpStale(requestId: protocol.RequestId, invocation: protocol.InvocationId, current: protocol.Stale['current'], codec: Codec, cors: CorsHeaders, maximumResponseBytes?: number): Response;
|
|
30
29
|
export declare function encodeEnvelope(codec: Codec, input: unknown): string | Uint8Array;
|
|
30
|
+
export declare function isStreamingBinary(input: Extract<HandlerResult, {
|
|
31
|
+
readonly kind: 'binary';
|
|
32
|
+
}>['value']): input is StreamingBinaryResult;
|
|
31
33
|
export {};
|
|
32
|
-
//# sourceMappingURL=render.d.ts.map
|
|
@@ -19,6 +19,8 @@ export function renderHttpResult(result, context) {
|
|
|
19
19
|
case 'miss':
|
|
20
20
|
return settled(context.active, envelopeResponse(context.codec, protocol.miss(context.requestId, context.invocation), context.cors(), 200, true, context.maximumResponseBytes));
|
|
21
21
|
case 'binary': {
|
|
22
|
+
if (isStreamingBinary(result.value))
|
|
23
|
+
return streamingBinaryHttpResponse(result.value, context);
|
|
22
24
|
const binary = renderBinary(context.requestId, context.invocation, result.value);
|
|
23
25
|
return settled(context.active, binaryHttpResponse(binary, context.cors, context.maximumResponseBytes));
|
|
24
26
|
}
|
|
@@ -41,6 +43,7 @@ export function renderBinary(requestId, invocation, binary) {
|
|
|
41
43
|
invocation,
|
|
42
44
|
body: binary.body,
|
|
43
45
|
mediaType: binary.mediaType,
|
|
46
|
+
status: binary.status ?? 200,
|
|
44
47
|
...(binary.headers === undefined ? {} : { headers: binary.headers }),
|
|
45
48
|
});
|
|
46
49
|
}
|
|
@@ -75,11 +78,81 @@ function envelopeResponse(codec, input, cors, status = 200, negotiated = true, m
|
|
|
75
78
|
}
|
|
76
79
|
function binaryHttpResponse(binary, cors, maximumResponseBytes) {
|
|
77
80
|
requireResponseBound(binary.body, maximumResponseBytes);
|
|
81
|
+
const headers = binaryHttpHeaders(binary, cors, false);
|
|
82
|
+
return new Response(toBody(binary.body), { status: binary.status, headers });
|
|
83
|
+
}
|
|
84
|
+
function streamingBinaryHttpResponse(binary, context) {
|
|
85
|
+
const iterator = binary.body[Symbol.asyncIterator]();
|
|
86
|
+
const headers = binaryHttpHeaders(Object.freeze({
|
|
87
|
+
requestId: context.requestId,
|
|
88
|
+
invocation: context.invocation,
|
|
89
|
+
mediaType: binary.mediaType,
|
|
90
|
+
headers: binary.headers,
|
|
91
|
+
}), context.cors, true);
|
|
92
|
+
let terminal = false;
|
|
93
|
+
let controller;
|
|
94
|
+
let stopping;
|
|
95
|
+
const stop = (reason = 'Binary response was interrupted.') => {
|
|
96
|
+
if (terminal)
|
|
97
|
+
return stopping ?? Promise.resolve();
|
|
98
|
+
terminal = true;
|
|
99
|
+
controller?.error(new Error(reason));
|
|
100
|
+
stopping = Promise.resolve().then(async () => {
|
|
101
|
+
try {
|
|
102
|
+
await iterator.return?.();
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
context.active.settle();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return stopping;
|
|
109
|
+
};
|
|
110
|
+
const body = new ReadableStream({
|
|
111
|
+
start(output) {
|
|
112
|
+
controller = output;
|
|
113
|
+
},
|
|
114
|
+
async pull(controller) {
|
|
115
|
+
if (terminal)
|
|
116
|
+
return;
|
|
117
|
+
try {
|
|
118
|
+
const next = await iterator.next();
|
|
119
|
+
if (terminal)
|
|
120
|
+
return;
|
|
121
|
+
if (next.done) {
|
|
122
|
+
terminal = true;
|
|
123
|
+
controller.close();
|
|
124
|
+
context.active.settle();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
controller.enqueue(next.value);
|
|
128
|
+
}
|
|
129
|
+
catch (cause) {
|
|
130
|
+
if (terminal)
|
|
131
|
+
return;
|
|
132
|
+
terminal = true;
|
|
133
|
+
controller.error(cause);
|
|
134
|
+
context.active.settle();
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
async cancel(reason) {
|
|
138
|
+
const message = typeof reason === 'string' ? reason : 'binary response consumer cancelled';
|
|
139
|
+
context.active.cancel(message);
|
|
140
|
+
await stop();
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
context.active.onCancel(stop);
|
|
144
|
+
return new Response(body, { status: binary.status ?? 200, headers });
|
|
145
|
+
}
|
|
146
|
+
export function isStreamingBinary(input) {
|
|
147
|
+
return !(input.body instanceof Uint8Array);
|
|
148
|
+
}
|
|
149
|
+
function binaryHttpHeaders(binary, cors, streaming) {
|
|
78
150
|
const applicationHeaders = Object.keys(binary.headers ?? {}).filter((name) => protocol.http.isApplicationBinaryHeader(name));
|
|
79
151
|
const headers = new Headers(cors([
|
|
80
152
|
protocol.http.REQUEST_ID_FIELD,
|
|
81
153
|
protocol.http.INVOCATION_FIELD,
|
|
82
154
|
protocol.http.BINARY_HEADERS_FIELD,
|
|
155
|
+
...(streaming ? [protocol.http.BINARY_DELIVERY_FIELD] : []),
|
|
83
156
|
...applicationHeaders,
|
|
84
157
|
]));
|
|
85
158
|
for (const [name, value] of Object.entries(binary.headers ?? {})) {
|
|
@@ -90,9 +163,12 @@ function binaryHttpResponse(binary, cors, maximumResponseBytes) {
|
|
|
90
163
|
headers.set(protocol.http.BINARY_HEADERS_FIELD, protocol.http.encodeBinaryHeaderNames(binary.headers));
|
|
91
164
|
headers.set(protocol.http.REQUEST_ID_FIELD, String(binary.requestId));
|
|
92
165
|
headers.set(protocol.http.INVOCATION_FIELD, protocol.http.encodeInvocationId(binary.invocation));
|
|
166
|
+
if (streaming) {
|
|
167
|
+
headers.set(protocol.http.BINARY_DELIVERY_FIELD, protocol.http.STREAMING_BINARY_DELIVERY);
|
|
168
|
+
}
|
|
93
169
|
headers.set('cache-control', 'no-store');
|
|
94
170
|
appendVary(headers, 'accept');
|
|
95
|
-
return
|
|
171
|
+
return headers;
|
|
96
172
|
}
|
|
97
173
|
function streamHttpResponse(stream, context) {
|
|
98
174
|
const iterator = stream[Symbol.asyncIterator]();
|
|
@@ -286,4 +362,3 @@ function toBody(input) {
|
|
|
286
362
|
const copy = new Uint8Array(input);
|
|
287
363
|
return copy.buffer;
|
|
288
364
|
}
|
|
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
|
@@ -5,7 +5,7 @@ import { ResponseBodyTooLargeError } from '../../errors/index.js';
|
|
|
5
5
|
import { invocationContext, publicationRouteDecision, } from '../../invocation/index.js';
|
|
6
6
|
import { byteLength } from '../body.js';
|
|
7
7
|
import { resolveCredential } from '../credential.js';
|
|
8
|
-
import { renderBinary, renderRedirect } from '../http/render.js';
|
|
8
|
+
import { isStreamingBinary, renderBinary, renderRedirect } from '../http/render.js';
|
|
9
9
|
import { interruptedStreamError } from '../stream-error.js';
|
|
10
10
|
const WS_OPEN = 1;
|
|
11
11
|
/** Build one socket-pinned transport session. */
|
|
@@ -211,6 +211,17 @@ async function sendResult(socket, codec, requestId, assigned, result, operation,
|
|
|
211
211
|
await send(socket, codec, protocol.miss(requestId, assigned), sendOutput);
|
|
212
212
|
return;
|
|
213
213
|
case 'binary': {
|
|
214
|
+
if (isStreamingBinary(result.value)) {
|
|
215
|
+
const streaming = result.value;
|
|
216
|
+
await Promise.allSettled([
|
|
217
|
+
sendError(socket, codec, requestId, assigned, {
|
|
218
|
+
code: errors.CODES.UNSUPPORTED,
|
|
219
|
+
message: 'Streaming binary output requires the HTTP transport.',
|
|
220
|
+
}, sendOutput),
|
|
221
|
+
Promise.resolve().then(() => streaming.body[Symbol.asyncIterator]().return?.()),
|
|
222
|
+
]);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
214
225
|
const binary = renderBinary(requestId, assigned, result.value);
|
|
215
226
|
if (socket.readyState === WS_OPEN) {
|
|
216
227
|
await sendOutput(socket, protocol.websocket.encodeBinary(binary));
|
|
@@ -333,4 +344,3 @@ function isCancelRequest(input) {
|
|
|
333
344
|
!Array.isArray(input) &&
|
|
334
345
|
input.kind === 'cancel');
|
|
335
346
|
}
|
|
336
|
-
//# sourceMappingURL=session.js.map
|
package/dist/types.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { PublicationSource } from './publication/index.js';
|
|
|
6
6
|
import type { Application, WebSocketAdapter } from './transport/index.js';
|
|
7
7
|
export interface ServerLimits {
|
|
8
8
|
readonly maximumRequestBytes: number;
|
|
9
|
-
/** Maximum bytes in one encoded envelope,
|
|
9
|
+
/** Maximum bytes in one encoded envelope, buffered binary body, or stream frame. */
|
|
10
10
|
readonly maximumResponseBytes?: number;
|
|
11
11
|
/** Maximum Server-owned active operations across all mounted protocol surfaces. */
|
|
12
12
|
readonly maximumConcurrentOperations?: number;
|
|
@@ -37,6 +37,8 @@ export interface ServerOptions {
|
|
|
37
37
|
readonly probe: HealthProbe;
|
|
38
38
|
} | false;
|
|
39
39
|
readonly issuer?: Issuer;
|
|
40
|
+
/** Public HTTP origin for transport placement, independent of the stable issuer. */
|
|
41
|
+
readonly publicOrigin?: string;
|
|
40
42
|
/** Present only for the configured self issuer; owns verification and signing. */
|
|
41
43
|
readonly tokenExchange?: exchange.Provider;
|
|
42
44
|
readonly publication?: PublicationSource;
|
|
@@ -63,4 +65,3 @@ export type { ApplicationHost, ExecutionControl, Handler, HandlerRequest, Handle
|
|
|
63
65
|
export type { exchange, Issuer } from './issuer/index.js';
|
|
64
66
|
export type { PublicationSnapshot, PublicationSnapshotInput, PublicationSource, } from './publication/index.js';
|
|
65
67
|
export type { Application, WebSocketAdapter, WebSocketHandlers, WebSocketLike, WebSocketUpgradeOptions, } from './transport/index.js';
|
|
66
|
-
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
CHANGED