@astrale-os/kernel-server 0.5.0-beta.66 → 0.5.0-beta.67
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/errors/classify.d.ts +8 -0
- package/dist/errors/classify.js +15 -0
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/routes/routes.js +4 -28
- package/dist/transport/http/handler.js +3 -16
- package/dist/transport/http/render.js +11 -4
- package/dist/transport/websocket/session.js +2 -9
- package/package.json +4 -4
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
2
2
|
/** Convert only errors owned by an admitted Server or Protocol boundary into safe public data. */
|
|
3
3
|
export declare function classifyError(input: unknown): errors.ErrorPayload;
|
|
4
|
+
/**
|
|
5
|
+
* Classify the failure of one request. Its own deadline or cancellation names the failure, except an
|
|
6
|
+
* outcome already unknown: ending the request proves nothing about an effect.
|
|
7
|
+
*/
|
|
8
|
+
export declare function classifyRequestError(input: unknown, request: {
|
|
9
|
+
readonly timedOut: boolean;
|
|
10
|
+
readonly signal: AbortSignal;
|
|
11
|
+
}): errors.ErrorPayload;
|
package/dist/errors/classify.js
CHANGED
|
@@ -17,6 +17,21 @@ export function classifyError(input) {
|
|
|
17
17
|
}
|
|
18
18
|
return internal(input);
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Classify the failure of one request. Its own deadline or cancellation names the failure, except an
|
|
22
|
+
* outcome already unknown: ending the request proves nothing about an effect.
|
|
23
|
+
*/
|
|
24
|
+
export function classifyRequestError(input, request) {
|
|
25
|
+
if (input instanceof errors.ProtocolError &&
|
|
26
|
+
input.payload.code === errors.CODES.OUTCOME_UNKNOWN) {
|
|
27
|
+
return input.payload;
|
|
28
|
+
}
|
|
29
|
+
if (request.timedOut)
|
|
30
|
+
return publicError('DEADLINE_EXCEEDED', 'Invocation deadline exceeded.');
|
|
31
|
+
if (request.signal.aborted)
|
|
32
|
+
return publicError('CANCELLED', 'Invocation was cancelled.');
|
|
33
|
+
return classifyError(input);
|
|
34
|
+
}
|
|
20
35
|
function publicError(name, message) {
|
|
21
36
|
return { code: errors.CODES[name], message };
|
|
22
37
|
}
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ADMISSION_RETRY_AFTER_MS, admissionRefusal, retryAfterHeaders } from './admission.js';
|
|
2
2
|
export { DeliveryUnavailableError, IssuerKeysUnavailableError } from './availability.js';
|
|
3
|
-
export { classifyError } from './classify.js';
|
|
3
|
+
export { classifyError, classifyRequestError } from './classify.js';
|
|
4
4
|
export { RequestBodyInvalidError, RequestBodyTooLargeError } from './request-body.js';
|
|
5
5
|
export { ResponseBodyTooLargeError } from './response-body.js';
|
package/dist/errors/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ADMISSION_RETRY_AFTER_MS, admissionRefusal, retryAfterHeaders } from './admission.js';
|
|
2
2
|
export { DeliveryUnavailableError, IssuerKeysUnavailableError } from './availability.js';
|
|
3
|
-
export { classifyError } from './classify.js';
|
|
3
|
+
export { classifyError, classifyRequestError } from './classify.js';
|
|
4
4
|
export { RequestBodyInvalidError, RequestBodyTooLargeError } from './request-body.js';
|
|
5
5
|
export { ResponseBodyTooLargeError } from './response-body.js';
|
package/dist/routes/routes.js
CHANGED
|
@@ -5,7 +5,7 @@ import { bundleUrl } from '@astrale-os/kernel-protocol/publication';
|
|
|
5
5
|
import { mediaType as publicationMediaType, PATH as PUBLICATION_PATH, url as publicationUrl, } from '@astrale-os/kernel-protocol/publication/publication';
|
|
6
6
|
import { reference as publicationReference } from '@astrale-os/kernel-protocol/publication/reference';
|
|
7
7
|
import * as routes from '@astrale-os/kernel-protocol/routes';
|
|
8
|
-
import { admissionRefusal, classifyError, RequestBodyInvalidError, RequestBodyTooLargeError, retryAfterHeaders, } from '../errors/index.js';
|
|
8
|
+
import { admissionRefusal, classifyError, classifyRequestError, RequestBodyInvalidError, RequestBodyTooLargeError, retryAfterHeaders, } from '../errors/index.js';
|
|
9
9
|
import { invocationContext } from '../invocation/index.js';
|
|
10
10
|
import { handleDelivery } from '../publication/delivery/index.js';
|
|
11
11
|
import { readPreparedPublicationSnapshot, } from '../publication/index.js';
|
|
@@ -203,16 +203,7 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
|
|
|
203
203
|
if (cause instanceof routes.RouteError) {
|
|
204
204
|
return nativeError({ code: errors.CODES.INVALID_REQUEST, message: 'Native route request is invalid.' }, 400, corsFor);
|
|
205
205
|
}
|
|
206
|
-
|
|
207
|
-
return nativeError({
|
|
208
|
-
code: errors.CODES.DEADLINE_EXCEEDED,
|
|
209
|
-
message: 'Invocation deadline exceeded.',
|
|
210
|
-
}, 504, corsFor);
|
|
211
|
-
}
|
|
212
|
-
if (operation.signal.aborted) {
|
|
213
|
-
return nativeError({ code: errors.CODES.CANCELLED, message: 'Invocation was cancelled.' }, 408, corsFor);
|
|
214
|
-
}
|
|
215
|
-
const payload = classifyError(cause);
|
|
206
|
+
const payload = classifyRequestError(cause, operation);
|
|
216
207
|
return nativeError(payload, errors.httpStatusOf(payload), corsFor);
|
|
217
208
|
}
|
|
218
209
|
finally {
|
|
@@ -396,13 +387,13 @@ async function streamResponse(stream, operation, cors) {
|
|
|
396
387
|
const cleanup = cancelSetup('native stream setup failed');
|
|
397
388
|
if (!operation.signal.aborted)
|
|
398
389
|
await cleanup;
|
|
399
|
-
const payload =
|
|
390
|
+
const payload = classifyRequestError(cause, operation);
|
|
400
391
|
return nativeError(payload, errors.httpStatusOf(payload), cors);
|
|
401
392
|
}
|
|
402
393
|
unregisterSetupCancellation();
|
|
403
394
|
if (operation.signal.aborted) {
|
|
404
395
|
void cancelSetup('native stream setup cancelled');
|
|
405
|
-
const payload =
|
|
396
|
+
const payload = classifyRequestError(operation.signal.reason, operation);
|
|
406
397
|
return response({ error: payload }, errors.httpStatusOf(payload), cors());
|
|
407
398
|
}
|
|
408
399
|
const encoder = new TextEncoder();
|
|
@@ -487,21 +478,6 @@ async function streamResponse(stream, operation, cors) {
|
|
|
487
478
|
},
|
|
488
479
|
});
|
|
489
480
|
}
|
|
490
|
-
function preStreamError(cause, operation) {
|
|
491
|
-
if (operation.timedOut) {
|
|
492
|
-
return {
|
|
493
|
-
code: errors.CODES.DEADLINE_EXCEEDED,
|
|
494
|
-
message: 'Invocation deadline exceeded before the native stream began.',
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
if (operation.signal.aborted) {
|
|
498
|
-
return {
|
|
499
|
-
code: errors.CODES.CANCELLED,
|
|
500
|
-
message: 'Invocation was cancelled before the native stream began.',
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
return classifyError(cause);
|
|
504
|
-
}
|
|
505
481
|
function sameRoute(left, right, externalOrigin) {
|
|
506
482
|
const a = new URL(left);
|
|
507
483
|
const b = new URL(right);
|
|
@@ -1,7 +1,7 @@
|
|
|
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 { admissionRefusal, classifyError, RequestBodyInvalidError, RequestBodyTooLargeError, } from '../../errors/index.js';
|
|
4
|
+
import { admissionRefusal, classifyError, classifyRequestError, RequestBodyInvalidError, RequestBodyTooLargeError, } from '../../errors/index.js';
|
|
5
5
|
import { invocationContext, publicationRouteDecision, } from '../../invocation/index.js';
|
|
6
6
|
import { readBoundedBody } from '../body.js';
|
|
7
7
|
import { loadCodecs } from '../codecs.js';
|
|
@@ -137,11 +137,7 @@ export function createHttpHandler(config) {
|
|
|
137
137
|
active.extend(config.binaryDownloadTimeoutMs);
|
|
138
138
|
}
|
|
139
139
|
if (active.signal.aborted) {
|
|
140
|
-
|
|
141
|
-
return renderError(envelope.requestId, {
|
|
142
|
-
code: timedOut ? errors.CODES.DEADLINE_EXCEEDED : errors.CODES.CANCELLED,
|
|
143
|
-
message: timedOut ? 'Invocation deadline exceeded.' : 'Invocation was cancelled.',
|
|
144
|
-
}, outgoing, { status: timedOut ? 504 : 408 });
|
|
140
|
+
return renderError(envelope.requestId, classifyRequestError(active.signal.reason, active), outgoing);
|
|
145
141
|
}
|
|
146
142
|
const publication = config.currentPublication?.(request);
|
|
147
143
|
if (config.currentPublication !== undefined && publication === undefined) {
|
|
@@ -202,16 +198,7 @@ export function createHttpHandler(config) {
|
|
|
202
198
|
return response;
|
|
203
199
|
}
|
|
204
200
|
catch (input) {
|
|
205
|
-
|
|
206
|
-
return renderError(envelope.requestId, {
|
|
207
|
-
code: errors.CODES.DEADLINE_EXCEEDED,
|
|
208
|
-
message: 'Invocation deadline exceeded.',
|
|
209
|
-
}, outgoing, { status: 504 });
|
|
210
|
-
}
|
|
211
|
-
if (active.signal.aborted) {
|
|
212
|
-
return renderError(envelope.requestId, { code: errors.CODES.CANCELLED, message: 'Invocation was cancelled.' }, outgoing, { status: 408 });
|
|
213
|
-
}
|
|
214
|
-
return renderError(envelope.requestId, classifyError(input), outgoing);
|
|
201
|
+
return renderError(envelope.requestId, classifyRequestError(input, active), outgoing);
|
|
215
202
|
}
|
|
216
203
|
}
|
|
217
204
|
catch (input) {
|
|
@@ -105,10 +105,15 @@ function streamingBinaryHttpResponse(binary, context) {
|
|
|
105
105
|
mediaType: binary.mediaType,
|
|
106
106
|
headers: binary.headers,
|
|
107
107
|
}), context.cors, true);
|
|
108
|
-
if (binary.length !== undefined)
|
|
108
|
+
if (binary.length !== undefined) {
|
|
109
109
|
headers.set('content-length', String(binary.length));
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
// Content-Length belongs to HTTP and an intermediary may rewrite it; the Client counts the
|
|
111
|
+
// delivered bytes against this protocol field instead.
|
|
112
|
+
headers.set(protocol.http.BINARY_LENGTH_FIELD, String(binary.length));
|
|
113
|
+
}
|
|
114
|
+
// Clients that predate the protocol length count against Content-Length, which an intermediary
|
|
115
|
+
// that re-encodes the stream would rewrite, so a compressible media type would fail delivery.
|
|
116
|
+
headers.set('cache-control', delivery === undefined ? 'no-store, no-transform' : 'private, no-store, no-transform');
|
|
112
117
|
let terminal = false;
|
|
113
118
|
let delivered = 0;
|
|
114
119
|
let controller;
|
|
@@ -207,7 +212,9 @@ function binaryHttpHeaders(binary, cors, streaming) {
|
|
|
207
212
|
protocol.http.REQUEST_ID_FIELD,
|
|
208
213
|
protocol.http.INVOCATION_FIELD,
|
|
209
214
|
protocol.http.BINARY_HEADERS_FIELD,
|
|
210
|
-
...(streaming
|
|
215
|
+
...(streaming
|
|
216
|
+
? [protocol.http.BINARY_DELIVERY_FIELD, protocol.http.BINARY_LENGTH_FIELD]
|
|
217
|
+
: []),
|
|
211
218
|
...applicationHeaders,
|
|
212
219
|
]));
|
|
213
220
|
for (const [name, value] of Object.entries(binary.headers ?? {})) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
2
2
|
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
3
|
-
import { admissionRefusal, classifyError } from '../../errors/index.js';
|
|
3
|
+
import { admissionRefusal, classifyError, classifyRequestError } from '../../errors/index.js';
|
|
4
4
|
import { ResponseBodyTooLargeError } from '../../errors/index.js';
|
|
5
5
|
import { invocationContext, publicationRouteDecision, } from '../../invocation/index.js';
|
|
6
6
|
import { byteLength } from '../body.js';
|
|
@@ -152,14 +152,7 @@ export function createWebSocketSession(config) {
|
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
154
|
if (socket.readyState === WS_OPEN) {
|
|
155
|
-
const error = operation
|
|
156
|
-
? {
|
|
157
|
-
code: errors.CODES.DEADLINE_EXCEEDED,
|
|
158
|
-
message: 'Invocation deadline exceeded.',
|
|
159
|
-
}
|
|
160
|
-
: operation.signal.aborted
|
|
161
|
-
? { code: errors.CODES.CANCELLED, message: 'Invocation was cancelled.' }
|
|
162
|
-
: classifyError(input);
|
|
155
|
+
const error = classifyRequestError(input, operation);
|
|
163
156
|
if (error.code === errors.CODES.INTERNAL_ERROR) {
|
|
164
157
|
socket.close(1011, 'Internal error');
|
|
165
158
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-os/kernel-server",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.67",
|
|
4
4
|
"description": "Portable HTTP and WebSocket boundary for Astrale invocation, publication, installation, and issuer protocols",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@astrale-os/kernel-core": "0.9.0-beta.
|
|
37
|
-
"@astrale-os/kernel-dsl": "0.2.0-beta.
|
|
38
|
-
"@astrale-os/kernel-protocol": "0.5.0-beta.
|
|
36
|
+
"@astrale-os/kernel-core": "0.9.0-beta.59",
|
|
37
|
+
"@astrale-os/kernel-dsl": "0.2.0-beta.43",
|
|
38
|
+
"@astrale-os/kernel-protocol": "0.5.0-beta.65",
|
|
39
39
|
"hono": "^4.6.20",
|
|
40
40
|
"msgpackr": "^2.0.5"
|
|
41
41
|
},
|