@astrale-os/kernel-protocol 0.5.0-beta.67 → 0.5.0-beta.68
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/codecs/index.d.ts +1 -2
- package/dist/codecs/index.js +1 -2
- package/dist/codecs/media-types.d.ts +0 -1
- package/dist/codecs/media-types.js +0 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/invocation/admission.d.ts +2 -2
- package/dist/invocation/admission.js +5 -5
- package/dist/invocation/binding-codecs.d.ts +1 -1
- package/dist/invocation/binding-codecs.js +1 -6
- package/dist/invocation/http/negotiation.js +3 -10
- package/dist/invocation/index.d.ts +5 -2
- package/dist/invocation/index.js +4 -2
- package/dist/invocation/legacy/index.d.ts +29 -0
- package/dist/invocation/legacy/index.js +28 -0
- package/dist/invocation/legacy/route.d.ts +14 -0
- package/dist/invocation/legacy/route.js +25 -0
- package/dist/invocation/legacy/via.d.ts +30 -0
- package/dist/invocation/legacy/via.js +15 -0
- package/dist/invocation/redirect.d.ts +15 -12
- package/dist/invocation/redirect.js +9 -21
- package/dist/invocation/request.js +2 -2
- package/dist/invocation/routing.d.ts +19 -5
- package/dist/invocation/routing.js +50 -20
- package/dist/invocation/schema.d.ts +3 -8
- package/dist/invocation/schema.js +3 -10
- package/dist/invocation/service-reason.d.ts +13 -13
- package/dist/invocation/service-reason.js +13 -12
- package/dist/invocation/stale-route.d.ts +1 -1
- package/dist/invocation/target.d.ts +3 -0
- package/dist/invocation/target.js +8 -1
- package/dist/invocation/websocket/index.d.ts +1 -1
- package/dist/invocation/websocket/index.js +1 -1
- package/dist/invocation/websocket/subprotocol.d.ts +1 -2
- package/dist/invocation/websocket/subprotocol.js +3 -4
- package/dist/issuer/index.d.ts +1 -0
- package/dist/issuer/index.js +1 -0
- package/dist/issuer/paths.d.ts +2 -0
- package/dist/issuer/paths.js +1 -0
- package/dist/issuer/workflow-engine.d.ts +18 -0
- package/dist/issuer/workflow-engine.js +59 -0
- package/dist/{publication → release}/bindings/callable.js +6 -6
- package/dist/release/bindings/invocation.js +13 -0
- package/dist/{publication → release}/bindings/view.d.ts +1 -1
- package/dist/{publication → release}/bindings/view.js +10 -10
- package/dist/release/build.d.ts +6 -0
- package/dist/release/build.js +7 -0
- package/dist/release/bundle-admission.d.ts +21 -0
- package/dist/{publication → release}/bundle-admission.js +18 -18
- package/dist/{publication → release}/bundle-descriptor.js +6 -6
- package/dist/release/bundle-evidence.d.ts +3 -0
- package/dist/release/bundle-evidence.js +13 -0
- package/dist/{publication → release}/bundle.d.ts +2 -2
- package/dist/{publication → release}/bundle.js +9 -9
- package/dist/{publication → release}/delivery/attestation.d.ts +1 -0
- package/dist/release/delivery/claims.d.ts +21 -0
- package/dist/release/delivery/claims.js +68 -0
- package/dist/release/delivery/delivery.d.ts +11 -0
- package/dist/release/delivery/delivery.js +24 -0
- package/dist/{publication → release}/delivery/error.d.ts +2 -0
- package/dist/{publication → release}/delivery/error.js +1 -0
- package/dist/{publication → release}/delivery/index.d.ts +6 -1
- package/dist/{publication → release}/delivery/index.js +5 -0
- package/dist/{publication → release}/delivery/limits.d.ts +2 -1
- package/dist/release/delivery/limits.js +5 -0
- package/dist/release/delivery/request.d.ts +21 -0
- package/dist/release/delivery/request.js +60 -0
- package/dist/{publication → release}/delivery-declaration.js +4 -4
- package/dist/release/delivery-endpoint.js +22 -0
- package/dist/release/digest.d.ts +13 -0
- package/dist/{publication/etag.js → release/digest.js} +1 -10
- package/dist/release/error.d.ts +7 -0
- package/dist/release/error.js +11 -0
- package/dist/release/index.d.ts +21 -0
- package/dist/release/index.js +14 -0
- package/dist/release/issuer.d.ts +5 -0
- package/dist/release/issuer.js +34 -0
- package/dist/release/legacy/bindings.d.ts +14 -0
- package/dist/release/legacy/bindings.js +11 -0
- package/dist/release/legacy/bundle-admission.d.ts +6 -0
- package/dist/release/legacy/bundle-admission.js +10 -0
- package/dist/release/legacy/bundle-evidence.d.ts +8 -0
- package/dist/release/legacy/bundle-evidence.js +11 -0
- package/dist/release/legacy/bundle.d.ts +24 -0
- package/dist/release/legacy/bundle.js +31 -0
- package/dist/release/legacy/delivery/attestation.d.ts +6 -0
- package/dist/release/legacy/delivery/attestation.js +1 -0
- package/dist/{publication → release/legacy}/delivery/claims.d.ts +9 -0
- package/dist/{publication → release/legacy}/delivery/claims.js +7 -2
- package/dist/release/legacy/delivery/delivery.d.ts +18 -0
- package/dist/{publication → release/legacy}/delivery/delivery.js +6 -2
- package/dist/release/legacy/delivery/error.d.ts +16 -0
- package/dist/release/legacy/delivery/error.js +6 -0
- package/dist/release/legacy/delivery/index.d.ts +13 -0
- package/dist/release/legacy/delivery/index.js +13 -0
- package/dist/release/legacy/delivery/limits.d.ts +21 -0
- package/dist/release/legacy/delivery/limits.js +22 -0
- package/dist/release/legacy/delivery/request.d.ts +26 -0
- package/dist/{publication → release/legacy}/delivery/request.js +8 -3
- package/dist/release/legacy/delivery-declaration.d.ts +9 -0
- package/dist/release/legacy/delivery-declaration.js +11 -0
- package/dist/release/legacy/error.d.ts +26 -0
- package/dist/release/legacy/error.js +114 -0
- package/dist/release/legacy/etag.d.ts +12 -0
- package/dist/release/legacy/etag.js +14 -0
- package/dist/release/legacy/index.d.ts +72 -0
- package/dist/{publication → release/legacy}/index.js +7 -2
- package/dist/release/legacy/profiles.d.ts +23 -0
- package/dist/release/legacy/profiles.js +36 -0
- package/dist/release/legacy/projection.d.ts +13 -0
- package/dist/release/legacy/projection.js +25 -0
- package/dist/release/legacy/publication.d.ts +90 -0
- package/dist/release/legacy/publication.js +124 -0
- package/dist/release/legacy/reference.d.ts +47 -0
- package/dist/{publication → release/legacy}/reference.js +27 -3
- package/dist/release/legacy/requirements.d.ts +30 -0
- package/dist/release/legacy/requirements.js +20 -0
- package/dist/{publication → release}/manifest.js +6 -6
- package/dist/release/reference.d.ts +26 -0
- package/dist/release/reference.js +70 -0
- package/dist/release/release.d.ts +60 -0
- package/dist/release/release.js +123 -0
- package/dist/{publication → release}/requirements.d.ts +11 -4
- package/dist/{publication → release}/requirements.js +58 -39
- package/dist/{publication → release}/route.js +24 -24
- package/dist/{publication → release}/schema.js +4 -4
- package/dist/release/served.d.ts +26 -0
- package/dist/release/served.js +65 -0
- package/dist/release/shape.d.ts +8 -0
- package/dist/routes/admission.d.ts +1 -1
- package/dist/routes/admission.js +7 -85
- package/dist/routes/binding.d.ts +3 -3
- package/dist/routes/binding.js +10 -19
- package/dist/routes/compiler.d.ts +2 -2
- package/dist/routes/compiler.js +3 -2
- package/dist/routes/credential.d.ts +1 -1
- package/dist/routes/decode.d.ts +5 -2
- package/dist/routes/decode.js +15 -4
- package/dist/routes/deployment-retired.d.ts +30 -0
- package/dist/routes/deployment-retired.js +96 -0
- package/dist/routes/encode.js +7 -6
- package/dist/routes/entries.d.ts +23 -0
- package/dist/routes/entries.js +150 -0
- package/dist/routes/evidence.d.ts +3 -3
- package/dist/routes/evidence.js +7 -34
- package/dist/routes/header.d.ts +9 -0
- package/dist/routes/header.js +34 -0
- package/dist/routes/index.d.ts +5 -2
- package/dist/routes/index.js +5 -2
- package/dist/routes/legacy/index.d.ts +29 -0
- package/dist/routes/legacy/index.js +29 -0
- package/dist/routes/legacy/routing.d.ts +24 -0
- package/dist/routes/legacy/routing.js +45 -0
- package/dist/routes/legacy/table.d.ts +34 -0
- package/dist/routes/legacy/table.js +50 -0
- package/dist/routes/mapping.d.ts +1 -1
- package/dist/routes/mapping.js +4 -4
- package/dist/routes/match.d.ts +1 -1
- package/dist/routes/placement.d.ts +2 -2
- package/dist/routes/plan.d.ts +1 -1
- package/dist/routes/request.d.ts +3 -3
- package/dist/routes/reservation.d.ts +4 -2
- package/dist/routes/reservation.js +4 -3
- package/dist/routes/routing.d.ts +30 -6
- package/dist/routes/routing.js +34 -49
- package/dist/routes/select.d.ts +1 -1
- package/dist/routes/table.d.ts +3 -2
- package/dist/routes/target.d.ts +5 -0
- package/dist/routes/target.js +16 -0
- package/dist/runtime/artifact.d.ts +2 -2
- package/dist/runtime/artifact.js +2 -2
- package/dist/schema/installation/codec.d.ts +6 -1
- package/dist/schema/installation/codec.js +10 -7
- package/dist/schema/installation/introspection/codec.d.ts +11 -2
- package/dist/schema/installation/introspection/codec.js +59 -41
- package/dist/schema/installation/introspection/result.d.ts +8 -1
- package/dist/schema/installation/legacy/index.d.ts +39 -0
- package/dist/schema/installation/legacy/index.js +39 -0
- package/dist/schema/installation/legacy/installation.d.ts +9 -0
- package/dist/schema/installation/legacy/installation.js +28 -0
- package/dist/schema/installation/legacy/introspection.d.ts +10 -0
- package/dist/schema/installation/legacy/introspection.js +70 -0
- package/dist/schema/installation/legacy/msgpack.d.ts +1 -0
- package/dist/schema/installation/legacy/msgpack.js +143 -0
- package/package.json +59 -36
- package/dist/codecs/msgpack.d.ts +0 -4
- package/dist/codecs/msgpack.js +0 -53
- package/dist/publication/bindings/invocation.js +0 -13
- package/dist/publication/bundle-admission.d.ts +0 -4
- package/dist/publication/bundle-evidence.d.ts +0 -3
- package/dist/publication/bundle-evidence.js +0 -13
- package/dist/publication/content.d.ts +0 -10
- package/dist/publication/delivery/delivery.d.ts +0 -9
- package/dist/publication/delivery/limits.js +0 -4
- package/dist/publication/delivery/request.d.ts +0 -15
- package/dist/publication/delivery-endpoint.js +0 -22
- package/dist/publication/error.d.ts +0 -7
- package/dist/publication/error.js +0 -11
- package/dist/publication/etag.d.ts +0 -6
- package/dist/publication/index.d.ts +0 -17
- package/dist/publication/legacy-requirements.d.ts +0 -12
- package/dist/publication/legacy-requirements.js +0 -23
- package/dist/publication/publication.d.ts +0 -38
- package/dist/publication/publication.js +0 -146
- package/dist/publication/reference.d.ts +0 -17
- package/dist/routes/release-unavailable.d.ts +0 -8
- package/dist/routes/release-unavailable.js +0 -32
- /package/dist/{publication → release}/bindings/callable.d.ts +0 -0
- /package/dist/{publication → release}/bindings/index.d.ts +0 -0
- /package/dist/{publication → release}/bindings/index.js +0 -0
- /package/dist/{publication → release}/bindings/invocation.d.ts +0 -0
- /package/dist/{publication → release}/bundle-descriptor.d.ts +0 -0
- /package/dist/{publication → release}/declaration.d.ts +0 -0
- /package/dist/{publication → release}/declaration.js +0 -0
- /package/dist/{publication → release}/delivery/attestation.js +0 -0
- /package/dist/{publication → release}/delivery-declaration.d.ts +0 -0
- /package/dist/{publication → release}/delivery-endpoint.d.ts +0 -0
- /package/dist/{publication → release}/manifest.d.ts +0 -0
- /package/dist/{publication → release}/route.d.ts +0 -0
- /package/dist/{publication → release}/schema.d.ts +0 -0
- /package/dist/{publication/content.js → release/shape.js} +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Key, patterns } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
2
|
+
import { admitCallableBinding, admitViewBinding } from './bindings/index.js';
|
|
3
|
+
import { canonicalizeViewBinding } from './bindings/view.js';
|
|
4
|
+
import { admitEndpoint as admitDeliveryEndpoint } from './delivery-endpoint.js';
|
|
5
|
+
import { fail } from './error.js';
|
|
6
|
+
import { admitIssuer } from './issuer.js';
|
|
7
|
+
import { admitManifest } from './manifest.js';
|
|
8
|
+
import { admitRequirements } from './requirements.js';
|
|
9
|
+
import { admitRoute } from './route.js';
|
|
10
|
+
import { admitSchema } from './schema.js';
|
|
11
|
+
import { array, exact, record, unique } from './shape.js';
|
|
12
|
+
/**
|
|
13
|
+
* Admit the version-neutral content of one served release document. Every document version runs
|
|
14
|
+
* these checks in this order, so a document of any version admits exactly the same served content.
|
|
15
|
+
*/
|
|
16
|
+
export function admitServedContent(value, options) {
|
|
17
|
+
if (typeof value.origin !== 'string' || !patterns.origin.test(value.origin)) {
|
|
18
|
+
fail('RELEASE_INVALID', 'Release origin is invalid.');
|
|
19
|
+
}
|
|
20
|
+
const origin = value.origin;
|
|
21
|
+
const identity = record(value.identity, 'RELEASE_INVALID', 'Release identity is invalid.');
|
|
22
|
+
exact(identity, ['issuer', 'subject'], 'RELEASE_INVALID');
|
|
23
|
+
const issuer = admitIssuer(identity.issuer);
|
|
24
|
+
if (identity.subject !== origin) {
|
|
25
|
+
fail('RELEASE_INVALID', 'Release identity subject must equal its root origin.');
|
|
26
|
+
}
|
|
27
|
+
const schema = admitSchema(value.schema);
|
|
28
|
+
const requirements = (options.requirements ?? admitRequirements)(value.requirements);
|
|
29
|
+
const manifest = admitManifest(value.manifest);
|
|
30
|
+
const bindingSet = record(value.bindings, 'RELEASE_BINDING_INVALID', 'Release bindings are invalid.');
|
|
31
|
+
exact(bindingSet, ['callables', 'views'], 'RELEASE_BINDING_INVALID');
|
|
32
|
+
const callables = array(bindingSet.callables, 'RELEASE_BINDING_INVALID').map((entry) => admitCallableBinding(entry, origin));
|
|
33
|
+
unique(callables.map(({ callable }) => callable), 'RELEASE_BINDING_INVALID', 'callable binding');
|
|
34
|
+
const views = array(bindingSet.views, 'RELEASE_VIEW_INVALID').map((entry) => options.views === 'canonicalize'
|
|
35
|
+
? canonicalizeViewBinding(entry, origin)
|
|
36
|
+
: admitViewBinding(entry, origin));
|
|
37
|
+
unique(views.map(({ view }) => view), 'RELEASE_VIEW_INVALID', 'View binding');
|
|
38
|
+
const routes = array(value.routes, 'RELEASE_ROUTE_INVALID').map((entry) => admitRoute(entry, origin));
|
|
39
|
+
unique(routes.map(({ name }) => name), 'RELEASE_ROUTE_INVALID', 'route name');
|
|
40
|
+
for (const callable of new Set(routes.map(({ callable }) => callable))) {
|
|
41
|
+
if (routes.filter((route) => route.callable === callable && route.default === true).length > 1) {
|
|
42
|
+
fail('RELEASE_ROUTE_INVALID', `Callable ${callable} has more than one default route.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (manifest?.entrypoint !== undefined) {
|
|
46
|
+
if (manifest.entrypoint.origin !== origin) {
|
|
47
|
+
fail('RELEASE_VIEW_INVALID', 'Manifest entrypoint must belong to the root origin.');
|
|
48
|
+
}
|
|
49
|
+
const key = Key.of(manifest.entrypoint);
|
|
50
|
+
if (!views.some(({ view }) => view === key)) {
|
|
51
|
+
fail('RELEASE_VIEW_INVALID', 'Manifest entrypoint must have a published View binding.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const delivery = admitDeliveryEndpoint(value.delivery, schema.bundle.href);
|
|
55
|
+
return {
|
|
56
|
+
origin,
|
|
57
|
+
identity: { issuer, subject: origin },
|
|
58
|
+
schema,
|
|
59
|
+
requirements,
|
|
60
|
+
...(manifest === undefined ? {} : { manifest }),
|
|
61
|
+
bindings: { callables, views },
|
|
62
|
+
routes,
|
|
63
|
+
...(delivery === undefined ? {} : { delivery }),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReleaseErrorCode } from './error.js';
|
|
2
|
+
export declare function record(input: unknown, code: ReleaseErrorCode, message: string): Record<string, unknown>;
|
|
3
|
+
export declare function exact(value: Record<string, unknown>, allowed: readonly string[], code: ReleaseErrorCode): void;
|
|
4
|
+
export declare function array(input: unknown, code: ReleaseErrorCode): readonly unknown[];
|
|
5
|
+
export declare function unique(values: readonly string[], code: ReleaseErrorCode, label: string): void;
|
|
6
|
+
export declare function deepFreeze<T>(value: T): T;
|
|
7
|
+
export declare function absoluteHttpUrl(input: unknown, allowSearch: boolean, code: ReleaseErrorCode, allowTemplate?: boolean): string;
|
|
8
|
+
export declare function absoluteUrl(input: unknown, protocols: readonly string[], allowSearch: boolean, code: ReleaseErrorCode, allowTemplate?: boolean): string;
|
package/dist/routes/admission.js
CHANGED
|
@@ -1,89 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { fail } from './error.js';
|
|
5
|
-
const AUTH = new Set(['anonymous', 'authenticated', 'authorized']);
|
|
6
|
-
const OUTPUT = new Set(['value', 'stream', 'binary']);
|
|
7
|
-
/** Admit one emitted portable Table before Server composition. */
|
|
1
|
+
import { acceptReference } from '../release/reference.js';
|
|
2
|
+
import { acceptEntries, tableObject } from './entries.js';
|
|
3
|
+
/** Admit one emitted portable release Table before Server composition. */
|
|
8
4
|
export function acceptTable(input) {
|
|
9
|
-
const value =
|
|
10
|
-
|
|
11
|
-
const reference = acceptReference(value.publication);
|
|
12
|
-
if (!Array.isArray(value.entries))
|
|
13
|
-
invalid('Route Table entries');
|
|
14
|
-
const entries = value.entries.map((entry) => acceptRoute(entry, reference.origin));
|
|
15
|
-
if (new Set(entries.map(({ name }) => name)).size !== entries.length) {
|
|
16
|
-
fail('NATIVE_ROUTE_BINDING_INVALID', 'Route Table names must be unique.');
|
|
17
|
-
}
|
|
18
|
-
return Object.freeze({ publication: reference, entries: Object.freeze(entries) });
|
|
19
|
-
}
|
|
20
|
-
function acceptRoute(input, origin) {
|
|
21
|
-
const value = object(input, 'Route Table entry');
|
|
22
|
-
exact(value, ['name', 'callable', 'target', 'auth', 'output', 'href', 'method', 'default'], 'Route Table entry', ['requestMethod', 'query', 'headers', 'body', 'credential']);
|
|
23
|
-
if (typeof value.default !== 'boolean')
|
|
24
|
-
invalid('Route Table default');
|
|
25
|
-
if (typeof value.auth !== 'string' || !AUTH.has(value.auth)) {
|
|
26
|
-
invalid('Route Table auth');
|
|
27
|
-
}
|
|
28
|
-
if (typeof value.output !== 'string' || !OUTPUT.has(value.output))
|
|
29
|
-
invalid('Route Table output');
|
|
30
|
-
const wire = admitRoute({
|
|
31
|
-
name: value.name,
|
|
32
|
-
callable: value.callable,
|
|
33
|
-
href: value.href,
|
|
34
|
-
method: value.method,
|
|
35
|
-
...(value.default === true ? { default: true } : {}),
|
|
36
|
-
...(value.requestMethod === undefined ? {} : { requestMethod: value.requestMethod }),
|
|
37
|
-
...(value.query === undefined ? {} : { query: value.query }),
|
|
38
|
-
...(value.headers === undefined ? {} : { headers: value.headers }),
|
|
39
|
-
...(value.body === undefined ? {} : { body: value.body }),
|
|
40
|
-
...(value.credential === undefined ? {} : { credential: value.credential }),
|
|
41
|
-
...receiver(value.target),
|
|
42
|
-
}, origin);
|
|
43
|
-
const { default: _default, receiver: _receiver, ...common } = wire;
|
|
5
|
+
const value = tableObject(input, ['release', 'entries']);
|
|
6
|
+
const reference = acceptReference(value.release);
|
|
44
7
|
return Object.freeze({
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
auth: value.auth,
|
|
48
|
-
output: value.output,
|
|
49
|
-
default: value.default,
|
|
8
|
+
release: reference,
|
|
9
|
+
entries: acceptEntries(value.entries, reference.origin),
|
|
50
10
|
});
|
|
51
11
|
}
|
|
52
|
-
function target(input) {
|
|
53
|
-
const value = object(input, 'Route target');
|
|
54
|
-
if (value.kind === 'function') {
|
|
55
|
-
exact(value, ['kind'], 'Route Function target');
|
|
56
|
-
return Object.freeze({ kind: 'function' });
|
|
57
|
-
}
|
|
58
|
-
if (value.kind !== 'method' || (value.dispatch !== 'static' && value.dispatch !== 'instance')) {
|
|
59
|
-
invalid('Route Method target');
|
|
60
|
-
}
|
|
61
|
-
if (value.dispatch === 'static') {
|
|
62
|
-
exact(value, ['kind', 'dispatch'], 'Route static Method target');
|
|
63
|
-
return Object.freeze({ kind: 'method', dispatch: 'static' });
|
|
64
|
-
}
|
|
65
|
-
exact(value, ['kind', 'dispatch', 'receiver'], 'Route instance Method target');
|
|
66
|
-
if (typeof value.receiver !== 'string')
|
|
67
|
-
invalid('Route receiver');
|
|
68
|
-
return Object.freeze({ kind: 'method', dispatch: 'instance', receiver: value.receiver });
|
|
69
|
-
}
|
|
70
|
-
function receiver(input) {
|
|
71
|
-
const admitted = target(input);
|
|
72
|
-
return admitted.kind === 'method' && admitted.dispatch === 'instance'
|
|
73
|
-
? { receiver: admitted.receiver }
|
|
74
|
-
: {};
|
|
75
|
-
}
|
|
76
|
-
function object(input, label) {
|
|
77
|
-
if (input === null || typeof input !== 'object' || Array.isArray(input))
|
|
78
|
-
invalid(label);
|
|
79
|
-
return input;
|
|
80
|
-
}
|
|
81
|
-
function exact(value, required, label, optional = []) {
|
|
82
|
-
const allowed = new Set([...required, ...optional]);
|
|
83
|
-
if (Object.keys(value).some((key) => !allowed.has(key)) ||
|
|
84
|
-
required.some((key) => !Object.hasOwn(value, key)))
|
|
85
|
-
invalid(label);
|
|
86
|
-
}
|
|
87
|
-
function invalid(label) {
|
|
88
|
-
return fail('NATIVE_ROUTE_BINDING_INVALID', `${label} is invalid.`);
|
|
89
|
-
}
|
package/dist/routes/binding.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Domain } from '@astrale-os/kernel-dsl/v1';
|
|
2
|
-
import type {
|
|
2
|
+
import type { DomainRelease } from '../release/release.js';
|
|
3
3
|
import type { Plan } from './plan.js';
|
|
4
4
|
import type { Table } from './table.js';
|
|
5
|
-
/** Bind one Plan to an exact placed
|
|
6
|
-
export declare function bind(plan: Plan, deployed:
|
|
5
|
+
/** Bind one Plan to an exact placed release and matching resolved Domain. */
|
|
6
|
+
export declare function bind(plan: Plan, deployed: DomainRelease, domain: Domain, publicUrl?: string): Table;
|
package/dist/routes/binding.js
CHANGED
|
@@ -1,26 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { reference } from '../release/reference.js';
|
|
2
|
+
import { placedEntries, placementMismatch } from './entries.js';
|
|
3
3
|
import { fail } from './error.js';
|
|
4
|
-
import { place } from './placement.js';
|
|
5
4
|
import { rejectReserved } from './reservation.js';
|
|
6
|
-
/** Bind one Plan to an exact placed
|
|
5
|
+
/** Bind one Plan to an exact placed release and matching resolved Domain. */
|
|
7
6
|
export function bind(plan, deployed, domain, publicUrl = deployed.identity.issuer) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
plan.revision !== domain.revision) {
|
|
12
|
-
fail('NATIVE_ROUTE_BINDING_INVALID', 'Route Plan, Publication, and Domain identity differ.');
|
|
7
|
+
const mismatch = placementMismatch(plan, deployed, domain, publicUrl);
|
|
8
|
+
if (mismatch === 'identity') {
|
|
9
|
+
fail('NATIVE_ROUTE_BINDING_INVALID', 'Route Plan, Release, and Domain identity differ.');
|
|
13
10
|
}
|
|
14
|
-
if (
|
|
15
|
-
fail('NATIVE_ROUTE_BINDING_INVALID', '
|
|
11
|
+
if (mismatch === 'routes') {
|
|
12
|
+
fail('NATIVE_ROUTE_BINDING_INVALID', 'Release routes differ from the placed Route Plan.');
|
|
16
13
|
}
|
|
17
|
-
const entries = plan
|
|
18
|
-
const { path: _path, ...portable } = entry;
|
|
19
|
-
return Object.freeze({ ...portable, href: deployed.routes[index].href });
|
|
20
|
-
});
|
|
14
|
+
const entries = placedEntries(plan, deployed);
|
|
21
15
|
rejectReserved(deployed, entries);
|
|
22
|
-
return Object.freeze({
|
|
23
|
-
publication: reference(deployed),
|
|
24
|
-
entries: Object.freeze(entries),
|
|
25
|
-
});
|
|
16
|
+
return Object.freeze({ release: reference(deployed), entries });
|
|
26
17
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Domain } from '@astrale-os/kernel-dsl/v1';
|
|
2
|
-
import type
|
|
2
|
+
import type { HttpRouteDeclaration } from '../release/route.js';
|
|
3
3
|
import type { Plan } from './plan.js';
|
|
4
4
|
export interface CompileInput {
|
|
5
|
-
readonly routes: readonly
|
|
5
|
+
readonly routes: readonly HttpRouteDeclaration[];
|
|
6
6
|
readonly domain: Domain;
|
|
7
7
|
}
|
|
8
8
|
export declare function compile(input: CompileInput): Plan;
|
package/dist/routes/compiler.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isBinaryInput } from '@astrale-os/kernel-dsl/v1/language';
|
|
2
|
-
import {
|
|
2
|
+
import { withPublicationError } from '../release/legacy/error.js';
|
|
3
|
+
import { admitRouteDeclaration } from '../release/route.js';
|
|
3
4
|
import { rejectCollisions } from './collision.js';
|
|
4
5
|
import { fail } from './error.js';
|
|
5
6
|
import { validateCredentialMapping, validateMapping, validateWireMapping } from './mapping.js';
|
|
@@ -9,7 +10,7 @@ export function compile(input) {
|
|
|
9
10
|
if (input === null || typeof input !== 'object' || !Array.isArray(input.routes)) {
|
|
10
11
|
throw new TypeError('Route compilation input is invalid.');
|
|
11
12
|
}
|
|
12
|
-
const authored = input.routes.map((entry) => admitRouteDeclaration(entry, input.domain.origin));
|
|
13
|
+
const authored = input.routes.map((entry) => withPublicationError(() => admitRouteDeclaration(entry, input.domain.origin)));
|
|
13
14
|
const entries = authored.map((declaration) => {
|
|
14
15
|
const meaning = input.domain.callable(declaration.callable);
|
|
15
16
|
if (meaning === undefined || (isMethod(meaning) && !meaning.executable)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HttpRoute, SingleCredentialSource } from '../
|
|
1
|
+
import type { HttpRoute, SingleCredentialSource } from '../release/route.js';
|
|
2
2
|
import type { Route } from './table.js';
|
|
3
3
|
export declare function credentialSources(route: Pick<HttpRoute, 'credential'>): readonly SingleCredentialSource[];
|
|
4
4
|
export declare function firstCredentialIsAmbient(entry: Route): boolean;
|
package/dist/routes/decode.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Domain } from '@astrale-os/kernel-dsl/v1';
|
|
2
|
-
import type { PublicationRef } from '../publication/index.js';
|
|
3
2
|
import type { HttpRequest, Invocation } from './request.js';
|
|
3
|
+
import type { ServedReferences } from './routing.js';
|
|
4
4
|
import type { Route } from './table.js';
|
|
5
|
-
|
|
5
|
+
/** Decode one native route request served by a release-backed destination. */
|
|
6
|
+
export declare function decode(entry: Route, request: HttpRequest, domain: Domain, served: ServedReferences): Invocation;
|
|
7
|
+
/** Decode one native route request, reading its routing pair with `routing` at its fixed step. */
|
|
8
|
+
export declare function decodeInvocation(entry: Route, request: HttpRequest, domain: Domain, routing: (input: HttpRequest) => Invocation['routing']): Invocation;
|
package/dist/routes/decode.js
CHANGED
|
@@ -7,9 +7,20 @@ import { methodMatches } from './match.js';
|
|
|
7
7
|
import { bodyText, normalizeHeaders, parseJsonObject } from './request.js';
|
|
8
8
|
import { decodeRouting } from './routing.js';
|
|
9
9
|
import { decodeScalar } from './scalar.js';
|
|
10
|
-
import { requiresReceiver } from './target.js';
|
|
10
|
+
import { requiresReceiver, routeFingerprint } from './target.js';
|
|
11
11
|
import { matchTemplate, PLACEHOLDER } from './template.js';
|
|
12
|
+
/** Decode one native route request served by a release-backed destination. */
|
|
12
13
|
export function decode(entry, request, domain, served) {
|
|
14
|
+
return decodeInvocation(entry, request, domain, (input) => {
|
|
15
|
+
const routing = decodeRouting(input, served);
|
|
16
|
+
if (routing?.kind === 'stale') {
|
|
17
|
+
fail('NATIVE_ROUTE_ROUTING_INVALID', 'If-Match does not name the served release.');
|
|
18
|
+
}
|
|
19
|
+
return routing;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** Decode one native route request, reading its routing pair with `routing` at its fixed step. */
|
|
23
|
+
export function decodeInvocation(entry, request, domain, routing) {
|
|
13
24
|
if (!methodMatches(entry, request.method))
|
|
14
25
|
invalidRequest('HTTP method does not match the route.');
|
|
15
26
|
const matched = matchTemplate(entry.href, request.url);
|
|
@@ -34,7 +45,7 @@ export function decode(entry, request, domain, served) {
|
|
|
34
45
|
}
|
|
35
46
|
const headers = normalizeHeaders(request.headers);
|
|
36
47
|
const credential = readCredential(entry, url, headers);
|
|
37
|
-
const
|
|
48
|
+
const routed = routing(request);
|
|
38
49
|
const applicationHeaders = withoutCredentialHeaders(entry, headers);
|
|
39
50
|
for (const [name, wire] of Object.entries(entry.query ?? {})) {
|
|
40
51
|
const found = url.searchParams.getAll(wire);
|
|
@@ -113,13 +124,13 @@ export function decode(entry, request, domain, served) {
|
|
|
113
124
|
}
|
|
114
125
|
const target = Object.freeze({
|
|
115
126
|
callable: entry.callable,
|
|
116
|
-
|
|
127
|
+
fingerprint: routeFingerprint(domain, entry.callable),
|
|
117
128
|
...(receiver === undefined ? {} : { instance: receiver }),
|
|
118
129
|
});
|
|
119
130
|
return Object.freeze({
|
|
120
131
|
call: Object.freeze({ target, input }),
|
|
121
132
|
...(credential === undefined ? {} : { credential }),
|
|
122
|
-
...(
|
|
133
|
+
...(routed === undefined ? {} : { routing: routed }),
|
|
123
134
|
});
|
|
124
135
|
}
|
|
125
136
|
function remainderFields(entry, domain) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type IssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
|
+
import type { DeploymentRetiredReason, ErrorResponse } from '../invocation/index.js';
|
|
3
|
+
import type { ReleaseDigest } from '../release/digest.js';
|
|
4
|
+
import type { HttpRequest } from './request.js';
|
|
5
|
+
/**
|
|
6
|
+
* Reserved control of a deployment that no longer serves. Every emitter (the deployment
|
|
7
|
+
* dispatchers, a release-snapshot server) answers every path with the same 410, headers and
|
|
8
|
+
* envelope, and never runs other code in its place.
|
|
9
|
+
*/
|
|
10
|
+
export interface DeploymentRetired {
|
|
11
|
+
readonly kind: 'deployment-retired';
|
|
12
|
+
readonly reason: DeploymentRetiredReason;
|
|
13
|
+
}
|
|
14
|
+
export declare const HTTP_DEPLOYMENT_RETIRED: 'x-astrale-deployment-retired';
|
|
15
|
+
export declare const HTTP_DEPLOYMENT_RETIRED_STATUS: 410;
|
|
16
|
+
export declare function deploymentRetiredHeaders(): Readonly<Record<string, string>>;
|
|
17
|
+
/**
|
|
18
|
+
* The envelope every deployed client already decodes from any status: a not-found Service error
|
|
19
|
+
* carrying `DEPLOYMENT_RETIRED`, attributed to the deployment rather than to one request. An
|
|
20
|
+
* emitter passing a deployment that is not a canonical issuer origin, or a release that is not a
|
|
21
|
+
* release digest, gets a `TypeError` naming that argument.
|
|
22
|
+
*/
|
|
23
|
+
export declare function deploymentRetiredBody(deployment: IssuerId, release?: ReleaseDigest): ErrorResponse;
|
|
24
|
+
/**
|
|
25
|
+
* Recognize only the reserved status, header, `no-store` and envelope together. A 410 without the
|
|
26
|
+
* header is ordinary application behavior; the header with another status or value, without
|
|
27
|
+
* `Cache-Control: no-store`, or beside any other body (including one correlated to a request or
|
|
28
|
+
* whose invocation source is not the retired deployment), is an invalid response.
|
|
29
|
+
*/
|
|
30
|
+
export declare function decodeDeploymentRetired(status: number, headers: HttpRequest['headers'], body: unknown): DeploymentRetired | undefined;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { is as isIssuer } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
|
+
import { CONTENT_TYPE_JSON } from '../codecs/media-types.js';
|
|
3
|
+
import { CODES, ProtocolError } from '../errors/index.js';
|
|
4
|
+
import { DEPLOYMENT_RETIRED, decodeResponse, serviceErrorOf } from '../invocation/index.js';
|
|
5
|
+
import { fail } from './error.js';
|
|
6
|
+
import { reservedHeader } from './header.js';
|
|
7
|
+
export const HTTP_DEPLOYMENT_RETIRED = 'x-astrale-deployment-retired';
|
|
8
|
+
export const HTTP_DEPLOYMENT_RETIRED_STATUS = 410;
|
|
9
|
+
const MESSAGE = 'This deployment is retired.';
|
|
10
|
+
const DIGEST = /^sha256:[0-9a-f]{64}$/u;
|
|
11
|
+
export function deploymentRetiredHeaders() {
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
[HTTP_DEPLOYMENT_RETIRED]: '1',
|
|
14
|
+
'cache-control': 'no-store',
|
|
15
|
+
'content-type': CONTENT_TYPE_JSON,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The envelope every deployed client already decodes from any status: a not-found Service error
|
|
20
|
+
* carrying `DEPLOYMENT_RETIRED`, attributed to the deployment rather than to one request. An
|
|
21
|
+
* emitter passing a deployment that is not a canonical issuer origin, or a release that is not a
|
|
22
|
+
* release digest, gets a `TypeError` naming that argument.
|
|
23
|
+
*/
|
|
24
|
+
export function deploymentRetiredBody(deployment, release) {
|
|
25
|
+
assertEmitterInput(deployment, release);
|
|
26
|
+
return decodeRetired({
|
|
27
|
+
requestId: null,
|
|
28
|
+
invocation: { source: deployment, id: 'unavailable' },
|
|
29
|
+
error: {
|
|
30
|
+
code: CODES.NOT_FOUND,
|
|
31
|
+
message: MESSAGE,
|
|
32
|
+
data: {
|
|
33
|
+
code: DEPLOYMENT_RETIRED,
|
|
34
|
+
details: { deployment, ...(release === undefined ? {} : { release }) },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
}).envelope;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Recognize only the reserved status, header, `no-store` and envelope together. A 410 without the
|
|
41
|
+
* header is ordinary application behavior; the header with another status or value, without
|
|
42
|
+
* `Cache-Control: no-store`, or beside any other body (including one correlated to a request or
|
|
43
|
+
* whose invocation source is not the retired deployment), is an invalid response.
|
|
44
|
+
*/
|
|
45
|
+
export function decodeDeploymentRetired(status, headers, body) {
|
|
46
|
+
const encoded = reservedHeader(headers, HTTP_DEPLOYMENT_RETIRED);
|
|
47
|
+
if (encoded === undefined)
|
|
48
|
+
return undefined;
|
|
49
|
+
if (status !== HTTP_DEPLOYMENT_RETIRED_STATUS || encoded !== '1') {
|
|
50
|
+
fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_DEPLOYMENT_RETIRED} is reserved for status ${HTTP_DEPLOYMENT_RETIRED_STATUS} with value 1.`);
|
|
51
|
+
}
|
|
52
|
+
if (!cacheDirectives(headers).includes('no-store')) {
|
|
53
|
+
fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_DEPLOYMENT_RETIRED} requires Cache-Control: no-store.`);
|
|
54
|
+
}
|
|
55
|
+
return Object.freeze({ kind: 'deployment-retired', reason: decodeRetired(body).reason });
|
|
56
|
+
}
|
|
57
|
+
function decodeRetired(input) {
|
|
58
|
+
let envelope;
|
|
59
|
+
try {
|
|
60
|
+
envelope = decodeResponse(input);
|
|
61
|
+
}
|
|
62
|
+
catch (cause) {
|
|
63
|
+
if (cause instanceof ProtocolError)
|
|
64
|
+
return invalid(cause);
|
|
65
|
+
throw cause;
|
|
66
|
+
}
|
|
67
|
+
const failure = 'error' in envelope ? serviceErrorOf(envelope.error) : undefined;
|
|
68
|
+
if (failure === undefined ||
|
|
69
|
+
failure.data.code !== DEPLOYMENT_RETIRED ||
|
|
70
|
+
envelope.requestId !== null ||
|
|
71
|
+
envelope.invocation.source !== failure.data.details.deployment) {
|
|
72
|
+
return invalid();
|
|
73
|
+
}
|
|
74
|
+
return { envelope: envelope, reason: failure.data };
|
|
75
|
+
}
|
|
76
|
+
/** Every `Cache-Control` directive, lower-cased, across repeated fields in any spelling. */
|
|
77
|
+
function cacheDirectives(input) {
|
|
78
|
+
const fields = input instanceof Headers
|
|
79
|
+
? [input.get('cache-control') ?? '']
|
|
80
|
+
: Object.entries(input ?? {})
|
|
81
|
+
.filter(([name]) => name.toLowerCase() === 'cache-control')
|
|
82
|
+
.flatMap(([, value]) => value ?? []);
|
|
83
|
+
return fields.flatMap((field) => field.split(',').map((d) => d.trim().toLowerCase()));
|
|
84
|
+
}
|
|
85
|
+
/** Emitter-side programmer invariant: never reported as a received-response decode failure. */
|
|
86
|
+
function assertEmitterInput(deployment, release) {
|
|
87
|
+
if (!isIssuer(deployment)) {
|
|
88
|
+
throw new TypeError('deploymentRetiredBody: deployment must be a canonical issuer origin.');
|
|
89
|
+
}
|
|
90
|
+
if (release !== undefined && (typeof release !== 'string' || !DIGEST.test(release))) {
|
|
91
|
+
throw new TypeError('deploymentRetiredBody: release must be a sha256 release digest.');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function invalid(cause) {
|
|
95
|
+
return fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_DEPLOYMENT_RETIRED} requires the deployment-retired error envelope.`, cause);
|
|
96
|
+
}
|
package/dist/routes/encode.js
CHANGED
|
@@ -2,18 +2,19 @@ import { targetPath } from '@astrale-os/kernel-core/callable';
|
|
|
2
2
|
import { Path } from '@astrale-os/kernel-core/path';
|
|
3
3
|
import { callableAddress } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
4
4
|
import * as portable from '@astrale-os/kernel-dsl/value';
|
|
5
|
+
import { routedDigest } from '../invocation/index.js';
|
|
5
6
|
import { firstCredentialIsAmbient, putCredential, specificHeaderWires, withoutCredentialHeaders, } from './credential.js';
|
|
6
7
|
import { fail, invalidRequest } from './error.js';
|
|
7
8
|
import { isReservedHeader } from './mapping.js';
|
|
8
|
-
import { encodeStrongEtag,
|
|
9
|
+
import { encodeStrongEtag, HTTP_ROUTE_DIGEST, HTTP_ROUTE_ISSUER } from './routing.js';
|
|
9
10
|
import { asObject, encodeScalar, scalar } from './scalar.js';
|
|
10
|
-
import { requiresReceiver } from './target.js';
|
|
11
|
+
import { requiresReceiver, routeFingerprint } from './target.js';
|
|
11
12
|
import { authorityEnd, encodeTemplate, PLACEHOLDER } from './template.js';
|
|
12
13
|
export function encode(entry, invocation, domain) {
|
|
13
14
|
if (typeof invocation.call.target !== 'string' &&
|
|
14
|
-
invocation.call.target.
|
|
15
|
-
invocation.call.target.
|
|
16
|
-
invalidRequest('Invocation Target
|
|
15
|
+
invocation.call.target.fingerprint !== undefined &&
|
|
16
|
+
invocation.call.target.fingerprint !== routeFingerprint(domain, entry.callable)) {
|
|
17
|
+
invalidRequest('Invocation Target fingerprint does not match the selected route Domain.');
|
|
17
18
|
}
|
|
18
19
|
const input = asObject(invocation.call.input);
|
|
19
20
|
const target = Path.from(targetPath(invocation.call.target));
|
|
@@ -109,7 +110,7 @@ export function encode(entry, invocation, domain) {
|
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
if (invocation.routing !== undefined) {
|
|
112
|
-
headers[
|
|
113
|
+
headers[HTTP_ROUTE_DIGEST] = encodeStrongEtag(routedDigest(invocation.routing));
|
|
113
114
|
headers[HTTP_ROUTE_ISSUER] = invocation.routing.issuer;
|
|
114
115
|
}
|
|
115
116
|
url = parsed.toString();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Domain } from '@astrale-os/kernel-dsl/v1';
|
|
2
|
+
import type { Object as JsonObject, Value as JsonValue } from '@astrale-os/kernel-dsl/value';
|
|
3
|
+
import type { Plan } from './plan.js';
|
|
4
|
+
import type { Placed } from './reservation.js';
|
|
5
|
+
import type { Route } from './table.js';
|
|
6
|
+
type Meaning = Pick<Domain, 'origin' | 'revision' | 'callable'>;
|
|
7
|
+
/**
|
|
8
|
+
* The coordinate in which a served document differs from one Plan placed at `publicUrl`: its
|
|
9
|
+
* origin or schema revision (`identity`), or its route wire (`routes`). Each Table words its own
|
|
10
|
+
* refusal after the document it binds.
|
|
11
|
+
*/
|
|
12
|
+
export declare function placementMismatch(plan: Plan, deployed: Placed, domain: Domain, publicUrl: string): 'identity' | 'routes' | undefined;
|
|
13
|
+
/** The entries of one placed Plan at the hrefs its served document publishes. */
|
|
14
|
+
export declare function placedEntries(plan: Plan, deployed: Pick<Placed, 'routes'>): readonly Route[];
|
|
15
|
+
/** Whether entries carry exactly the route wire and Domain identity of one served document. */
|
|
16
|
+
export declare function sameServedWire(entries: readonly Route[], deployed: Pick<Placed, 'origin' | 'schema' | 'routes'>, domain: Meaning): boolean;
|
|
17
|
+
/** Refuse an entry whose target, auth or output differs from the Domain's callable meaning. */
|
|
18
|
+
export declare function verifyMeaning(entries: readonly Route[], domain: Meaning): void;
|
|
19
|
+
/** Admit one portable Table object holding exactly `fields`. */
|
|
20
|
+
export declare function tableObject(input: unknown, fields: readonly string[]): JsonObject;
|
|
21
|
+
/** Admit the entries of one Table bound to `origin`; an invalid route wire is a `ReleaseError`. */
|
|
22
|
+
export declare function acceptEntries(input: JsonValue | undefined, origin: string): readonly Route[];
|
|
23
|
+
export {};
|