@astrale-os/kernel-protocol 0.5.0-beta.60 → 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/artifact/index.d.ts +1 -1
- package/dist/artifact/index.js +1 -1
- package/dist/artifact/ref.d.ts +2 -0
- package/dist/artifact/ref.js +4 -3
- package/dist/invocation/binary.js +7 -3
- package/dist/invocation/http/index.d.ts +1 -0
- package/dist/invocation/http/index.js +1 -0
- package/dist/invocation/http/request.d.ts +9 -0
- package/dist/invocation/http/request.js +61 -0
- package/dist/routes/compiler.js +4 -0
- package/package.json +3 -3
package/dist/artifact/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { accept, identify, type Digest, type Ref } from './ref.js';
|
|
1
|
+
export { accept, canonicalMediaType, identify, type Digest, type Ref } from './ref.js';
|
package/dist/artifact/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { accept, identify } from './ref.js';
|
|
1
|
+
export { accept, canonicalMediaType, identify } from './ref.js';
|
package/dist/artifact/ref.d.ts
CHANGED
|
@@ -8,3 +8,5 @@ export interface Ref {
|
|
|
8
8
|
export declare function accept(input: unknown): Ref;
|
|
9
9
|
/** Identify exact bytes under one canonical media type. */
|
|
10
10
|
export declare function identify(bytes: Uint8Array, mediaType: string): Ref;
|
|
11
|
+
/** Canonicalize one portable artifact media type, retaining token parameters and their values. */
|
|
12
|
+
export declare function canonicalMediaType(input: string): string;
|
package/dist/artifact/ref.js
CHANGED
|
@@ -18,7 +18,7 @@ export function accept(input) {
|
|
|
18
18
|
}
|
|
19
19
|
return Object.freeze({
|
|
20
20
|
digest: value.digest,
|
|
21
|
-
mediaType:
|
|
21
|
+
mediaType: canonicalMediaType(value.mediaType),
|
|
22
22
|
size: value.size,
|
|
23
23
|
});
|
|
24
24
|
}
|
|
@@ -31,11 +31,12 @@ export function identify(bytes, mediaType) {
|
|
|
31
31
|
hexadecimal += byte.toString(16).padStart(2, '0');
|
|
32
32
|
return Object.freeze({
|
|
33
33
|
digest: `sha256:${hexadecimal}`,
|
|
34
|
-
mediaType:
|
|
34
|
+
mediaType: canonicalMediaType(mediaType),
|
|
35
35
|
size: bytes.byteLength,
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
/** Canonicalize one portable artifact media type, retaining token parameters and their values. */
|
|
39
|
+
export function canonicalMediaType(input) {
|
|
39
40
|
const [essence, ...parametersInput] = input.split(';');
|
|
40
41
|
const [rawType, rawSubtype, ...extra] = essence.trim().split('/');
|
|
41
42
|
if (rawType === undefined ||
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalMediaType } from '../artifact/index.js';
|
|
1
2
|
import { acceptHeaders, exactDataObject, exactFields, invalid, invocation, requestId, required, string, } from './admission.js';
|
|
2
3
|
const FIELDS = new Set([
|
|
3
4
|
'kind',
|
|
@@ -8,7 +9,6 @@ const FIELDS = new Set([
|
|
|
8
9
|
'status',
|
|
9
10
|
'headers',
|
|
10
11
|
]);
|
|
11
|
-
const MEDIA_TYPE = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u;
|
|
12
12
|
export function decodeBinaryResponse(input) {
|
|
13
13
|
const object = exactDataObject(input, 'binary response');
|
|
14
14
|
exactFields(object, FIELDS, 'binary response');
|
|
@@ -17,9 +17,13 @@ export function decodeBinaryResponse(input) {
|
|
|
17
17
|
invalid('binary response.kind must be "binary"');
|
|
18
18
|
if (!(object.body instanceof Uint8Array))
|
|
19
19
|
invalid('binary response.body must be a Uint8Array');
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
let mediaType;
|
|
21
|
+
try {
|
|
22
|
+
mediaType = canonicalMediaType(string(object.mediaType, 'binary response.mediaType'));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
22
25
|
invalid('binary response.mediaType is invalid');
|
|
26
|
+
}
|
|
23
27
|
const status = object.status ?? 200;
|
|
24
28
|
if (!Number.isInteger(status) || status < 200 || status > 599) {
|
|
25
29
|
invalid('binary response.status is invalid');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { BINARY_DELIVERY_FIELD, BINARY_HEADERS_FIELD, decodeBinaryHeaderNames, decodeInvocationId, encodeBinaryHeaderNames, encodeInvocationId, isApplicationBinaryHeader, MAXIMUM_INVOCATION_FIELD_BYTES, INVOCATION_FIELD, REQUEST_ID_FIELD, STREAMING_BINARY_DELIVERY, } from './binary.js';
|
|
2
2
|
export { codecForContentType, negotiateResponseCodec } from './negotiation.js';
|
|
3
|
+
export { BINARY_LENGTH_FIELD, BINARY_REQUEST_CONTENT_TYPE, decodeBinaryRequestEnvelope, encodeBinaryRequestEnvelope, MAXIMUM_REQUEST_ENVELOPE_BYTES, REQUEST_ENVELOPE_FIELD, } from './request.js';
|
|
3
4
|
export { encodeStreamFrame, LENGTH_PREFIX_V1, STREAM_FRAMING_FIELD } from './stream.js';
|
|
4
5
|
export { BAGGAGE_FIELD, decodeTraceHeaders, encodeTraceHeaders, TRACEPARENT_FIELD, TRACESTATE_FIELD, } from './trace.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { BINARY_DELIVERY_FIELD, BINARY_HEADERS_FIELD, decodeBinaryHeaderNames, decodeInvocationId, encodeBinaryHeaderNames, encodeInvocationId, isApplicationBinaryHeader, MAXIMUM_INVOCATION_FIELD_BYTES, INVOCATION_FIELD, REQUEST_ID_FIELD, STREAMING_BINARY_DELIVERY, } from './binary.js';
|
|
2
2
|
export { codecForContentType, negotiateResponseCodec } from './negotiation.js';
|
|
3
|
+
export { BINARY_LENGTH_FIELD, BINARY_REQUEST_CONTENT_TYPE, decodeBinaryRequestEnvelope, encodeBinaryRequestEnvelope, MAXIMUM_REQUEST_ENVELOPE_BYTES, REQUEST_ENVELOPE_FIELD, } from './request.js';
|
|
3
4
|
export { encodeStreamFrame, LENGTH_PREFIX_V1, STREAM_FRAMING_FIELD } from './stream.js';
|
|
4
5
|
export { BAGGAGE_FIELD, decodeTraceHeaders, encodeTraceHeaders, TRACEPARENT_FIELD, TRACESTATE_FIELD, } from './trace.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Request } from '../request.js';
|
|
2
|
+
export declare const REQUEST_ENVELOPE_FIELD: 'x-astrale-request';
|
|
3
|
+
export declare const BINARY_LENGTH_FIELD: 'x-astrale-binary-length';
|
|
4
|
+
export declare const BINARY_REQUEST_CONTENT_TYPE: 'application/octet-stream';
|
|
5
|
+
export declare const MAXIMUM_REQUEST_ENVELOPE_BYTES = 8192;
|
|
6
|
+
/** Encode one canonical V2 Request into the binary-request HTTP field. */
|
|
7
|
+
export declare function encodeBinaryRequestEnvelope(input: Request): string;
|
|
8
|
+
/** Decode one bounded base64url JSON V2 Request from the binary-request HTTP field. */
|
|
9
|
+
export declare function decodeBinaryRequestEnvelope(input: string | null): Request;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { json } from '../../codecs/index.js';
|
|
2
|
+
import { CODES, ProtocolError } from '../../errors/index.js';
|
|
3
|
+
import { decodeRequest } from '../request.js';
|
|
4
|
+
export const REQUEST_ENVELOPE_FIELD = 'x-astrale-request';
|
|
5
|
+
export const BINARY_LENGTH_FIELD = 'x-astrale-binary-length';
|
|
6
|
+
export const BINARY_REQUEST_CONTENT_TYPE = 'application/octet-stream';
|
|
7
|
+
export const MAXIMUM_REQUEST_ENVELOPE_BYTES = 8_192;
|
|
8
|
+
const BASE64URL = /^[A-Za-z0-9_-]+$/u;
|
|
9
|
+
const MAXIMUM_ENCODED_BYTES = Math.ceil(MAXIMUM_REQUEST_ENVELOPE_BYTES / 3) * 4;
|
|
10
|
+
/** Encode one canonical V2 Request into the binary-request HTTP field. */
|
|
11
|
+
export function encodeBinaryRequestEnvelope(input) {
|
|
12
|
+
const request = decodeRequest(input);
|
|
13
|
+
const encoded = json.encode(request);
|
|
14
|
+
if (typeof encoded !== 'string')
|
|
15
|
+
throw new TypeError('JSON Request codec returned non-text output.');
|
|
16
|
+
const bytes = new TextEncoder().encode(encoded);
|
|
17
|
+
if (bytes.byteLength > MAXIMUM_REQUEST_ENVELOPE_BYTES)
|
|
18
|
+
return invalid();
|
|
19
|
+
return btoa(String.fromCharCode(...bytes))
|
|
20
|
+
.replaceAll('+', '-')
|
|
21
|
+
.replaceAll('/', '_')
|
|
22
|
+
.replace(/=+$/u, '');
|
|
23
|
+
}
|
|
24
|
+
/** Decode one bounded base64url JSON V2 Request from the binary-request HTTP field. */
|
|
25
|
+
export function decodeBinaryRequestEnvelope(input) {
|
|
26
|
+
if (input === null ||
|
|
27
|
+
input.length === 0 ||
|
|
28
|
+
input.length > MAXIMUM_ENCODED_BYTES ||
|
|
29
|
+
!BASE64URL.test(input)) {
|
|
30
|
+
return invalid();
|
|
31
|
+
}
|
|
32
|
+
let bytes;
|
|
33
|
+
try {
|
|
34
|
+
const binary = atob(input.replaceAll('-', '+').replaceAll('_', '/') + '='.repeat((4 - (input.length % 4)) % 4));
|
|
35
|
+
bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return invalid();
|
|
39
|
+
}
|
|
40
|
+
if (bytes.byteLength > MAXIMUM_REQUEST_ENVELOPE_BYTES)
|
|
41
|
+
return invalid();
|
|
42
|
+
try {
|
|
43
|
+
// Require canonical base64url so alternate encodings cannot name the same envelope.
|
|
44
|
+
const canonical = btoa(String.fromCharCode(...bytes))
|
|
45
|
+
.replaceAll('+', '-')
|
|
46
|
+
.replaceAll('/', '_')
|
|
47
|
+
.replace(/=+$/u, '');
|
|
48
|
+
if (canonical !== input)
|
|
49
|
+
return invalid();
|
|
50
|
+
return decodeRequest(json.decode(bytes));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return invalid();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function invalid() {
|
|
57
|
+
throw new ProtocolError({
|
|
58
|
+
code: CODES.INVALID_REQUEST,
|
|
59
|
+
message: 'HTTP binary Request envelope is invalid.',
|
|
60
|
+
});
|
|
61
|
+
}
|
package/dist/routes/compiler.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isBinaryInput } from '@astrale-os/kernel-dsl/v1';
|
|
1
2
|
import { admitRouteDeclaration } from '../publication/route.js';
|
|
2
3
|
import { rejectCollisions } from './collision.js';
|
|
3
4
|
import { fail } from './error.js';
|
|
@@ -14,6 +15,9 @@ export function compile(input) {
|
|
|
14
15
|
if (meaning === undefined || (isMethod(meaning) && !meaning.executable)) {
|
|
15
16
|
fail('NATIVE_ROUTE_BINDING_INVALID', `Unavailable callable ${declaration.callable}.`);
|
|
16
17
|
}
|
|
18
|
+
if (isBinaryInput(meaning.definition.input)) {
|
|
19
|
+
fail('NATIVE_ROUTE_BINDING_INVALID', `Native route ${declaration.name} cannot bind a binary-input Function.`);
|
|
20
|
+
}
|
|
17
21
|
const href = placePath(declaration.path, 'https://routes.invalid');
|
|
18
22
|
const route = { ...declaration, href };
|
|
19
23
|
validateTemplate(href);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-os/kernel-protocol",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.61",
|
|
4
4
|
"description": "Portable Astrale invocation, publication, installation, routing, and issuer protocols",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -137,8 +137,8 @@
|
|
|
137
137
|
"access": "public"
|
|
138
138
|
},
|
|
139
139
|
"dependencies": {
|
|
140
|
-
"@astrale-os/kernel-core": "0.9.0-beta.
|
|
141
|
-
"@astrale-os/kernel-dsl": "0.2.0-beta.
|
|
140
|
+
"@astrale-os/kernel-core": "0.9.0-beta.55",
|
|
141
|
+
"@astrale-os/kernel-dsl": "0.2.0-beta.40",
|
|
142
142
|
"@noble/hashes": "2.3.0"
|
|
143
143
|
},
|
|
144
144
|
"devDependencies": {
|