@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,31 @@
1
+ import { BoundaryError, accept as acceptValue, } from '@astrale-os/kernel-dsl/value';
2
+ import { acceptPayload } from '../errors/index.js';
3
+ import { fail } from './error.js';
4
+ export function decodeErrorResponse(input) {
5
+ let value;
6
+ try {
7
+ value = acceptValue(input);
8
+ }
9
+ catch (cause) {
10
+ if (cause instanceof BoundaryError)
11
+ return invalid(cause);
12
+ throw cause;
13
+ }
14
+ if (!isValueObject(value) || Object.keys(value).length !== 1 || !Object.hasOwn(value, 'error')) {
15
+ return invalid();
16
+ }
17
+ try {
18
+ return Object.freeze({ error: acceptPayload(value.error, 'native route error') });
19
+ }
20
+ catch (cause) {
21
+ if (cause instanceof TypeError)
22
+ return invalid(cause);
23
+ throw cause;
24
+ }
25
+ }
26
+ function isValueObject(value) {
27
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
28
+ }
29
+ function invalid(cause) {
30
+ return fail('NATIVE_ROUTE_RESPONSE_INVALID', 'Native route error response is invalid.', cause);
31
+ }
@@ -0,0 +1,8 @@
1
+ export type ErrorCode = 'NATIVE_ROUTE_DECLARATION_INVALID' | 'NATIVE_ROUTE_BINDING_INVALID' | 'NATIVE_ROUTE_MAPPING_INCOMPLETE' | 'NATIVE_ROUTE_MAPPING_AMBIGUOUS' | 'NATIVE_ROUTE_COLLISION' | 'NATIVE_ROUTE_RESERVED' | 'NATIVE_ROUTE_NOT_FOUND' | 'NATIVE_ROUTE_REQUEST_INVALID' | 'NATIVE_ROUTE_RESPONSE_INVALID' | 'NATIVE_ROUTE_ROUTING_INVALID';
2
+ export declare class RouteError extends Error {
3
+ readonly code: ErrorCode;
4
+ readonly name = "RouteError";
5
+ constructor(code: ErrorCode, message: string, options?: ErrorOptions);
6
+ }
7
+ export declare function fail(code: ErrorCode, message: string, cause?: unknown): never;
8
+ export declare function invalidRequest(message: string): never;
@@ -0,0 +1,14 @@
1
+ export class RouteError extends Error {
2
+ code;
3
+ name = 'RouteError';
4
+ constructor(code, message, options) {
5
+ super(message, options);
6
+ this.code = code;
7
+ }
8
+ }
9
+ export function fail(code, message, cause) {
10
+ throw new RouteError(code, message, cause === undefined ? undefined : { cause });
11
+ }
12
+ export function invalidRequest(message) {
13
+ return fail('NATIVE_ROUTE_REQUEST_INVALID', message);
14
+ }
@@ -0,0 +1,5 @@
1
+ import type { Domain } from '@astrale-os/kernel-dsl/v1';
2
+ import type { Publication } from '../publication/publication.js';
3
+ import type { Table } from './table.js';
4
+ /** Verify that an admitted Table carries the exact published wire and Domain meaning. */
5
+ export declare function verifyTable(table: Table, deployed: Publication, domain: Pick<Domain, 'origin' | 'revision' | 'callable'>): Table;
@@ -0,0 +1,38 @@
1
+ import { json } from '@astrale-os/kernel-dsl/value';
2
+ import { compareReferences, reference } from '../publication/reference.js';
3
+ import { fail } from './error.js';
4
+ /** Verify that an admitted Table carries the exact published wire and Domain meaning. */
5
+ export function verifyTable(table, deployed, domain) {
6
+ if (compareReferences(table.publication, reference(deployed)) !== 'exact' ||
7
+ domain.origin !== deployed.origin ||
8
+ domain.revision !== deployed.schema.revision ||
9
+ json.serialize(table.entries.map(wireRoute)) !==
10
+ json.serialize(deployed.routes)) {
11
+ fail('NATIVE_ROUTE_BINDING_INVALID', 'Route Table evidence differs from its Publication.');
12
+ }
13
+ for (const entry of table.entries) {
14
+ const meaning = domain.callable(entry.callable);
15
+ const targetMatches = meaning !== undefined &&
16
+ ((entry.target.kind === 'function' && !isMethod(meaning)) ||
17
+ (entry.target.kind === 'method' &&
18
+ isMethod(meaning) &&
19
+ entry.target.dispatch === (meaning.static ? 'static' : 'instance')));
20
+ if (!targetMatches || meaning.auth !== entry.auth || meaning.output.mode !== entry.output) {
21
+ fail('NATIVE_ROUTE_BINDING_INVALID', `Route ${entry.name} differs from Domain meaning.`);
22
+ }
23
+ }
24
+ return table;
25
+ }
26
+ function isMethod(meaning) {
27
+ return 'owner' in meaning;
28
+ }
29
+ function wireRoute(entry) {
30
+ const { target, auth: _auth, output: _output, default: isDefault, ...wire } = entry;
31
+ return {
32
+ ...wire,
33
+ ...(isDefault ? { default: true } : {}),
34
+ ...(target.kind === 'method' && target.dispatch === 'instance'
35
+ ? { receiver: target.receiver }
36
+ : {}),
37
+ };
38
+ }
@@ -0,0 +1,19 @@
1
+ export { bind } from './binding.js';
2
+ export { acceptTable } from './admission.js';
3
+ export { compile, type CompileInput } from './compiler.js';
4
+ export { decode } from './decode.js';
5
+ export { decodeErrorResponse, type ErrorResponse } from './error-response.js';
6
+ export { encode } from './encode.js';
7
+ export { verifyTable } from './evidence.js';
8
+ export { type ErrorCode, RouteError } from './error.js';
9
+ export { match } from './match.js';
10
+ export { type HttpRequest, type Invocation, type Request } from './request.js';
11
+ export type { Plan, PlanEntry } from './plan.js';
12
+ export type { Route, Table } from './table.js';
13
+ export type { RouteTarget } from './target.js';
14
+ export { place } from './placement.js';
15
+ export { decodeReleaseUnavailable, HTTP_ROUTE_RELEASE_UNAVAILABLE, HTTP_ROUTE_RELEASE_UNAVAILABLE_STATUS, releaseUnavailableHeaders, type ReleaseUnavailable, } from './release-unavailable.js';
16
+ export { decodeRouting, decodeMiss, decodeStale, HTTP_ROUTE_PUBLICATION, HTTP_ROUTE_ISSUER, HTTP_ROUTE_MISS, HTTP_ROUTE_MISS_STATUS, HTTP_ROUTE_STALE, HTTP_ROUTE_STALE_STATUS, missHeaders, staleHeaders, type MissControl, type StaleControl, } from './routing.js';
17
+ export { select } from './select.js';
18
+ export { decodeStreamFrame, encodeStreamFrame, STREAM_MEDIA_TYPE, type StreamFrame, } from './stream.js';
19
+ export { templatesOverlap } from './template.js';
@@ -0,0 +1,16 @@
1
+ export { bind } from './binding.js';
2
+ export { acceptTable } from './admission.js';
3
+ export { compile } from './compiler.js';
4
+ export { decode } from './decode.js';
5
+ export { decodeErrorResponse } from './error-response.js';
6
+ export { encode } from './encode.js';
7
+ export { verifyTable } from './evidence.js';
8
+ export { RouteError } from './error.js';
9
+ export { match } from './match.js';
10
+ export {} from './request.js';
11
+ export { place } from './placement.js';
12
+ export { decodeReleaseUnavailable, HTTP_ROUTE_RELEASE_UNAVAILABLE, HTTP_ROUTE_RELEASE_UNAVAILABLE_STATUS, releaseUnavailableHeaders, } from './release-unavailable.js';
13
+ export { decodeRouting, decodeMiss, decodeStale, HTTP_ROUTE_PUBLICATION, HTTP_ROUTE_ISSUER, HTTP_ROUTE_MISS, HTTP_ROUTE_MISS_STATUS, HTTP_ROUTE_STALE, HTTP_ROUTE_STALE_STATUS, missHeaders, staleHeaders, } from './routing.js';
14
+ export { select } from './select.js';
15
+ export { decodeStreamFrame, encodeStreamFrame, STREAM_MEDIA_TYPE, } from './stream.js';
16
+ export { templatesOverlap } from './template.js';
@@ -0,0 +1,5 @@
1
+ import type { HttpRoute } from '../publication/index.js';
2
+ export declare function validateMapping(route: HttpRoute, fields: readonly string[] | undefined, instanceMethod: boolean): void;
3
+ export declare function validateWireMapping(route: HttpRoute): void;
4
+ export declare function validateCredentialMapping(route: HttpRoute): void;
5
+ export declare function isReservedHeader(input: string): boolean;
@@ -0,0 +1,126 @@
1
+ import { HTTP_IDEMPOTENCY_KEY } from '../invocation/index.js';
2
+ import { credentialSources } from './credential.js';
3
+ import { fail } from './error.js';
4
+ import { HTTP_ROUTE_RELEASE_UNAVAILABLE } from './release-unavailable.js';
5
+ import { HTTP_ROUTE_ISSUER, HTTP_ROUTE_MISS, HTTP_ROUTE_PUBLICATION, HTTP_ROUTE_STALE, } from './routing.js';
6
+ import { PLACEHOLDER } from './template.js';
7
+ export function validateMapping(route, fields, instanceMethod) {
8
+ if (fields === undefined) {
9
+ fail('NATIVE_ROUTE_DECLARATION_INVALID', `Route ${route.name} requires a closed object input schema.`);
10
+ }
11
+ if (route.receiver !== undefined && fields.includes(route.receiver)) {
12
+ fail('NATIVE_ROUTE_MAPPING_AMBIGUOUS', `Route ${route.name} receiver cannot also be a callable input field.`);
13
+ }
14
+ const claims = new Map();
15
+ const claim = (field) => claims.set(field, (claims.get(field) ?? 0) + 1);
16
+ for (const match of route.href.matchAll(PLACEHOLDER))
17
+ claim(match[1]);
18
+ if (route.requestMethod !== undefined)
19
+ claim(route.requestMethod);
20
+ for (const field of Object.keys(route.query ?? {}))
21
+ claim(field);
22
+ for (const field of Object.keys(route.headers ?? {}))
23
+ claim(field);
24
+ if (route.body === '*') {
25
+ for (const field of fields)
26
+ if (!claims.has(field))
27
+ claim(field);
28
+ if (route.receiver !== undefined && !claims.has(route.receiver))
29
+ claim(route.receiver);
30
+ }
31
+ else {
32
+ for (const field of Object.keys(route.body ?? {}))
33
+ claim(field);
34
+ }
35
+ for (const field of claims.keys()) {
36
+ if (!fields.includes(field) && field !== route.receiver) {
37
+ fail('NATIVE_ROUTE_DECLARATION_INVALID', `Route ${route.name} maps unknown input field ${field}.`);
38
+ }
39
+ }
40
+ for (const field of fields) {
41
+ const count = claims.get(field) ?? 0;
42
+ if (count === 0) {
43
+ fail('NATIVE_ROUTE_MAPPING_INCOMPLETE', `Route ${route.name} does not map input field ${field}.`);
44
+ }
45
+ if (count > 1) {
46
+ fail('NATIVE_ROUTE_MAPPING_AMBIGUOUS', `Route ${route.name} maps input field ${field} more than once.`);
47
+ }
48
+ }
49
+ if (instanceMethod) {
50
+ if (route.receiver === undefined || claims.get(route.receiver) !== 1) {
51
+ fail('NATIVE_ROUTE_MAPPING_INCOMPLETE', `Instance route ${route.name} must map one receiver source.`);
52
+ }
53
+ if (route.headers?.[route.receiver] === '*') {
54
+ fail('NATIVE_ROUTE_DECLARATION_INVALID', `Instance route ${route.name} receiver must use a scalar wire source.`);
55
+ }
56
+ }
57
+ else if (route.receiver !== undefined) {
58
+ fail('NATIVE_ROUTE_DECLARATION_INVALID', `Route ${route.name} cannot map a receiver for a Function or static Method.`);
59
+ }
60
+ const textPicks = route.body === '*' || route.body === undefined
61
+ ? []
62
+ : Object.values(route.body).filter((pick) => typeof pick !== 'string');
63
+ if (textPicks.length > 1 ||
64
+ (textPicks.length === 1 && Object.keys(route.body).length !== 1)) {
65
+ fail('NATIVE_ROUTE_MAPPING_AMBIGUOUS', 'Whole-text body mapping must be the only body pick.');
66
+ }
67
+ }
68
+ export function validateWireMapping(route) {
69
+ uniqueWires(route, 'query', Object.values(route.query ?? {}));
70
+ const headerPicks = Object.values(route.headers ?? {});
71
+ if (headerPicks.filter((wire) => wire === '*').length > 1) {
72
+ fail('NATIVE_ROUTE_MAPPING_AMBIGUOUS', `Route ${route.name} has more than one wildcard header remainder.`);
73
+ }
74
+ uniqueWires(route, 'header', headerPicks.filter((wire) => wire !== '*').map((wire) => wire.toLowerCase()));
75
+ const reserved = headerPicks.find((wire) => wire !== '*' && RESERVED_HEADERS.has(wire.toLowerCase()));
76
+ if (reserved !== undefined) {
77
+ fail('NATIVE_ROUTE_DECLARATION_INVALID', `Route ${route.name} maps the reserved ${reserved.toLowerCase()} header.`);
78
+ }
79
+ if (route.body !== undefined && route.body !== '*') {
80
+ uniqueWires(route, 'body', Object.values(route.body).flatMap((pick) => (typeof pick === 'string' ? [pick] : [])));
81
+ }
82
+ }
83
+ export function validateCredentialMapping(route) {
84
+ const queryWires = new Set(Object.values(route.query ?? {}));
85
+ const headerWires = new Set(Object.values(route.headers ?? {})
86
+ .filter((wire) => wire !== '*')
87
+ .map((wire) => wire.toLowerCase()));
88
+ const credentialWires = new Set();
89
+ for (const source of credentialSources(route)) {
90
+ const coordinate = source.in === 'header'
91
+ ? `header:${(source.name ?? 'authorization').toLowerCase()}`
92
+ : `${source.in}:${source.name}`;
93
+ if (credentialWires.has(coordinate)) {
94
+ fail('NATIVE_ROUTE_MAPPING_AMBIGUOUS', `Route ${route.name} declares one credential wire source more than once.`);
95
+ }
96
+ credentialWires.add(coordinate);
97
+ const credentialHeader = source.in === 'header' ? (source.name ?? 'authorization').toLowerCase() : undefined;
98
+ if (credentialHeader !== undefined && RESERVED_HEADERS.has(credentialHeader)) {
99
+ fail('NATIVE_ROUTE_DECLARATION_INVALID', `Route ${route.name} uses the reserved ${credentialHeader} header as a credential.`);
100
+ }
101
+ const collision = source.in === 'query'
102
+ ? queryWires.has(source.name)
103
+ : source.in === 'cookie'
104
+ ? headerWires.has('cookie')
105
+ : headerWires.has((source.name ?? 'authorization').toLowerCase());
106
+ if (collision) {
107
+ fail('NATIVE_ROUTE_MAPPING_AMBIGUOUS', `Route ${route.name} maps one wire source as both input and credential.`);
108
+ }
109
+ }
110
+ }
111
+ const RESERVED_HEADERS = new Set([
112
+ HTTP_IDEMPOTENCY_KEY,
113
+ HTTP_ROUTE_ISSUER,
114
+ HTTP_ROUTE_MISS,
115
+ HTTP_ROUTE_PUBLICATION,
116
+ HTTP_ROUTE_RELEASE_UNAVAILABLE,
117
+ HTTP_ROUTE_STALE,
118
+ ]);
119
+ export function isReservedHeader(input) {
120
+ return RESERVED_HEADERS.has(input.toLowerCase());
121
+ }
122
+ function uniqueWires(route, kind, wires) {
123
+ if (new Set(wires).size !== wires.length) {
124
+ fail('NATIVE_ROUTE_MAPPING_AMBIGUOUS', `Route ${route.name} maps one ${kind} wire source more than once.`);
125
+ }
126
+ }
@@ -0,0 +1,4 @@
1
+ import type { HttpRequest } from './request.js';
2
+ import type { Route, Table } from './table.js';
3
+ export declare function match(table: Table, request: Pick<HttpRequest, 'url' | 'method'>): Route | undefined;
4
+ export declare function methodMatches(entry: Route, method: string): boolean;
@@ -0,0 +1,7 @@
1
+ import { matchTemplate } from './template.js';
2
+ export function match(table, request) {
3
+ return table.entries.find((entry) => methodMatches(entry, request.method) && matchTemplate(entry.href, request.url) !== undefined);
4
+ }
5
+ export function methodMatches(entry, method) {
6
+ return entry.method === '*' || entry.method === method.toUpperCase();
7
+ }
@@ -0,0 +1,5 @@
1
+ import type * as publication from '../publication/index.js';
2
+ import type { Plan } from './plan.js';
3
+ /** Resolve a provider-neutral Plan beneath one exact serving issuer. */
4
+ export declare function place(plan: Plan, issuer: string): readonly publication.HttpRoute[];
5
+ export declare function placePath(path: string, issuer: string): string;
@@ -0,0 +1,18 @@
1
+ /** Resolve a provider-neutral Plan beneath one exact serving issuer. */
2
+ export function place(plan, issuer) {
3
+ return Object.freeze(plan.entries.map((entry) => {
4
+ const { path, target, auth: _auth, output: _output, default: isDefault, ...wire } = entry;
5
+ return Object.freeze({
6
+ ...wire,
7
+ href: placePath(path, issuer),
8
+ ...(isDefault ? { default: true } : {}),
9
+ ...(target.kind === 'method' && target.dispatch === 'instance'
10
+ ? { receiver: target.receiver }
11
+ : {}),
12
+ });
13
+ }));
14
+ }
15
+ export function placePath(path, issuer) {
16
+ const base = new URL(issuer);
17
+ return `${base.origin}${path}`;
18
+ }
@@ -0,0 +1,24 @@
1
+ import type { FunctionAuth, Key, Revision } from '@astrale-os/kernel-dsl/v1';
2
+ import type { OutputMode } from '@astrale-os/kernel-dsl/v1/language';
3
+ import type { CredentialSource, HttpMethod, RouteBody, RouteHeaders } from '../publication/index.js';
4
+ import type { RouteTarget } from './target.js';
5
+ export interface PlanEntry {
6
+ readonly name: string;
7
+ readonly callable: Key.Callable;
8
+ readonly target: RouteTarget;
9
+ readonly auth: FunctionAuth;
10
+ readonly output: OutputMode;
11
+ readonly path: string;
12
+ readonly method: HttpMethod;
13
+ readonly requestMethod?: string;
14
+ readonly default: boolean;
15
+ readonly query?: Readonly<Record<string, string>>;
16
+ readonly headers?: RouteHeaders;
17
+ readonly body?: RouteBody;
18
+ readonly credential?: CredentialSource;
19
+ }
20
+ export interface Plan {
21
+ readonly origin: Key.Origin;
22
+ readonly revision: Revision;
23
+ readonly entries: readonly PlanEntry[];
24
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { HttpRequest } from './request.js';
2
+ export interface ReleaseUnavailable {
3
+ readonly kind: 'release-unavailable';
4
+ }
5
+ export declare const HTTP_ROUTE_RELEASE_UNAVAILABLE: 'x-astrale-release-unavailable';
6
+ export declare const HTTP_ROUTE_RELEASE_UNAVAILABLE_STATUS: 410;
7
+ export declare function releaseUnavailableHeaders(): Readonly<Record<string, string>>;
8
+ export declare function decodeReleaseUnavailable(status: number, headers: HttpRequest['headers']): ReleaseUnavailable | undefined;
@@ -0,0 +1,32 @@
1
+ import { fail } from './error.js';
2
+ export const HTTP_ROUTE_RELEASE_UNAVAILABLE = 'x-astrale-release-unavailable';
3
+ export const HTTP_ROUTE_RELEASE_UNAVAILABLE_STATUS = 410;
4
+ export function releaseUnavailableHeaders() {
5
+ return Object.freeze({
6
+ [HTTP_ROUTE_RELEASE_UNAVAILABLE]: '1',
7
+ 'cache-control': 'no-store',
8
+ });
9
+ }
10
+ export function decodeReleaseUnavailable(status, headers) {
11
+ const encoded = header(headers, HTTP_ROUTE_RELEASE_UNAVAILABLE);
12
+ if (encoded === undefined)
13
+ return undefined;
14
+ if (status !== HTTP_ROUTE_RELEASE_UNAVAILABLE_STATUS || encoded !== '1') {
15
+ fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_ROUTE_RELEASE_UNAVAILABLE} is reserved for status ${HTTP_ROUTE_RELEASE_UNAVAILABLE_STATUS} with value 1.`);
16
+ }
17
+ return Object.freeze({ kind: 'release-unavailable' });
18
+ }
19
+ function header(input, name) {
20
+ if (input instanceof Headers)
21
+ return input.get(name) ?? undefined;
22
+ let found;
23
+ for (const [candidate, value] of Object.entries(input ?? {})) {
24
+ if (candidate.toLowerCase() !== name || value === undefined)
25
+ continue;
26
+ if (found !== undefined || (Array.isArray(value) && value.length !== 1)) {
27
+ fail('NATIVE_ROUTE_ROUTING_INVALID', `Header ${name} must occur at most once.`);
28
+ }
29
+ found = typeof value === 'string' ? value : value[0];
30
+ }
31
+ return found;
32
+ }
@@ -0,0 +1,26 @@
1
+ import type { Value } from '@astrale-os/kernel-dsl/value';
2
+ import type { Call, Via } from '../invocation/index.js';
3
+ import type { HttpMethod } from '../publication/index.js';
4
+ /** Canonical invocation values presented to or returned from a native route mapping. */
5
+ export interface Invocation {
6
+ readonly call: Call;
7
+ readonly credential?: string;
8
+ readonly routing?: Via;
9
+ readonly method?: Exclude<HttpMethod, '*'>;
10
+ }
11
+ export interface Request {
12
+ readonly url: string;
13
+ readonly method: Exclude<HttpMethod, '*'>;
14
+ readonly headers: Readonly<Record<string, string>>;
15
+ readonly body?: string;
16
+ readonly credentials?: 'include';
17
+ }
18
+ export interface HttpRequest {
19
+ readonly url: string;
20
+ readonly method: string;
21
+ readonly headers?: Headers | Readonly<Record<string, string | readonly string[] | undefined>>;
22
+ readonly body?: string | Uint8Array;
23
+ }
24
+ export declare function bodyText(body: HttpRequest['body']): string;
25
+ export declare function parseJsonObject(body: HttpRequest['body']): Record<string, Value>;
26
+ export declare function normalizeHeaders(input: HttpRequest['headers']): Record<string, string>;
@@ -0,0 +1,53 @@
1
+ import { DecodeError, json } from '@astrale-os/kernel-dsl/value';
2
+ import { fail, invalidRequest } from './error.js';
3
+ export function bodyText(body) {
4
+ if (body === undefined)
5
+ return '';
6
+ try {
7
+ return typeof body === 'string'
8
+ ? body
9
+ : new TextDecoder('utf-8', { fatal: true, ignoreBOM: false }).decode(body);
10
+ }
11
+ catch (cause) {
12
+ if (cause instanceof TypeError) {
13
+ return fail('NATIVE_ROUTE_REQUEST_INVALID', 'Native route body is not valid UTF-8.', cause);
14
+ }
15
+ throw cause;
16
+ }
17
+ }
18
+ export function parseJsonObject(body) {
19
+ let decoded;
20
+ try {
21
+ decoded = json.decode(bodyText(body));
22
+ }
23
+ catch (cause) {
24
+ if (cause instanceof DecodeError) {
25
+ return fail('NATIVE_ROUTE_REQUEST_INVALID', 'Native route body must be a JSON object.', cause);
26
+ }
27
+ throw cause;
28
+ }
29
+ if (decoded === null || typeof decoded !== 'object' || Array.isArray(decoded)) {
30
+ fail('NATIVE_ROUTE_REQUEST_INVALID', 'Native route body must be a JSON object.');
31
+ }
32
+ return decoded;
33
+ }
34
+ export function normalizeHeaders(input) {
35
+ const output = {};
36
+ if (input instanceof Headers) {
37
+ input.forEach((value, key) => {
38
+ output[key.toLowerCase()] = value;
39
+ });
40
+ }
41
+ else {
42
+ for (const [key, value] of Object.entries(input ?? {})) {
43
+ if (value === undefined)
44
+ continue;
45
+ const canonical = key.toLowerCase();
46
+ if (Object.hasOwn(output, canonical)) {
47
+ invalidRequest(`Header ${canonical} occurs more than once.`);
48
+ }
49
+ output[canonical] = typeof value === 'string' ? value : value.join(', ');
50
+ }
51
+ }
52
+ return output;
53
+ }
@@ -0,0 +1,3 @@
1
+ import type { Publication } from '../publication/publication.js';
2
+ import type { Route } from './table.js';
3
+ export declare function rejectReserved(publication: Publication, entries: readonly Route[]): void;
@@ -0,0 +1,37 @@
1
+ import * as issuer from '../issuer/index.js';
2
+ import { PATH as PUBLICATION_PATH } from '../publication/publication.js';
3
+ import { fail } from './error.js';
4
+ import { templateUrl, templatesOverlap } from './template.js';
5
+ const STATIC_RESERVED_PATHS = new Set([PUBLICATION_PATH]);
6
+ export function rejectReserved(publication, entries) {
7
+ const discovery = issuer.paths(publication.identity.issuer);
8
+ const reservedPaths = new Set([...STATIC_RESERVED_PATHS, discovery.configuration, discovery.keys]);
9
+ const reservedUrls = [
10
+ publication.schema.bundle.href,
11
+ ...(publication.delivery === undefined ? [] : [publication.delivery.href]),
12
+ ...publication.bindings.callables.flatMap(({ endpoint }) => [
13
+ endpoint.http,
14
+ ...(endpoint.websocket === undefined ? [] : [httpUrlForWebSocket(endpoint.websocket)]),
15
+ ]),
16
+ ];
17
+ for (const entry of entries) {
18
+ const claimed = [...reservedPaths].some((path) => templateClaimsPath(entry.href, path)) ||
19
+ reservedUrls.some((reserved) => templatesOverlap(entry.href, routeLocation(reserved)));
20
+ if (claimed) {
21
+ fail('NATIVE_ROUTE_RESERVED', `Route ${entry.name} claims reserved URL ${templateUrl(entry.href).href}.`);
22
+ }
23
+ }
24
+ }
25
+ function httpUrlForWebSocket(input) {
26
+ const url = new URL(input);
27
+ url.protocol = url.protocol === 'wss:' ? 'https:' : 'http:';
28
+ return url.toString();
29
+ }
30
+ function routeLocation(input) {
31
+ const url = new URL(input);
32
+ return `${url.origin}${url.pathname}`;
33
+ }
34
+ function templateClaimsPath(template, path) {
35
+ const url = templateUrl(template);
36
+ return templatesOverlap(template, `${url.origin}${path}`);
37
+ }
@@ -0,0 +1,22 @@
1
+ import type * as invocation from '../invocation/index.js';
2
+ import type { Etag, PublicationRef } from '../publication/index.js';
3
+ import type { HttpRequest } from './request.js';
4
+ export interface StaleControl {
5
+ readonly kind: 'stale';
6
+ readonly current: Etag;
7
+ }
8
+ export interface MissControl {
9
+ readonly kind: 'miss';
10
+ }
11
+ export declare const HTTP_ROUTE_PUBLICATION: 'if-match';
12
+ export declare const HTTP_ROUTE_ISSUER: 'x-astrale-issuer';
13
+ export declare const HTTP_ROUTE_STALE: 'x-astrale-route-stale';
14
+ export declare const HTTP_ROUTE_STALE_STATUS: 412;
15
+ export declare const HTTP_ROUTE_MISS: 'x-astrale-route-miss';
16
+ export declare const HTTP_ROUTE_MISS_STATUS: 421;
17
+ export declare function decodeRouting(input: Pick<HttpRequest, 'headers'>, publication: PublicationRef): invocation.Via | undefined;
18
+ export declare function staleHeaders(current: Etag): Readonly<Record<string, string>>;
19
+ export declare function decodeStale(status: number, headers: HttpRequest['headers']): StaleControl | undefined;
20
+ export declare function missHeaders(): Readonly<Record<string, string>>;
21
+ export declare function decodeMiss(status: number, headers: HttpRequest['headers']): MissControl | undefined;
22
+ export declare function encodeStrongEtag(input: Etag): string;
@@ -0,0 +1,87 @@
1
+ import * as auth from '@astrale-os/kernel-core/auth';
2
+ import { fail } from './error.js';
3
+ export const HTTP_ROUTE_PUBLICATION = 'if-match';
4
+ export const HTTP_ROUTE_ISSUER = 'x-astrale-issuer';
5
+ export const HTTP_ROUTE_STALE = 'x-astrale-route-stale';
6
+ export const HTTP_ROUTE_STALE_STATUS = 412;
7
+ export const HTTP_ROUTE_MISS = 'x-astrale-route-miss';
8
+ export const HTTP_ROUTE_MISS_STATUS = 421;
9
+ const STRONG_ETAG = /^"(sha256:[0-9a-f]{64})"$/u;
10
+ export function decodeRouting(input, publication) {
11
+ const encodedEtag = routingHeader(input.headers, HTTP_ROUTE_PUBLICATION);
12
+ const encodedIssuer = routingHeader(input.headers, HTTP_ROUTE_ISSUER);
13
+ if (encodedEtag === undefined && encodedIssuer === undefined)
14
+ return undefined;
15
+ if (encodedEtag === undefined || encodedIssuer === undefined) {
16
+ fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_ROUTE_PUBLICATION} and ${HTTP_ROUTE_ISSUER} must occur together.`);
17
+ }
18
+ const etag = decodeStrongEtag(encodedEtag, 'If-Match');
19
+ if (etag !== publication.etag) {
20
+ fail('NATIVE_ROUTE_ROUTING_INVALID', 'If-Match does not name the compiled Publication.');
21
+ }
22
+ let issuer;
23
+ try {
24
+ issuer = auth.issuer.accept(encodedIssuer);
25
+ }
26
+ catch (cause) {
27
+ if (cause instanceof auth.AuthValueError) {
28
+ return fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_ROUTE_ISSUER} is invalid.`, cause);
29
+ }
30
+ throw cause;
31
+ }
32
+ return Object.freeze({ kind: 'via', publication, issuer });
33
+ }
34
+ export function staleHeaders(current) {
35
+ return Object.freeze({
36
+ [HTTP_ROUTE_STALE]: encodeStrongEtag(current),
37
+ 'cache-control': 'no-store',
38
+ });
39
+ }
40
+ export function decodeStale(status, headers) {
41
+ const encoded = routingHeader(headers, HTTP_ROUTE_STALE);
42
+ if (encoded === undefined)
43
+ return undefined;
44
+ if (status !== HTTP_ROUTE_STALE_STATUS) {
45
+ fail('NATIVE_ROUTE_ROUTING_INVALID', `Header ${HTTP_ROUTE_STALE} is reserved for status ${HTTP_ROUTE_STALE_STATUS}.`);
46
+ }
47
+ return Object.freeze({
48
+ kind: 'stale',
49
+ current: decodeStrongEtag(encoded, HTTP_ROUTE_STALE),
50
+ });
51
+ }
52
+ export function missHeaders() {
53
+ return Object.freeze({ [HTTP_ROUTE_MISS]: '1', 'cache-control': 'no-store' });
54
+ }
55
+ export function decodeMiss(status, headers) {
56
+ const encoded = routingHeader(headers, HTTP_ROUTE_MISS);
57
+ if (encoded === undefined)
58
+ return undefined;
59
+ if (status !== HTTP_ROUTE_MISS_STATUS || encoded !== '1') {
60
+ fail('NATIVE_ROUTE_ROUTING_INVALID', `Header ${HTTP_ROUTE_MISS} is reserved for status ${HTTP_ROUTE_MISS_STATUS} with value 1.`);
61
+ }
62
+ return Object.freeze({ kind: 'miss' });
63
+ }
64
+ export function encodeStrongEtag(input) {
65
+ return `"${input}"`;
66
+ }
67
+ function decodeStrongEtag(input, label) {
68
+ const matched = STRONG_ETAG.exec(input);
69
+ if (matched === null) {
70
+ fail('NATIVE_ROUTE_ROUTING_INVALID', `${label} must contain exactly one strong quoted Publication ETag.`);
71
+ }
72
+ return matched[1];
73
+ }
74
+ function routingHeader(input, name) {
75
+ if (input instanceof Headers)
76
+ return input.get(name) ?? undefined;
77
+ let found;
78
+ for (const [candidate, value] of Object.entries(input ?? {})) {
79
+ if (candidate.toLowerCase() !== name || value === undefined)
80
+ continue;
81
+ if (found !== undefined || (Array.isArray(value) && value.length !== 1)) {
82
+ fail('NATIVE_ROUTE_ROUTING_INVALID', `Header ${name} must occur at most once.`);
83
+ }
84
+ found = typeof value === 'string' ? value : value[0];
85
+ }
86
+ return found;
87
+ }
@@ -0,0 +1,7 @@
1
+ import type { Domain } from '@astrale-os/kernel-dsl/v1';
2
+ import type * as portable from '@astrale-os/kernel-dsl/value';
3
+ import type { Route } from './table.js';
4
+ export declare function encodeScalar(entry: Route, name: string, value: unknown, domain: Domain): string;
5
+ export declare function decodeScalar(entry: Route, name: string, encoded: string, domain: Domain): portable.Value;
6
+ export declare function scalar(value: unknown): string;
7
+ export declare function asObject(value: unknown): Record<string, portable.Value>;