@astrale-os/kernel-protocol 0.5.0-beta.10

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.
Files changed (224) hide show
  1. package/LICENSE +202 -0
  2. package/dist/artifact/index.d.ts +1 -0
  3. package/dist/artifact/index.js +1 -0
  4. package/dist/artifact/ref.d.ts +10 -0
  5. package/dist/artifact/ref.js +70 -0
  6. package/dist/codecs/codec.d.ts +13 -0
  7. package/dist/codecs/codec.js +6 -0
  8. package/dist/codecs/index.d.ts +4 -0
  9. package/dist/codecs/index.js +4 -0
  10. package/dist/codecs/json.d.ts +2 -0
  11. package/dist/codecs/json.js +20 -0
  12. package/dist/codecs/media-types.d.ts +2 -0
  13. package/dist/codecs/media-types.js +2 -0
  14. package/dist/codecs/msgpack.d.ts +4 -0
  15. package/dist/codecs/msgpack.js +50 -0
  16. package/dist/errors/codes.d.ts +23 -0
  17. package/dist/errors/codes.js +22 -0
  18. package/dist/errors/http-status.d.ts +2 -0
  19. package/dist/errors/http-status.js +25 -0
  20. package/dist/errors/index.d.ts +5 -0
  21. package/dist/errors/index.js +5 -0
  22. package/dist/errors/payload.d.ts +8 -0
  23. package/dist/errors/payload.js +37 -0
  24. package/dist/errors/protocol-error.d.ts +6 -0
  25. package/dist/errors/protocol-error.js +11 -0
  26. package/dist/errors/reason.d.ts +7 -0
  27. package/dist/errors/reason.js +43 -0
  28. package/dist/index.d.ts +8 -0
  29. package/dist/index.js +8 -0
  30. package/dist/invocation/admission.d.ts +23 -0
  31. package/dist/invocation/admission.js +177 -0
  32. package/dist/invocation/binary.d.ts +11 -0
  33. package/dist/invocation/binary.js +24 -0
  34. package/dist/invocation/binding-codecs.d.ts +8 -0
  35. package/dist/invocation/binding-codecs.js +27 -0
  36. package/dist/invocation/cancellation.d.ts +8 -0
  37. package/dist/invocation/cancellation.js +16 -0
  38. package/dist/invocation/correlation.d.ts +2 -0
  39. package/dist/invocation/correlation.js +7 -0
  40. package/dist/invocation/delegate.d.ts +24 -0
  41. package/dist/invocation/delegate.js +43 -0
  42. package/dist/invocation/http/binary.d.ts +12 -0
  43. package/dist/invocation/http/binary.js +132 -0
  44. package/dist/invocation/http/index.d.ts +4 -0
  45. package/dist/invocation/http/index.js +4 -0
  46. package/dist/invocation/http/negotiation.d.ts +3 -0
  47. package/dist/invocation/http/negotiation.js +207 -0
  48. package/dist/invocation/http/stream.d.ts +5 -0
  49. package/dist/invocation/http/stream.js +15 -0
  50. package/dist/invocation/http/trace.d.ts +6 -0
  51. package/dist/invocation/http/trace.js +24 -0
  52. package/dist/invocation/idempotency.d.ts +10 -0
  53. package/dist/invocation/idempotency.js +13 -0
  54. package/dist/invocation/identity.d.ts +16 -0
  55. package/dist/invocation/identity.js +55 -0
  56. package/dist/invocation/index.d.ts +20 -0
  57. package/dist/invocation/index.js +20 -0
  58. package/dist/invocation/miss.d.ts +9 -0
  59. package/dist/invocation/miss.js +17 -0
  60. package/dist/invocation/redirect.d.ts +40 -0
  61. package/dist/invocation/redirect.js +81 -0
  62. package/dist/invocation/request.d.ts +21 -0
  63. package/dist/invocation/request.js +54 -0
  64. package/dist/invocation/response.d.ts +19 -0
  65. package/dist/invocation/response.js +38 -0
  66. package/dist/invocation/routing.d.ts +14 -0
  67. package/dist/invocation/routing.js +34 -0
  68. package/dist/invocation/schema.d.ts +9 -0
  69. package/dist/invocation/schema.js +19 -0
  70. package/dist/invocation/service-reason.d.ts +48 -0
  71. package/dist/invocation/service-reason.js +85 -0
  72. package/dist/invocation/stale-route.d.ts +12 -0
  73. package/dist/invocation/stale-route.js +19 -0
  74. package/dist/invocation/stream.d.ts +27 -0
  75. package/dist/invocation/stream.js +44 -0
  76. package/dist/invocation/target.d.ts +3 -0
  77. package/dist/invocation/target.js +11 -0
  78. package/dist/invocation/trace.d.ts +9 -0
  79. package/dist/invocation/trace.js +86 -0
  80. package/dist/invocation/version.d.ts +2 -0
  81. package/dist/invocation/version.js +1 -0
  82. package/dist/invocation/websocket/binary.d.ts +3 -0
  83. package/dist/invocation/websocket/binary.js +71 -0
  84. package/dist/invocation/websocket/index.d.ts +2 -0
  85. package/dist/invocation/websocket/index.js +2 -0
  86. package/dist/invocation/websocket/subprotocol.d.ts +10 -0
  87. package/dist/invocation/websocket/subprotocol.js +29 -0
  88. package/dist/issuer/configuration.d.ts +14 -0
  89. package/dist/issuer/configuration.js +86 -0
  90. package/dist/issuer/error.d.ts +7 -0
  91. package/dist/issuer/error.js +11 -0
  92. package/dist/issuer/exchange.d.ts +25 -0
  93. package/dist/issuer/exchange.js +129 -0
  94. package/dist/issuer/hosted.d.ts +2 -0
  95. package/dist/issuer/hosted.js +7 -0
  96. package/dist/issuer/index.d.ts +6 -0
  97. package/dist/issuer/index.js +6 -0
  98. package/dist/issuer/key-set.d.ts +6 -0
  99. package/dist/issuer/key-set.js +48 -0
  100. package/dist/issuer/paths.d.ts +6 -0
  101. package/dist/issuer/paths.js +10 -0
  102. package/dist/issuer/url.d.ts +2 -0
  103. package/dist/issuer/url.js +34 -0
  104. package/dist/publication/bindings/callable.d.ts +8 -0
  105. package/dist/publication/bindings/callable.js +34 -0
  106. package/dist/publication/bindings/index.d.ts +3 -0
  107. package/dist/publication/bindings/index.js +3 -0
  108. package/dist/publication/bindings/invocation.d.ts +5 -0
  109. package/dist/publication/bindings/invocation.js +13 -0
  110. package/dist/publication/bindings/view.d.ts +7 -0
  111. package/dist/publication/bindings/view.js +28 -0
  112. package/dist/publication/bundle-admission.d.ts +4 -0
  113. package/dist/publication/bundle-admission.js +65 -0
  114. package/dist/publication/bundle-descriptor.d.ts +9 -0
  115. package/dist/publication/bundle-descriptor.js +19 -0
  116. package/dist/publication/bundle-evidence.d.ts +3 -0
  117. package/dist/publication/bundle-evidence.js +13 -0
  118. package/dist/publication/bundle.d.ts +9 -0
  119. package/dist/publication/bundle.js +44 -0
  120. package/dist/publication/content.d.ts +10 -0
  121. package/dist/publication/content.js +55 -0
  122. package/dist/publication/declaration.d.ts +11 -0
  123. package/dist/publication/declaration.js +1 -0
  124. package/dist/publication/delivery/attestation.d.ts +6 -0
  125. package/dist/publication/delivery/attestation.js +10 -0
  126. package/dist/publication/delivery/claims.d.ts +16 -0
  127. package/dist/publication/delivery/claims.js +61 -0
  128. package/dist/publication/delivery/delivery.d.ts +9 -0
  129. package/dist/publication/delivery/delivery.js +29 -0
  130. package/dist/publication/delivery/error.d.ts +7 -0
  131. package/dist/publication/delivery/error.js +11 -0
  132. package/dist/publication/delivery/index.d.ts +6 -0
  133. package/dist/publication/delivery/index.js +6 -0
  134. package/dist/publication/delivery/limits.d.ts +4 -0
  135. package/dist/publication/delivery/limits.js +4 -0
  136. package/dist/publication/delivery/request.d.ts +15 -0
  137. package/dist/publication/delivery/request.js +52 -0
  138. package/dist/publication/delivery-declaration.d.ts +4 -0
  139. package/dist/publication/delivery-declaration.js +25 -0
  140. package/dist/publication/delivery-endpoint.d.ts +11 -0
  141. package/dist/publication/delivery-endpoint.js +22 -0
  142. package/dist/publication/error.d.ts +7 -0
  143. package/dist/publication/error.js +11 -0
  144. package/dist/publication/etag.d.ts +6 -0
  145. package/dist/publication/etag.js +79 -0
  146. package/dist/publication/index.d.ts +17 -0
  147. package/dist/publication/index.js +10 -0
  148. package/dist/publication/manifest.d.ts +6 -0
  149. package/dist/publication/manifest.js +30 -0
  150. package/dist/publication/publication.d.ts +36 -0
  151. package/dist/publication/publication.js +133 -0
  152. package/dist/publication/reference.d.ts +17 -0
  153. package/dist/publication/reference.js +68 -0
  154. package/dist/publication/requirements.d.ts +26 -0
  155. package/dist/publication/requirements.js +180 -0
  156. package/dist/publication/route.d.ts +37 -0
  157. package/dist/publication/route.js +154 -0
  158. package/dist/publication/schema.d.ts +7 -0
  159. package/dist/publication/schema.js +15 -0
  160. package/dist/routes/admission.d.ts +3 -0
  161. package/dist/routes/admission.js +89 -0
  162. package/dist/routes/binding.d.ts +6 -0
  163. package/dist/routes/binding.js +27 -0
  164. package/dist/routes/collision.d.ts +2 -0
  165. package/dist/routes/collision.js +16 -0
  166. package/dist/routes/compiler.d.ts +8 -0
  167. package/dist/routes/compiler.js +50 -0
  168. package/dist/routes/credential.d.ts +8 -0
  169. package/dist/routes/credential.js +118 -0
  170. package/dist/routes/decode.d.ts +5 -0
  171. package/dist/routes/decode.js +147 -0
  172. package/dist/routes/encode.d.ts +4 -0
  173. package/dist/routes/encode.js +145 -0
  174. package/dist/routes/error-response.d.ts +5 -0
  175. package/dist/routes/error-response.js +31 -0
  176. package/dist/routes/error.d.ts +8 -0
  177. package/dist/routes/error.js +14 -0
  178. package/dist/routes/evidence.d.ts +5 -0
  179. package/dist/routes/evidence.js +38 -0
  180. package/dist/routes/index.d.ts +19 -0
  181. package/dist/routes/index.js +16 -0
  182. package/dist/routes/mapping.d.ts +5 -0
  183. package/dist/routes/mapping.js +126 -0
  184. package/dist/routes/match.d.ts +4 -0
  185. package/dist/routes/match.js +7 -0
  186. package/dist/routes/placement.d.ts +5 -0
  187. package/dist/routes/placement.js +18 -0
  188. package/dist/routes/plan.d.ts +24 -0
  189. package/dist/routes/plan.js +1 -0
  190. package/dist/routes/release-unavailable.d.ts +8 -0
  191. package/dist/routes/release-unavailable.js +32 -0
  192. package/dist/routes/request.d.ts +26 -0
  193. package/dist/routes/request.js +53 -0
  194. package/dist/routes/reservation.d.ts +3 -0
  195. package/dist/routes/reservation.js +37 -0
  196. package/dist/routes/routing.d.ts +22 -0
  197. package/dist/routes/routing.js +87 -0
  198. package/dist/routes/scalar.d.ts +7 -0
  199. package/dist/routes/scalar.js +61 -0
  200. package/dist/routes/select.d.ts +3 -0
  201. package/dist/routes/select.js +16 -0
  202. package/dist/routes/stream.d.ts +14 -0
  203. package/dist/routes/stream.js +50 -0
  204. package/dist/routes/table.d.ts +9 -0
  205. package/dist/routes/table.js +1 -0
  206. package/dist/routes/target.d.ts +13 -0
  207. package/dist/routes/target.js +3 -0
  208. package/dist/routes/template.d.ts +8 -0
  209. package/dist/routes/template.js +227 -0
  210. package/dist/runtime/artifact.d.ts +47 -0
  211. package/dist/runtime/artifact.js +176 -0
  212. package/dist/runtime/index.d.ts +1 -0
  213. package/dist/runtime/index.js +1 -0
  214. package/dist/schema/index.d.ts +1 -0
  215. package/dist/schema/index.js +1 -0
  216. package/dist/schema/installation/index.d.ts +1 -0
  217. package/dist/schema/installation/index.js +1 -0
  218. package/dist/schema/installation/introspection/codec.d.ts +5 -0
  219. package/dist/schema/installation/introspection/codec.js +272 -0
  220. package/dist/schema/installation/introspection/index.d.ts +2 -0
  221. package/dist/schema/installation/introspection/index.js +1 -0
  222. package/dist/schema/installation/introspection/result.d.ts +33 -0
  223. package/dist/schema/installation/introspection/result.js +1 -0
  224. package/package.json +163 -0
@@ -0,0 +1,61 @@
1
+ import { invalidRequest } from './error.js';
2
+ const JSON_NUMBER = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/u;
3
+ export function encodeScalar(entry, name, value, domain) {
4
+ const encoded = scalar(value);
5
+ if (name === receiver(entry))
6
+ return encoded;
7
+ const decoded = decodeScalar(entry, name, encoded, domain);
8
+ if (!sameScalar(decoded, value)) {
9
+ invalidRequest(`Native route field ${name} does not round-trip through its declared value schema.`);
10
+ }
11
+ return encoded;
12
+ }
13
+ export function decodeScalar(entry, name, encoded, domain) {
14
+ if (name === receiver(entry))
15
+ return encoded;
16
+ const meaning = domain.callable(entry.callable);
17
+ if (meaning?.inputFields?.includes(name) !== true) {
18
+ invalidRequest(`Native route maps unknown input field ${name}.`);
19
+ }
20
+ const candidates = [encoded];
21
+ if (encoded === 'true')
22
+ candidates.push(true);
23
+ else if (encoded === 'false')
24
+ candidates.push(false);
25
+ else if (encoded === 'null')
26
+ candidates.push(null);
27
+ if (JSON_NUMBER.test(encoded)) {
28
+ const number = Number(encoded);
29
+ if (Number.isFinite(number))
30
+ candidates.push(number);
31
+ }
32
+ const accepted = candidates.filter((candidate) => meaning.validateInputField(name, candidate).length === 0);
33
+ if (accepted.length === 0) {
34
+ invalidRequest(`Native route field ${name} is invalid under its declared value schema.`);
35
+ }
36
+ if (accepted.length > 1) {
37
+ invalidRequest(`Native route field ${name} has an ambiguous scalar wire representation.`);
38
+ }
39
+ return accepted[0];
40
+ }
41
+ function receiver(entry) {
42
+ return entry.target.kind === 'method' && entry.target.dispatch === 'instance'
43
+ ? entry.target.receiver
44
+ : undefined;
45
+ }
46
+ export function scalar(value) {
47
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
48
+ return String(value);
49
+ }
50
+ return invalidRequest('Native route field must be a string, number, or boolean at this wire source.');
51
+ }
52
+ export function asObject(value) {
53
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
54
+ invalidRequest('Native route input must be an object.');
55
+ }
56
+ return value;
57
+ }
58
+ function sameScalar(left, right) {
59
+ return (Object.is(left, right) ||
60
+ (typeof left === 'number' && typeof right === 'number' && left === 0 && right === 0));
61
+ }
@@ -0,0 +1,3 @@
1
+ import type { Key } from '@astrale-os/kernel-dsl/v1';
2
+ import type { Route, Table } from './table.js';
3
+ export declare function select(table: Table, callable: Key.Callable, route?: string): Route | undefined;
@@ -0,0 +1,16 @@
1
+ import { RouteError } from './error.js';
2
+ export function select(table, callable, route) {
3
+ const candidates = table.entries.filter((entry) => entry.callable === callable);
4
+ if (route !== undefined) {
5
+ const selected = candidates.find((entry) => entry.name === route);
6
+ if (selected === undefined) {
7
+ throw new RouteError('NATIVE_ROUTE_NOT_FOUND', `Unknown route ${route} for ${callable}.`);
8
+ }
9
+ return selected;
10
+ }
11
+ if (candidates.length === 0)
12
+ return undefined;
13
+ if (candidates.length === 1)
14
+ return candidates[0];
15
+ return candidates.find((entry) => entry.default);
16
+ }
@@ -0,0 +1,14 @@
1
+ import { type Value } from '@astrale-os/kernel-dsl/value';
2
+ import type { ErrorPayload } from '../errors/index.js';
3
+ export declare const STREAM_MEDIA_TYPE: 'text/event-stream';
4
+ export type StreamFrame = {
5
+ readonly kind: 'item';
6
+ readonly value: Value;
7
+ } | {
8
+ readonly kind: 'end';
9
+ } | {
10
+ readonly kind: 'error';
11
+ readonly error: ErrorPayload;
12
+ };
13
+ export declare function encodeStreamFrame(input: StreamFrame): string;
14
+ export declare function decodeStreamFrame(event: string, data: string): StreamFrame;
@@ -0,0 +1,50 @@
1
+ import { DecodeError, json } from '@astrale-os/kernel-dsl/value';
2
+ import { acceptPayload } from '../errors/index.js';
3
+ import { fail } from './error.js';
4
+ export const STREAM_MEDIA_TYPE = 'text/event-stream';
5
+ export function encodeStreamFrame(input) {
6
+ switch (input.kind) {
7
+ case 'item':
8
+ return `data: ${json.serialize(input.value)}\n\n`;
9
+ case 'end':
10
+ return 'event: end\ndata: {}\n\n';
11
+ case 'error':
12
+ return `event: error\ndata: ${json.serialize(input.error)}\n\n`;
13
+ }
14
+ }
15
+ export function decodeStreamFrame(event, data) {
16
+ let value;
17
+ try {
18
+ value = json.decode(data);
19
+ }
20
+ catch (cause) {
21
+ if (cause instanceof DecodeError) {
22
+ return fail('NATIVE_ROUTE_RESPONSE_INVALID', 'Native stream event data is invalid.', cause);
23
+ }
24
+ throw cause;
25
+ }
26
+ if (event === '' || event === 'message') {
27
+ return Object.freeze({ kind: 'item', value });
28
+ }
29
+ if (event === 'end') {
30
+ if (value === null ||
31
+ typeof value !== 'object' ||
32
+ Array.isArray(value) ||
33
+ Reflect.ownKeys(value).length !== 0) {
34
+ fail('NATIVE_ROUTE_RESPONSE_INVALID', 'Native stream end event is invalid.');
35
+ }
36
+ return Object.freeze({ kind: 'end' });
37
+ }
38
+ if (event === 'error') {
39
+ try {
40
+ return Object.freeze({ kind: 'error', error: acceptPayload(value) });
41
+ }
42
+ catch (cause) {
43
+ if (cause instanceof TypeError) {
44
+ return fail('NATIVE_ROUTE_RESPONSE_INVALID', 'Native stream error event is invalid.', cause);
45
+ }
46
+ throw cause;
47
+ }
48
+ }
49
+ return fail('NATIVE_ROUTE_RESPONSE_INVALID', `Unknown native stream event ${JSON.stringify(event)}.`);
50
+ }
@@ -0,0 +1,9 @@
1
+ import type { PublicationRef } from '../publication/index.js';
2
+ import type { PlanEntry } from './plan.js';
3
+ export interface Route extends Omit<PlanEntry, 'path'> {
4
+ readonly href: string;
5
+ }
6
+ export interface Table {
7
+ readonly publication: PublicationRef;
8
+ readonly entries: readonly Route[];
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ export type RouteTarget = {
2
+ readonly kind: 'function';
3
+ } | {
4
+ readonly kind: 'method';
5
+ readonly dispatch: 'static';
6
+ } | {
7
+ readonly kind: 'method';
8
+ readonly dispatch: 'instance';
9
+ readonly receiver: string;
10
+ };
11
+ export declare function requiresReceiver(entry: {
12
+ readonly target: RouteTarget;
13
+ }): boolean;
@@ -0,0 +1,3 @@
1
+ export function requiresReceiver(entry) {
2
+ return entry.target.kind === 'method' && entry.target.dispatch === 'instance';
3
+ }
@@ -0,0 +1,8 @@
1
+ export declare const PLACEHOLDER: RegExp;
2
+ export declare function validateTemplate(href: string): void;
3
+ export declare function templateUrl(href: string): URL;
4
+ export declare function routePattern(href: string): string;
5
+ export declare function templatesOverlap(left: string, right: string): boolean;
6
+ export declare function matchTemplate(template: string, input: string): Record<string, string> | undefined;
7
+ export declare function encodeTemplate(value: unknown, catchall: string | undefined, host: boolean): string;
8
+ export declare function authorityEnd(input: string): number;
@@ -0,0 +1,227 @@
1
+ import { fail, invalidRequest } from './error.js';
2
+ export const PLACEHOLDER = /\{([A-Za-z][A-Za-z0-9_]*)([+*])?\}/gu;
3
+ const WHOLE_PLACEHOLDER = /^\{([A-Za-z][A-Za-z0-9_]*)([+*])?\}$/u;
4
+ const HOST_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u;
5
+ export function validateTemplate(href) {
6
+ validateHostTemplate(href);
7
+ const pathname = rawTemplatePath(href);
8
+ for (const segment of pathname.split('/')) {
9
+ if ((segment.includes('{') || segment.includes('}')) && !WHOLE_PLACEHOLDER.test(segment)) {
10
+ fail('NATIVE_ROUTE_DECLARATION_INVALID', 'Route placeholders must occupy one complete path segment.');
11
+ }
12
+ }
13
+ const placeholders = [...href.matchAll(PLACEHOLDER)];
14
+ const names = placeholders.map((match) => match[1]);
15
+ if (new Set(names).size !== names.length) {
16
+ fail('NATIVE_ROUTE_MAPPING_AMBIGUOUS', 'Route href uses one placeholder more than once.');
17
+ }
18
+ const catchalls = placeholders.filter((match) => match[2] !== undefined);
19
+ if (catchalls.length > 1 ||
20
+ (catchalls[0] !== undefined && catchalls[0].index + catchalls[0][0].length !== href.length)) {
21
+ fail('NATIVE_ROUTE_DECLARATION_INVALID', 'A catchall placeholder must be unique and terminal.');
22
+ }
23
+ templateUrl(href);
24
+ }
25
+ export function templateUrl(href) {
26
+ try {
27
+ const substituted = href.replaceAll(PLACEHOLDER, 'astrale-placeholder');
28
+ const url = new URL(substituted);
29
+ if ((url.protocol !== 'http:' && url.protocol !== 'https:') ||
30
+ url.username !== '' ||
31
+ url.password !== '' ||
32
+ url.search !== '' ||
33
+ url.hash !== '' ||
34
+ url.toString() !== substituted) {
35
+ throw new TypeError();
36
+ }
37
+ return url;
38
+ }
39
+ catch (cause) {
40
+ if (cause instanceof TypeError) {
41
+ return fail('NATIVE_ROUTE_DECLARATION_INVALID', 'Route href must be an absolute HTTP(S) template without credentials, query, or fragment.', cause);
42
+ }
43
+ throw cause;
44
+ }
45
+ }
46
+ export function routePattern(href) {
47
+ return href.replaceAll(PLACEHOLDER, (_whole, _name, catchall) => catchall === '+' ? '{+}' : catchall === '*' ? '{*}' : '{}');
48
+ }
49
+ export function templatesOverlap(left, right) {
50
+ const leftUrl = templateUrl(left);
51
+ const rightUrl = templateUrl(right);
52
+ if (leftUrl.protocol !== rightUrl.protocol || leftUrl.port !== rightUrl.port)
53
+ return false;
54
+ const leftHost = templateHostSegments(left);
55
+ const rightHost = templateHostSegments(right);
56
+ if (leftHost.length !== rightHost.length ||
57
+ leftHost.some((segment, index) => !hostSegmentsOverlap(segment, rightHost[index]))) {
58
+ return false;
59
+ }
60
+ const leftSegments = templateSegments(left);
61
+ const rightSegments = templateSegments(right);
62
+ const leftCatchall = leftSegments.findIndex((segment) => segment.kind.endsWith('catchall'));
63
+ const rightCatchall = rightSegments.findIndex((segment) => segment.kind.endsWith('catchall'));
64
+ const shared = Math.min(leftCatchall < 0 ? leftSegments.length : leftCatchall, rightCatchall < 0 ? rightSegments.length : rightCatchall);
65
+ for (let index = 0; index < shared; index += 1) {
66
+ if (!segmentsOverlap(leftSegments[index], rightSegments[index]))
67
+ return false;
68
+ }
69
+ if (leftCatchall < 0 && rightCatchall < 0) {
70
+ return leftSegments.length === rightSegments.length;
71
+ }
72
+ if (leftCatchall >= 0 && rightCatchall >= 0)
73
+ return true;
74
+ const catchall = leftCatchall >= 0 ? leftCatchall : rightCatchall;
75
+ const catchallSegment = (leftCatchall >= 0 ? leftSegments : rightSegments)[catchall];
76
+ const fixed = leftCatchall >= 0 ? rightSegments : leftSegments;
77
+ const remainder = fixed.slice(catchall);
78
+ if (catchallSegment.kind === 'optional-catchall')
79
+ return remainder.length > 0;
80
+ return (remainder.length > 1 ||
81
+ (remainder[0] !== undefined &&
82
+ (remainder[0].kind !== 'literal' || remainder[0].value.length > 0)));
83
+ }
84
+ export function matchTemplate(template, input) {
85
+ try {
86
+ const url = new URL(input);
87
+ url.search = '';
88
+ url.hash = '';
89
+ input = url.toString();
90
+ }
91
+ catch (cause) {
92
+ if (cause instanceof TypeError)
93
+ return undefined;
94
+ throw cause;
95
+ }
96
+ const names = [];
97
+ let cursor = 0;
98
+ let pattern = '^';
99
+ for (const match of template.matchAll(PLACEHOLDER)) {
100
+ pattern += escapePattern(template.slice(cursor, match.index));
101
+ names.push(match[1]);
102
+ pattern +=
103
+ match.index < authorityEnd(template)
104
+ ? '([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)'
105
+ : match[2] === '+'
106
+ ? '(.+)'
107
+ : match[2] === '*'
108
+ ? '(.*)'
109
+ : '([^/?#]+)';
110
+ cursor = match.index + match[0].length;
111
+ }
112
+ pattern += `${escapePattern(template.slice(cursor))}$`;
113
+ const matched = new RegExp(pattern, 'u').exec(input);
114
+ if (matched === null)
115
+ return undefined;
116
+ const output = {};
117
+ for (const [index, name] of names.entries()) {
118
+ try {
119
+ output[name] = decodeURIComponent(matched[index + 1]);
120
+ }
121
+ catch (cause) {
122
+ if (cause instanceof URIError)
123
+ return undefined;
124
+ throw cause;
125
+ }
126
+ }
127
+ return output;
128
+ }
129
+ export function encodeTemplate(value, catchall, host) {
130
+ if (typeof value !== 'string')
131
+ invalidRequest('Native route template value must be a string.');
132
+ if (host) {
133
+ if (!HOST_LABEL.test(value)) {
134
+ invalidRequest('Native route host field must be one canonical DNS label.');
135
+ }
136
+ return value;
137
+ }
138
+ if (catchall === '+' && value.length === 0) {
139
+ invalidRequest('A required catchall route value cannot be empty.');
140
+ }
141
+ return catchall === '+' || catchall === '*'
142
+ ? value.split('/').map(encodeURIComponent).join('/')
143
+ : encodeURIComponent(value);
144
+ }
145
+ export function authorityEnd(input) {
146
+ const start = input.indexOf('://') + 3;
147
+ const end = input.indexOf('/', start);
148
+ return end < 0 ? input.length : end;
149
+ }
150
+ function templateHostSegments(input) {
151
+ validateHostTemplate(input);
152
+ const url = templateUrl(input);
153
+ const raw = authority(input);
154
+ const hostname = raw.startsWith('[')
155
+ ? raw.slice(0, raw.indexOf(']') + 1)
156
+ : url.port === ''
157
+ ? raw
158
+ : raw.slice(0, -(url.port.length + 1));
159
+ return hostname
160
+ .split('.')
161
+ .map((segment) => WHOLE_PLACEHOLDER.test(segment)
162
+ ? Object.freeze({ kind: 'parameter' })
163
+ : Object.freeze({ kind: 'literal', value: segment }));
164
+ }
165
+ function hostSegmentsOverlap(left, right) {
166
+ if (left.kind === 'literal' && right.kind === 'literal')
167
+ return left.value === right.value;
168
+ const literal = left.kind === 'literal' ? left.value : right.kind === 'literal' ? right.value : undefined;
169
+ return literal === undefined || HOST_LABEL.test(literal);
170
+ }
171
+ function validateHostTemplate(input) {
172
+ const start = input.indexOf('://') + 3;
173
+ const end = authorityEnd(input);
174
+ for (const match of input.matchAll(PLACEHOLDER)) {
175
+ if (match.index >= end)
176
+ continue;
177
+ const after = match.index + match[0].length;
178
+ if (match[2] !== undefined ||
179
+ (match.index !== start && input[match.index - 1] !== '.') ||
180
+ (after !== end && input[after] !== '.' && input[after] !== ':')) {
181
+ fail('NATIVE_ROUTE_DECLARATION_INVALID', 'A host placeholder must occupy one complete DNS label and cannot be a catchall.');
182
+ }
183
+ }
184
+ }
185
+ function authority(input) {
186
+ const start = input.indexOf('://') + 3;
187
+ return input.slice(start, authorityEnd(input));
188
+ }
189
+ function rawTemplatePath(input) {
190
+ const authority = input.indexOf('://');
191
+ const start = input.indexOf('/', authority + 3);
192
+ return start < 0 ? '/' : input.slice(start);
193
+ }
194
+ function templateSegments(input) {
195
+ templateUrl(input);
196
+ validateHostTemplate(input);
197
+ return rawTemplatePath(input)
198
+ .split('/')
199
+ .slice(1)
200
+ .map((segment) => {
201
+ const placeholder = WHOLE_PLACEHOLDER.exec(segment);
202
+ if (placeholder !== null) {
203
+ return Object.freeze({
204
+ kind: placeholder[2] === '+'
205
+ ? 'catchall'
206
+ : placeholder[2] === '*'
207
+ ? 'optional-catchall'
208
+ : 'parameter',
209
+ });
210
+ }
211
+ if (segment.includes('{') || segment.includes('}')) {
212
+ fail('NATIVE_ROUTE_DECLARATION_INVALID', 'Route placeholders must occupy one complete path segment.');
213
+ }
214
+ return Object.freeze({ kind: 'literal', value: segment });
215
+ });
216
+ }
217
+ function segmentsOverlap(left, right) {
218
+ if (left.kind !== 'literal' && right.kind !== 'literal')
219
+ return true;
220
+ if (left.kind === 'literal' && right.kind === 'literal')
221
+ return left.value === right.value;
222
+ const literal = left.kind === 'literal' ? left.value : right.value;
223
+ return literal.length > 0;
224
+ }
225
+ function escapePattern(value) {
226
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
227
+ }
@@ -0,0 +1,47 @@
1
+ import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
2
+ import type { Key, Revision } from '@astrale-os/kernel-dsl/v1';
3
+ import type { Digest, Ref } from '../artifact/index.js';
4
+ import type { PublicationRequirements } from '../publication/index.js';
5
+ export interface ExecutableDependency {
6
+ readonly path: string;
7
+ readonly ref: Ref;
8
+ }
9
+ export interface Executable {
10
+ readonly format: 'astrale.executable-artifact';
11
+ readonly version: 2;
12
+ readonly content: Ref;
13
+ readonly loader: 'node-esm' | 'oci-image' | 'immutable-package';
14
+ readonly entrypoint: string;
15
+ readonly runtimeAbi: string;
16
+ readonly dependencies: readonly ExecutableDependency[];
17
+ readonly closure: Digest;
18
+ readonly sbom: Ref;
19
+ readonly digest: Digest;
20
+ }
21
+ export interface Export {
22
+ readonly executable: Executable;
23
+ readonly name: string;
24
+ }
25
+ export interface Module {
26
+ readonly origin: Key.Origin;
27
+ readonly revision: Revision;
28
+ readonly factory: Export;
29
+ readonly configuration: Ref | null;
30
+ }
31
+ /** Immutable portable closure selected by one local Schema installation source. */
32
+ export interface RuntimeArtifact {
33
+ readonly format: 'astrale.domain-runtime-artifact';
34
+ readonly version: 2;
35
+ readonly issuer: IssuerId;
36
+ readonly bundle: Ref;
37
+ readonly requirements: PublicationRequirements;
38
+ readonly modules: readonly [Module, ...Module[]];
39
+ }
40
+ export declare const MEDIA_TYPE: 'application/vnd.astrale.domain-runtime+json;v=2';
41
+ export declare function accept(input: unknown): RuntimeArtifact;
42
+ export declare function encode(input: RuntimeArtifact): Uint8Array;
43
+ export declare function runtimeDigest(input: RuntimeArtifact): Digest;
44
+ export declare function acceptExecutable(input: unknown): Executable;
45
+ export declare function executableDigest(input: Omit<Executable, 'digest'>): Digest;
46
+ export declare function executableClosure(input: readonly ExecutableDependency[]): Digest;
47
+ export type { Digest, Ref };
@@ -0,0 +1,176 @@
1
+ import { accept as acceptIssuer } from '@astrale-os/kernel-core/auth/issuer';
2
+ import { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
3
+ import * as portable from '@astrale-os/kernel-dsl/value';
4
+ import { sha256 } from '@noble/hashes/sha2.js';
5
+ import * as artifact from '../artifact/index.js';
6
+ import { admitRequirements } from '../publication/index.js';
7
+ export const MEDIA_TYPE = 'application/vnd.astrale.domain-runtime+json;v=2';
8
+ export function accept(input) {
9
+ const root = record(input, 'Domain Runtime artifact');
10
+ exact(root, ['bundle', 'format', 'issuer', 'modules', 'requirements', 'version']);
11
+ if (root.format !== 'astrale.domain-runtime-artifact' || root.version !== 2) {
12
+ invalid('Domain Runtime artifact format or version is unsupported.');
13
+ }
14
+ if (typeof root.issuer !== 'string')
15
+ invalid('Domain Runtime artifact issuer is invalid.');
16
+ const admittedIssuer = acceptIssuer(root.issuer);
17
+ const bundle = artifact.accept(root.bundle);
18
+ const requirements = admitRequirements(root.requirements);
19
+ if (!Array.isArray(root.modules) || root.modules.length === 0) {
20
+ invalid('Domain Runtime artifact requires at least one Module.');
21
+ }
22
+ const modules = root.modules.map((value, index) => module(value, index));
23
+ const identities = modules.map(({ origin, revision }) => `${origin}\0${revision}`);
24
+ if (identities.some((identity, index) => index > 0 && identities[index - 1] >= identity)) {
25
+ invalid('Domain Runtime Modules must be unique and canonically ordered.');
26
+ }
27
+ return Object.freeze({
28
+ format: 'astrale.domain-runtime-artifact',
29
+ version: 2,
30
+ issuer: admittedIssuer,
31
+ bundle,
32
+ requirements,
33
+ modules: Object.freeze(modules),
34
+ });
35
+ }
36
+ export function encode(input) {
37
+ return portable.json.encode(accept(input));
38
+ }
39
+ export function runtimeDigest(input) {
40
+ return digestBytes(portable.json.encode(input));
41
+ }
42
+ export function acceptExecutable(input) {
43
+ return executable(input, 0);
44
+ }
45
+ export function executableDigest(input) {
46
+ return digestBytes(portable.json.encode(input));
47
+ }
48
+ export function executableClosure(input) {
49
+ return digestBytes(portable.json.encode(input));
50
+ }
51
+ function module(input, index) {
52
+ const value = record(input, `Domain Runtime Module ${index}`);
53
+ exact(value, ['configuration', 'factory', 'origin', 'revision']);
54
+ if (typeof value.origin !== 'string' || !patterns.origin.test(value.origin)) {
55
+ invalid(`Domain Runtime Module ${index} origin is invalid.`);
56
+ }
57
+ if (typeof value.revision !== 'string' || !patterns.revision.test(value.revision)) {
58
+ invalid(`Domain Runtime Module ${index} revision is invalid.`);
59
+ }
60
+ const factory = executableExport(value.factory, index);
61
+ return Object.freeze({
62
+ origin: value.origin,
63
+ revision: value.revision,
64
+ factory,
65
+ configuration: value.configuration === null ? null : artifact.accept(value.configuration),
66
+ });
67
+ }
68
+ function executableExport(input, index) {
69
+ const value = record(input, `Domain Runtime Module ${index} factory`);
70
+ exact(value, ['executable', 'name']);
71
+ if (typeof value.name !== 'string' || value.name.length === 0) {
72
+ invalid(`Domain Runtime Module ${index} export name is invalid.`);
73
+ }
74
+ return Object.freeze({ executable: executable(value.executable, index), name: value.name });
75
+ }
76
+ function executable(input, index) {
77
+ const value = record(input, `Domain Runtime Module ${index} executable`);
78
+ exact(value, [
79
+ 'closure',
80
+ 'content',
81
+ 'dependencies',
82
+ 'digest',
83
+ 'entrypoint',
84
+ 'format',
85
+ 'loader',
86
+ 'runtimeAbi',
87
+ 'sbom',
88
+ 'version',
89
+ ]);
90
+ if (value.format !== 'astrale.executable-artifact' || value.version !== 2) {
91
+ invalid(`Domain Runtime Module ${index} executable format or version is unsupported.`);
92
+ }
93
+ if (value.loader !== 'node-esm' &&
94
+ value.loader !== 'oci-image' &&
95
+ value.loader !== 'immutable-package') {
96
+ invalid(`Domain Runtime Module ${index} executable loader is unsupported.`);
97
+ }
98
+ if (typeof value.entrypoint !== 'string' ||
99
+ value.entrypoint.length === 0 ||
100
+ typeof value.runtimeAbi !== 'string' ||
101
+ value.runtimeAbi.length === 0) {
102
+ invalid(`Domain Runtime Module ${index} executable coordinate is invalid.`);
103
+ }
104
+ if (!Array.isArray(value.dependencies)) {
105
+ invalid(`Domain Runtime Module ${index} executable dependencies are invalid.`);
106
+ }
107
+ const dependencies = value.dependencies.map((entry, dependencyIndex) => {
108
+ const dependency = record(entry, `Executable dependency ${dependencyIndex}`);
109
+ exact(dependency, ['path', 'ref']);
110
+ if (typeof dependency.path !== 'string' || dependency.path.length === 0) {
111
+ invalid(`Executable dependency ${dependencyIndex} is invalid.`);
112
+ }
113
+ return Object.freeze({
114
+ path: dependency.path,
115
+ ref: artifact.accept(dependency.ref),
116
+ });
117
+ });
118
+ if (dependencies.some((dependency, dependencyIndex) => dependencyIndex > 0 && dependencies[dependencyIndex - 1].path >= dependency.path)) {
119
+ invalid(`Domain Runtime Module ${index} executable dependencies are not canonical.`);
120
+ }
121
+ const closure = admittedDigest(value.closure, `Domain Runtime Module ${index} executable closure`);
122
+ if (executableClosure(dependencies) !== closure) {
123
+ invalid(`Domain Runtime Module ${index} executable dependency closure is invalid.`);
124
+ }
125
+ const body = Object.freeze({
126
+ format: 'astrale.executable-artifact',
127
+ version: 2,
128
+ content: artifact.accept(value.content),
129
+ loader: value.loader,
130
+ entrypoint: value.entrypoint,
131
+ runtimeAbi: value.runtimeAbi,
132
+ dependencies: Object.freeze(dependencies),
133
+ closure,
134
+ sbom: artifact.accept(value.sbom),
135
+ });
136
+ const digest = admittedDigest(value.digest, `Domain Runtime Module ${index} executable`);
137
+ if (executableDigest(body) !== digest) {
138
+ invalid(`Domain Runtime Module ${index} executable digest is invalid.`);
139
+ }
140
+ return Object.freeze({ ...body, digest });
141
+ }
142
+ function admittedDigest(input, label) {
143
+ if (typeof input !== 'string' || !/^sha256:[0-9a-f]{64}$/u.test(input)) {
144
+ invalid(`${label} digest is invalid.`);
145
+ }
146
+ return input;
147
+ }
148
+ function digestBytes(input) {
149
+ let hexadecimal = '';
150
+ for (const byte of sha256(input))
151
+ hexadecimal += byte.toString(16).padStart(2, '0');
152
+ return `sha256:${hexadecimal}`;
153
+ }
154
+ function record(input, label) {
155
+ let admitted;
156
+ try {
157
+ admitted = portable.accept(input);
158
+ }
159
+ catch (cause) {
160
+ throw new TypeError(`${label} is not a portable value.`, { cause });
161
+ }
162
+ if (admitted === null || typeof admitted !== 'object' || Array.isArray(admitted)) {
163
+ invalid(`${label} must be an object.`);
164
+ }
165
+ return admitted;
166
+ }
167
+ function exact(input, expected) {
168
+ const actual = Object.keys(input).sort();
169
+ const wanted = [...expected].sort();
170
+ if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
171
+ invalid('Domain Runtime artifact fields are invalid.');
172
+ }
173
+ }
174
+ function invalid(message) {
175
+ throw new TypeError(message);
176
+ }
@@ -0,0 +1 @@
1
+ export { MEDIA_TYPE, accept, acceptExecutable, encode, executableClosure, executableDigest, runtimeDigest, type Digest, type Executable, type ExecutableDependency, type Export, type Module, type RuntimeArtifact, type Ref, } from './artifact.js';
@@ -0,0 +1 @@
1
+ export { MEDIA_TYPE, accept, acceptExecutable, encode, executableClosure, executableDigest, runtimeDigest, } from './artifact.js';
@@ -0,0 +1 @@
1
+ export * as installation from './installation/index.js';
@@ -0,0 +1 @@
1
+ export * as installation from './installation/index.js';
@@ -0,0 +1 @@
1
+ export * as introspection from './introspection/index.js';
@@ -0,0 +1 @@
1
+ export * as introspection from './introspection/index.js';
@@ -0,0 +1,5 @@
1
+ import type { introspection } from '@astrale-os/kernel-core/schema';
2
+ import type { Limits, ResultFor } from './result.js';
3
+ export declare const MEDIA_TYPE: 'application/vnd.astrale.schema-introspection.v1+msgpack';
4
+ export declare function encode<Input extends introspection.Introspection>(input: Input, result: ResultFor<Input>): Promise<Uint8Array>;
5
+ export declare function decode<Input extends introspection.Introspection>(input: Input, bytes: Uint8Array, limits: Limits): Promise<ResultFor<Input>>;