@astrale-os/kernel-server 0.5.0-beta.8 → 1.0.0-beta.1

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 (241) hide show
  1. package/dist/capture.d.ts +0 -1
  2. package/dist/capture.js +0 -1
  3. package/dist/cors/cors.d.ts +0 -1
  4. package/dist/cors/cors.js +11 -4
  5. package/dist/cors/index.d.ts +0 -1
  6. package/dist/cors/index.js +0 -1
  7. package/dist/cors/types.d.ts +0 -1
  8. package/dist/cors/types.js +0 -1
  9. package/dist/errors/admission.d.ts +7 -0
  10. package/dist/errors/admission.js +22 -0
  11. package/dist/errors/availability.d.ts +0 -1
  12. package/dist/errors/availability.js +0 -1
  13. package/dist/errors/classify.d.ts +8 -1
  14. package/dist/errors/classify.js +15 -1
  15. package/dist/errors/index.d.ts +2 -2
  16. package/dist/errors/index.js +2 -2
  17. package/dist/errors/request-body.d.ts +0 -1
  18. package/dist/errors/request-body.js +0 -1
  19. package/dist/errors/response-body.d.ts +0 -1
  20. package/dist/errors/response-body.js +0 -1
  21. package/dist/index.d.ts +3 -4
  22. package/dist/index.js +2 -3
  23. package/dist/invocation/context.d.ts +3 -3
  24. package/dist/invocation/context.js +2 -2
  25. package/dist/invocation/identity.d.ts +1 -2
  26. package/dist/invocation/identity.js +1 -2
  27. package/dist/invocation/index.d.ts +1 -2
  28. package/dist/invocation/index.js +1 -2
  29. package/dist/invocation/release-route.d.ts +22 -0
  30. package/dist/invocation/release-route.js +32 -0
  31. package/dist/invocation/types.d.ts +8 -7
  32. package/dist/invocation/types.js +0 -1
  33. package/dist/issuer/exchange.d.ts +0 -1
  34. package/dist/issuer/exchange.js +20 -10
  35. package/dist/issuer/index.d.ts +0 -1
  36. package/dist/issuer/index.js +0 -1
  37. package/dist/issuer/routes.d.ts +0 -1
  38. package/dist/issuer/routes.js +57 -48
  39. package/dist/issuer/types.d.ts +5 -1
  40. package/dist/issuer/types.js +0 -1
  41. package/dist/lifecycle/active.d.ts +4 -1
  42. package/dist/lifecycle/active.js +48 -11
  43. package/dist/lifecycle/index.d.ts +0 -1
  44. package/dist/lifecycle/index.js +0 -1
  45. package/dist/release/delivery/handler.d.ts +9 -0
  46. package/dist/{publication → release}/delivery/handler.js +19 -13
  47. package/dist/release/delivery/index.d.ts +1 -0
  48. package/dist/release/delivery/index.js +1 -0
  49. package/dist/{publication → release}/delivery/types.d.ts +4 -5
  50. package/dist/release/delivery/types.js +1 -0
  51. package/dist/release/index.d.ts +3 -0
  52. package/dist/release/index.js +2 -0
  53. package/dist/release/served.d.ts +21 -0
  54. package/dist/release/served.js +10 -0
  55. package/dist/release/snapshot.d.ts +22 -0
  56. package/dist/release/snapshot.js +49 -0
  57. package/dist/release/table.d.ts +4 -0
  58. package/dist/release/table.js +11 -0
  59. package/dist/release/types.d.ts +23 -0
  60. package/dist/release/types.js +1 -0
  61. package/dist/routes/address.d.ts +2 -0
  62. package/dist/routes/address.js +6 -0
  63. package/dist/routes/binary.d.ts +9 -0
  64. package/dist/routes/binary.js +110 -0
  65. package/dist/routes/bundle.d.ts +5 -0
  66. package/dist/routes/bundle.js +50 -0
  67. package/dist/routes/csrf.d.ts +16 -0
  68. package/dist/routes/csrf.js +35 -0
  69. package/dist/routes/document.d.ts +6 -0
  70. package/dist/routes/document.js +57 -0
  71. package/dist/routes/index.d.ts +3 -2
  72. package/dist/routes/index.js +2 -2
  73. package/dist/routes/model.d.ts +15 -0
  74. package/dist/routes/model.js +1 -0
  75. package/dist/routes/native.d.ts +17 -0
  76. package/dist/routes/native.js +126 -0
  77. package/dist/routes/preflight.d.ts +4 -0
  78. package/dist/routes/preflight.js +55 -0
  79. package/dist/routes/response.d.ts +14 -0
  80. package/dist/routes/response.js +33 -0
  81. package/dist/routes/routes.d.ts +7 -24
  82. package/dist/routes/routes.js +24 -439
  83. package/dist/routes/snapshot.d.ts +13 -0
  84. package/dist/routes/snapshot.js +39 -0
  85. package/dist/routes/stream.d.ts +6 -0
  86. package/dist/routes/stream.js +122 -0
  87. package/dist/server.d.ts +0 -1
  88. package/dist/server.js +95 -51
  89. package/dist/transport/body.d.ts +0 -1
  90. package/dist/transport/body.js +0 -1
  91. package/dist/transport/codecs.d.ts +2 -3
  92. package/dist/transport/codecs.js +4 -4
  93. package/dist/transport/credential.d.ts +0 -1
  94. package/dist/transport/credential.js +0 -1
  95. package/dist/transport/http/binary-request.d.ts +11 -0
  96. package/dist/transport/http/binary-request.js +102 -0
  97. package/dist/transport/http/binary.d.ts +6 -0
  98. package/dist/transport/http/binary.js +35 -0
  99. package/dist/transport/http/byte-stream.d.ts +4 -0
  100. package/dist/transport/http/byte-stream.js +123 -0
  101. package/dist/transport/http/control.d.ts +12 -0
  102. package/dist/transport/http/control.js +24 -0
  103. package/dist/transport/http/handler.d.ts +8 -3
  104. package/dist/transport/http/handler.js +97 -45
  105. package/dist/transport/http/index.d.ts +3 -2
  106. package/dist/transport/http/index.js +2 -2
  107. package/dist/transport/http/model.d.ts +20 -0
  108. package/dist/transport/http/model.js +1 -0
  109. package/dist/transport/http/render.d.ts +3 -31
  110. package/dist/transport/http/render.js +18 -269
  111. package/dist/transport/http/response.d.ts +14 -0
  112. package/dist/transport/http/response.js +77 -0
  113. package/dist/transport/http/stream.d.ts +5 -0
  114. package/dist/transport/http/stream.js +134 -0
  115. package/dist/transport/index.d.ts +2 -2
  116. package/dist/transport/index.js +2 -2
  117. package/dist/transport/result.d.ts +13 -0
  118. package/dist/transport/result.js +35 -0
  119. package/dist/transport/router.d.ts +0 -1
  120. package/dist/transport/router.js +0 -1
  121. package/dist/transport/stream-error.d.ts +0 -1
  122. package/dist/transport/stream-error.js +0 -1
  123. package/dist/transport/types.d.ts +1 -2
  124. package/dist/transport/types.js +0 -1
  125. package/dist/transport/websocket/index.d.ts +0 -1
  126. package/dist/transport/websocket/index.js +0 -1
  127. package/dist/transport/websocket/session.d.ts +3 -3
  128. package/dist/transport/websocket/session.js +30 -30
  129. package/dist/transport/websocket/upgrade.d.ts +2 -3
  130. package/dist/transport/websocket/upgrade.js +7 -7
  131. package/dist/types.d.ts +33 -10
  132. package/dist/types.js +0 -1
  133. package/package.json +11 -22
  134. package/dist/capture.d.ts.map +0 -1
  135. package/dist/capture.js.map +0 -1
  136. package/dist/cors/cors.d.ts.map +0 -1
  137. package/dist/cors/cors.js.map +0 -1
  138. package/dist/cors/index.d.ts.map +0 -1
  139. package/dist/cors/index.js.map +0 -1
  140. package/dist/cors/types.d.ts.map +0 -1
  141. package/dist/cors/types.js.map +0 -1
  142. package/dist/errors/availability.d.ts.map +0 -1
  143. package/dist/errors/availability.js.map +0 -1
  144. package/dist/errors/classify.d.ts.map +0 -1
  145. package/dist/errors/classify.js.map +0 -1
  146. package/dist/errors/index.d.ts.map +0 -1
  147. package/dist/errors/index.js.map +0 -1
  148. package/dist/errors/request-body.d.ts.map +0 -1
  149. package/dist/errors/request-body.js.map +0 -1
  150. package/dist/errors/response-body.d.ts.map +0 -1
  151. package/dist/errors/response-body.js.map +0 -1
  152. package/dist/index.d.ts.map +0 -1
  153. package/dist/index.js.map +0 -1
  154. package/dist/invocation/context.d.ts.map +0 -1
  155. package/dist/invocation/context.js.map +0 -1
  156. package/dist/invocation/identity.d.ts.map +0 -1
  157. package/dist/invocation/identity.js.map +0 -1
  158. package/dist/invocation/index.d.ts.map +0 -1
  159. package/dist/invocation/index.js.map +0 -1
  160. package/dist/invocation/publication-route.d.ts +0 -14
  161. package/dist/invocation/publication-route.d.ts.map +0 -1
  162. package/dist/invocation/publication-route.js +0 -45
  163. package/dist/invocation/publication-route.js.map +0 -1
  164. package/dist/invocation/types.d.ts.map +0 -1
  165. package/dist/invocation/types.js.map +0 -1
  166. package/dist/issuer/exchange.d.ts.map +0 -1
  167. package/dist/issuer/exchange.js.map +0 -1
  168. package/dist/issuer/index.d.ts.map +0 -1
  169. package/dist/issuer/index.js.map +0 -1
  170. package/dist/issuer/routes.d.ts.map +0 -1
  171. package/dist/issuer/routes.js.map +0 -1
  172. package/dist/issuer/types.d.ts.map +0 -1
  173. package/dist/issuer/types.js.map +0 -1
  174. package/dist/lifecycle/active.d.ts.map +0 -1
  175. package/dist/lifecycle/active.js.map +0 -1
  176. package/dist/lifecycle/index.d.ts.map +0 -1
  177. package/dist/lifecycle/index.js.map +0 -1
  178. package/dist/publication/delivery/handler.d.ts +0 -10
  179. package/dist/publication/delivery/handler.d.ts.map +0 -1
  180. package/dist/publication/delivery/handler.js.map +0 -1
  181. package/dist/publication/delivery/index.d.ts +0 -3
  182. package/dist/publication/delivery/index.d.ts.map +0 -1
  183. package/dist/publication/delivery/index.js +0 -2
  184. package/dist/publication/delivery/index.js.map +0 -1
  185. package/dist/publication/delivery/types.d.ts.map +0 -1
  186. package/dist/publication/delivery/types.js +0 -2
  187. package/dist/publication/delivery/types.js.map +0 -1
  188. package/dist/publication/index.d.ts +0 -4
  189. package/dist/publication/index.d.ts.map +0 -1
  190. package/dist/publication/index.js +0 -3
  191. package/dist/publication/index.js.map +0 -1
  192. package/dist/publication/snapshot.d.ts +0 -16
  193. package/dist/publication/snapshot.d.ts.map +0 -1
  194. package/dist/publication/snapshot.js +0 -39
  195. package/dist/publication/snapshot.js.map +0 -1
  196. package/dist/publication/table.d.ts +0 -4
  197. package/dist/publication/table.d.ts.map +0 -1
  198. package/dist/publication/table.js +0 -17
  199. package/dist/publication/table.js.map +0 -1
  200. package/dist/publication/types.d.ts +0 -23
  201. package/dist/publication/types.d.ts.map +0 -1
  202. package/dist/publication/types.js +0 -2
  203. package/dist/publication/types.js.map +0 -1
  204. package/dist/publication/validator.d.ts +0 -4
  205. package/dist/publication/validator.d.ts.map +0 -1
  206. package/dist/publication/validator.js +0 -17
  207. package/dist/publication/validator.js.map +0 -1
  208. package/dist/routes/index.d.ts.map +0 -1
  209. package/dist/routes/index.js.map +0 -1
  210. package/dist/routes/routes.d.ts.map +0 -1
  211. package/dist/routes/routes.js.map +0 -1
  212. package/dist/server.d.ts.map +0 -1
  213. package/dist/server.js.map +0 -1
  214. package/dist/transport/body.d.ts.map +0 -1
  215. package/dist/transport/body.js.map +0 -1
  216. package/dist/transport/codecs.d.ts.map +0 -1
  217. package/dist/transport/codecs.js.map +0 -1
  218. package/dist/transport/credential.d.ts.map +0 -1
  219. package/dist/transport/credential.js.map +0 -1
  220. package/dist/transport/http/handler.d.ts.map +0 -1
  221. package/dist/transport/http/handler.js.map +0 -1
  222. package/dist/transport/http/index.d.ts.map +0 -1
  223. package/dist/transport/http/index.js.map +0 -1
  224. package/dist/transport/http/render.d.ts.map +0 -1
  225. package/dist/transport/http/render.js.map +0 -1
  226. package/dist/transport/index.d.ts.map +0 -1
  227. package/dist/transport/index.js.map +0 -1
  228. package/dist/transport/router.d.ts.map +0 -1
  229. package/dist/transport/router.js.map +0 -1
  230. package/dist/transport/stream-error.d.ts.map +0 -1
  231. package/dist/transport/stream-error.js.map +0 -1
  232. package/dist/transport/types.d.ts.map +0 -1
  233. package/dist/transport/types.js.map +0 -1
  234. package/dist/transport/websocket/index.d.ts.map +0 -1
  235. package/dist/transport/websocket/index.js.map +0 -1
  236. package/dist/transport/websocket/session.d.ts.map +0 -1
  237. package/dist/transport/websocket/session.js.map +0 -1
  238. package/dist/transport/websocket/upgrade.d.ts.map +0 -1
  239. package/dist/transport/websocket/upgrade.js.map +0 -1
  240. package/dist/types.d.ts.map +0 -1
  241. package/dist/types.js.map +0 -1
@@ -1,449 +1,34 @@
1
- import * as codecs from '@astrale-os/kernel-protocol/codecs';
2
- import * as errors from '@astrale-os/kernel-protocol/errors';
3
- import * as protocol from '@astrale-os/kernel-protocol/invocation';
4
- import { PATH as PUBLICATION_PATH, url as publicationUrl, } from '@astrale-os/kernel-protocol/publication/publication';
5
- import { reference as publicationReference } from '@astrale-os/kernel-protocol/publication/reference';
6
- import * as routes from '@astrale-os/kernel-protocol/routes';
7
- import { classifyError, RequestBodyInvalidError, RequestBodyTooLargeError, } from '../errors/index.js';
8
- import { invocationContext } from '../invocation/index.js';
9
- import { handleDelivery } from '../publication/delivery/index.js';
10
- import { readPreparedPublicationSnapshot, } from '../publication/index.js';
11
- import { readBoundedBody } from '../transport/body.js';
12
- import {} from '../transport/router.js';
13
- import { interruptedStreamError } from '../transport/stream-error.js';
14
- export function mountRoutes(app, source, deliveryProvider, handler, active, nextInvocation, limits, cors, hostFor, reservations, externalIssuer) {
15
- const publicationPath = externalIssuer === undefined
16
- ? PUBLICATION_PATH
17
- : new URL(publicationUrl(externalIssuer)).pathname;
18
- const externalOrigin = externalIssuer === undefined ? undefined : new URL(externalIssuer).origin;
19
- if (cors.enabled) {
20
- app.options(publicationPath, (context) => cors.preflight(context.req.raw, {
21
- methods: ['GET'],
22
- headers: ['if-none-match'],
23
- }));
24
- }
25
- app.get(publicationPath, (context) => {
26
- const responseCors = cors.response(context.req.raw, ['etag']);
27
- if (!active.isAccepting)
28
- return response({ error: 'Publication is unavailable while draining.' }, 503, responseCors);
29
- const snapshot = servingSnapshot(source, limits, context.req.raw, reservations, externalIssuer);
30
- if (snapshot === undefined)
31
- return response({ error: 'Publication is not ready.' }, 503, responseCors);
32
- const body = snapshot.publicationBytes;
33
- if (context.req.raw.headers.get('if-none-match') === quoteEtag(snapshot.publication.etag)) {
34
- return new Response(null, {
35
- status: 304,
36
- headers: {
37
- ...responseCors,
38
- etag: quoteEtag(snapshot.publication.etag),
39
- 'cache-control': 'public, max-age=0, must-revalidate, no-transform',
40
- },
41
- });
42
- }
43
- return new Response(toBody(body), {
44
- status: 200,
45
- headers: {
46
- ...responseCors,
47
- 'content-type': 'application/json',
48
- etag: quoteEtag(snapshot.publication.etag),
49
- 'cache-control': 'public, max-age=0, must-revalidate, no-transform',
50
- },
51
- });
52
- });
1
+ import { handleDelivery } from '../release/delivery/handler.js';
2
+ import { servedDocument } from '../release/served.js';
3
+ import { sameRoute } from './address.js';
4
+ import { bundleResponse } from './bundle.js';
5
+ import { mountDocuments } from './document.js';
6
+ import { nativeResponse } from './native.js';
7
+ import { releasePreflight } from './preflight.js';
8
+ import { servingSnapshot } from './snapshot.js';
9
+ /** Mount the advertised HTTP resources and native routes from one pinned serving snapshot. */
10
+ export function mountRoutes(app, source, deliveryProvider, handler, active, nextInvocation, limits, cors, hostFor, reservations, externalIssuer, publicOrigin) {
11
+ const externalOrigin = publicOrigin ?? (externalIssuer === undefined ? undefined : new URL(externalIssuer).origin);
12
+ const served = (request) => servingSnapshot(source, limits, request, reservations, externalIssuer, externalOrigin);
13
+ mountDocuments(app, cors, active, served, externalIssuer);
14
+ const native = { handler, active, nextInvocation, limits, cors, hostFor, externalOrigin };
53
15
  app.all('*', async (context) => {
54
- const snapshot = servingSnapshot(source, limits, context.req.raw, reservations, externalIssuer);
16
+ const request = context.req.raw;
17
+ const snapshot = served(request);
55
18
  if (snapshot === undefined)
56
19
  return context.notFound();
57
- const request = context.req.raw;
58
20
  if (request.method === 'OPTIONS' && cors.enabled) {
59
- return publicationPreflight(cors, request, snapshot, externalOrigin);
21
+ return releasePreflight(cors, request, snapshot, externalOrigin);
60
22
  }
61
23
  const responseCors = cors.response(request);
62
- if (sameRoute(request.url, snapshot.publication.schema.bundle.href, externalOrigin)) {
63
- if (!active.isAccepting)
64
- return response({ error: 'Publication artifact is unavailable while draining.' }, 503, responseCors);
65
- if (request.method !== 'GET')
66
- return response({ error: 'Bundle retrieval requires GET.' }, 405, responseCors);
67
- const body = snapshot.bundle;
68
- if (body.byteLength !== snapshot.publication.schema.bundle.ref.size) {
69
- return response({ error: 'Bundle differs from its Publication.' }, 500, responseCors);
70
- }
71
- return new Response(toBody(body), {
72
- status: 200,
73
- headers: {
74
- ...responseCors,
75
- 'content-type': snapshot.publication.schema.bundle.ref.mediaType,
76
- etag: quoteEtag(snapshot.publication.schema.bundle.ref.digest),
77
- 'cache-control': 'public, max-age=31536000, immutable, no-transform',
78
- },
79
- });
80
- }
81
- const deliveryUrl = snapshot.publication.delivery?.href;
24
+ const corsFor = (exposed) => cors.response(request, exposed);
25
+ const bundle = bundleResponse(request, snapshot, active, responseCors, corsFor, externalOrigin);
26
+ if (bundle !== undefined)
27
+ return bundle;
28
+ const deliveryUrl = servedDocument(snapshot).delivery?.href;
82
29
  if (deliveryUrl !== undefined && sameRoute(request.url, deliveryUrl, externalOrigin)) {
83
- return handleDelivery(request, snapshot, deliveryProvider, active, limits, cors.response(request));
84
- }
85
- let routing;
86
- try {
87
- const ifMatch = request.headers.get(routes.HTTP_ROUTE_PUBLICATION);
88
- if (ifMatch !== null &&
89
- /^"sha256:[0-9a-f]{64}"$/u.test(ifMatch) &&
90
- ifMatch !== quoteEtag(snapshot.publication.etag)) {
91
- return new Response(null, {
92
- status: routes.HTTP_ROUTE_STALE_STATUS,
93
- headers: {
94
- ...cors.response(request, [routes.HTTP_ROUTE_STALE]),
95
- ...routes.staleHeaders(snapshot.publication.etag),
96
- },
97
- });
98
- }
99
- routing = routes.decodeRouting(request, publicationReference(snapshot.publication));
100
- }
101
- catch (cause) {
102
- if (cause instanceof routes.RouteError) {
103
- return nativeError({ code: errors.CODES.INVALID_REQUEST, message: 'Native route routing is invalid.' }, 400, responseCors);
104
- }
105
- throw cause;
106
- }
107
- const route = routes.match(snapshot.table, request);
108
- if (route === undefined)
109
- return context.notFound();
110
- const assigned = nextInvocation();
111
- const operation = active.begin(limits.invocationTimeoutMs, request.signal);
112
- if (operation === undefined)
113
- return nativeError({
114
- code: errors.CODES.BACKEND_UNAVAILABLE,
115
- message: 'Server is draining.',
116
- }, 503, responseCors);
117
- let handedOff = false;
118
- try {
119
- const body = await readBoundedBody(request, limits.maximumRequestBytes, operation.signal);
120
- const invocation = routes.decode(route, { url: request.url, method: request.method, headers: request.headers, body }, snapshot.validator, snapshot.table.publication);
121
- const idempotencyHeader = request.headers.get(protocol.HTTP_IDEMPOTENCY_KEY);
122
- const idempotencyKey = idempotencyHeader === null
123
- ? undefined
124
- : protocol.acceptIdempotencyKey(idempotencyHeader, protocol.HTTP_IDEMPOTENCY_KEY);
125
- const result = await handler.dispatch(Object.freeze({
126
- call: invocation.call,
127
- ...(invocation.credential === undefined ? {} : { credential: invocation.credential }),
128
- ...(routing === undefined ? {} : { via: routing }),
129
- publication: snapshot.snapshot,
130
- context: invocationContext(assigned, operation, active, hostFor(request), body, idempotencyKey),
131
- }));
132
- if (result.kind === 'value') {
133
- return new Response(encodedBody(codecs.json.encode(result.value)), {
134
- headers: {
135
- ...responseCors,
136
- 'content-type': 'application/json',
137
- },
138
- });
139
- }
140
- if (result.kind === 'binary') {
141
- const applicationHeaders = applicationBinaryHeaders(result.value.headers);
142
- return new Response(toBody(result.value.body), {
143
- headers: {
144
- ...cors.response(request, [
145
- protocol.http.BINARY_HEADERS_FIELD,
146
- ...Object.keys(applicationHeaders),
147
- ]),
148
- ...applicationHeaders,
149
- 'content-type': result.value.mediaType,
150
- [protocol.http.BINARY_HEADERS_FIELD]: protocol.http.encodeBinaryHeaderNames(applicationHeaders),
151
- },
152
- });
153
- }
154
- if (result.kind === 'stream') {
155
- handedOff = true;
156
- return await streamResponse(result.stream, operation, responseCors);
157
- }
158
- if (result.kind === 'miss') {
159
- return new Response(null, {
160
- status: routes.HTTP_ROUTE_MISS_STATUS,
161
- headers: {
162
- ...cors.response(request, [routes.HTTP_ROUTE_MISS]),
163
- ...routes.missHeaders(),
164
- },
165
- });
166
- }
167
- return nativeError({
168
- code: errors.CODES.UNSUPPORTED,
169
- message: 'Native routes do not forward Runtime redirects.',
170
- }, 502, responseCors);
171
- }
172
- catch (cause) {
173
- if (cause instanceof RequestBodyTooLargeError) {
174
- return nativeError({ code: errors.CODES.LIMIT_EXCEEDED, message: 'Request body is too large.' }, 413, responseCors);
175
- }
176
- if (cause instanceof RequestBodyInvalidError) {
177
- return nativeError({ code: errors.CODES.INVALID_REQUEST, message: 'Request body is invalid.' }, 400, responseCors);
178
- }
179
- if (cause instanceof routes.RouteError) {
180
- return nativeError({ code: errors.CODES.INVALID_REQUEST, message: 'Native route request is invalid.' }, 400, responseCors);
181
- }
182
- if (operation.timedOut) {
183
- return nativeError({
184
- code: errors.CODES.DEADLINE_EXCEEDED,
185
- message: 'Invocation deadline exceeded.',
186
- }, 504, responseCors);
187
- }
188
- if (operation.signal.aborted) {
189
- return nativeError({ code: errors.CODES.CANCELLED, message: 'Invocation was cancelled.' }, 408, responseCors);
190
- }
191
- const payload = classifyError(cause);
192
- return nativeError(payload, errors.httpStatusOf(payload), responseCors);
193
- }
194
- finally {
195
- if (!handedOff)
196
- operation.settle();
197
- }
198
- });
199
- }
200
- function publicationPreflight(cors, request, snapshot, externalOrigin) {
201
- if (sameRoute(request.url, snapshot.publication.schema.bundle.href, externalOrigin)) {
202
- return cors.preflight(request, { methods: ['GET'], headers: ['if-none-match'] });
203
- }
204
- const deliveryUrl = snapshot.publication.delivery?.href;
205
- if (deliveryUrl !== undefined && sameRoute(request.url, deliveryUrl, externalOrigin)) {
206
- return cors.preflight(request, {
207
- methods: ['POST'],
208
- headers: ['authorization', 'content-type'],
209
- });
210
- }
211
- const requestedMethod = request.headers.get('access-control-request-method');
212
- const route = requestedMethod === null
213
- ? undefined
214
- : routes.match(snapshot.table, { url: request.url, method: requestedMethod });
215
- return cors.preflight(request, route === undefined ? undefined : nativeCorsRoute(route, requestedMethod));
216
- }
217
- function nativeCorsRoute(entry, requestedMethod) {
218
- const headers = new Set([
219
- 'accept',
220
- routes.HTTP_ROUTE_PUBLICATION,
221
- routes.HTTP_ROUTE_ISSUER,
222
- protocol.HTTP_IDEMPOTENCY_KEY,
223
- ]);
224
- if (entry.body !== undefined)
225
- headers.add('content-type');
226
- let allowAnyHeaders = false;
227
- for (const pick of Object.values(entry.headers ?? {})) {
228
- if (pick === '*')
229
- allowAnyHeaders = true;
230
- else
231
- headers.add(pick.toLowerCase());
232
- }
233
- const credential = entry.credential;
234
- const sources = credential === undefined
235
- ? [{ in: 'header' }]
236
- : Array.isArray(credential)
237
- ? credential
238
- : [credential];
239
- for (const source of sources) {
240
- if (source.in === 'header')
241
- headers.add((source.name ?? 'authorization').toLowerCase());
242
- }
243
- return Object.freeze({
244
- methods: Object.freeze([entry.method === '*' ? requestedMethod.toUpperCase() : entry.method]),
245
- headers: Object.freeze([...headers]),
246
- ...(allowAnyHeaders ? { allowAnyHeaders: true } : {}),
247
- });
248
- }
249
- export function currentPublicationSnapshot(source, limits, serving) {
250
- const snapshot = source.current();
251
- if (snapshot === undefined)
252
- return undefined;
253
- const prepared = readPreparedPublicationSnapshot(snapshot);
254
- if (prepared === undefined ||
255
- prepared.publicationBytes.byteLength > limits.maximumPublicationBytes ||
256
- prepared.bundle.byteLength > limits.maximumBundleBytes ||
257
- (serving?.issuer !== undefined && prepared.publication.identity.issuer !== serving.issuer) ||
258
- (serving !== undefined && conflictsWithServingRoutes(prepared, serving))) {
259
- return undefined;
260
- }
261
- return prepared;
262
- }
263
- function servingSnapshot(source, limits, request, reservations, externalIssuer) {
264
- return currentPublicationSnapshot(source, limits, {
265
- origin: externalIssuer === undefined ? new URL(request.url).origin : new URL(externalIssuer).origin,
266
- ...(externalIssuer === undefined ? {} : { issuer: externalIssuer }),
267
- reservations,
268
- });
269
- }
270
- function conflictsWithServingRoutes(snapshot, serving) {
271
- for (const reservation of serving.reservations) {
272
- const href = `${serving.origin}${reservation.path}`;
273
- for (const entry of snapshot.table.entries) {
274
- if (reservation.methods.some((method) => entry.method === '*' || entry.method === method) &&
275
- routes.templatesOverlap(entry.href, href)) {
276
- return true;
277
- }
30
+ return handleDelivery(request, snapshot, deliveryProvider, active, limits, corsFor);
278
31
  }
279
- }
280
- return false;
281
- }
282
- async function streamResponse(stream, operation, cors) {
283
- const iterator = stream[Symbol.asyncIterator]();
284
- let rejectSetup;
285
- const setupInterrupted = new Promise((_resolve, reject) => {
286
- rejectSetup = reject;
32
+ return (await nativeResponse(request, snapshot, native, responseCors)) ?? context.notFound();
287
33
  });
288
- let setupCleanup;
289
- const cancelSetup = (reason) => {
290
- rejectSetup(operation.signal.reason ?? new Error(reason));
291
- setupCleanup ??= Promise.allSettled([
292
- Promise.resolve().then(() => stream.cancel(reason)),
293
- Promise.resolve().then(() => iterator.return?.()),
294
- ]).then(() => operation.settle());
295
- return setupCleanup;
296
- };
297
- const unregisterSetupCancellation = operation.onCancel(cancelSetup);
298
- let first;
299
- try {
300
- first = await Promise.race([iterator.next(), setupInterrupted]);
301
- }
302
- catch (cause) {
303
- unregisterSetupCancellation();
304
- const cleanup = cancelSetup('native stream setup failed');
305
- if (!operation.signal.aborted)
306
- await cleanup;
307
- const payload = preStreamError(cause, operation);
308
- return nativeError(payload, errors.httpStatusOf(payload), cors);
309
- }
310
- unregisterSetupCancellation();
311
- if (operation.signal.aborted) {
312
- void cancelSetup('native stream setup cancelled');
313
- const payload = preStreamError(operation.signal.reason, operation);
314
- return response({ error: payload }, errors.httpStatusOf(payload), cors);
315
- }
316
- const encoder = new TextEncoder();
317
- let pending = first;
318
- let terminal = false;
319
- let controller;
320
- let stopping;
321
- const encoded = (frame) => encoder.encode(routes.encodeStreamFrame(frame));
322
- const stop = (reason, error) => {
323
- stopping ??= (async () => {
324
- if (terminal)
325
- return;
326
- terminal = true;
327
- if (controller !== undefined) {
328
- if (error !== undefined) {
329
- try {
330
- controller.enqueue(encoded({ kind: 'error', error }));
331
- }
332
- catch {
333
- // The transport consumer may already have disconnected.
334
- }
335
- }
336
- try {
337
- controller.close();
338
- }
339
- catch {
340
- // A cancelled response body is already terminal.
341
- }
342
- }
343
- try {
344
- await Promise.allSettled([
345
- Promise.resolve().then(() => stream.cancel(reason)),
346
- Promise.resolve().then(() => iterator.return?.()),
347
- ]);
348
- }
349
- finally {
350
- operation.settle();
351
- }
352
- })();
353
- return stopping;
354
- };
355
- const body = new ReadableStream({
356
- start(output) {
357
- controller = output;
358
- },
359
- async pull(output) {
360
- if (terminal)
361
- return;
362
- try {
363
- const next = pending ?? (await iterator.next());
364
- pending = undefined;
365
- if (terminal)
366
- return;
367
- if (next.done) {
368
- terminal = true;
369
- output.enqueue(encoded({ kind: 'end' }));
370
- output.close();
371
- operation.settle();
372
- }
373
- else {
374
- output.enqueue(encoded({ kind: 'item', value: next.value }));
375
- }
376
- }
377
- catch (cause) {
378
- await stop('native stream failed', operation.signal.aborted
379
- ? interruptedStreamError(operation.timedOut)
380
- : classifyError(cause));
381
- }
382
- },
383
- async cancel(reason) {
384
- const message = typeof reason === 'string' ? reason : 'native response cancelled';
385
- operation.cancel(message);
386
- await stop(message);
387
- },
388
- });
389
- operation.onCancel((reason) => stop(reason, interruptedStreamError(operation.timedOut)));
390
- return new Response(body, {
391
- headers: {
392
- ...cors,
393
- 'content-type': routes.STREAM_MEDIA_TYPE,
394
- 'cache-control': 'no-cache',
395
- },
396
- });
397
- }
398
- function preStreamError(cause, operation) {
399
- if (operation.timedOut) {
400
- return {
401
- code: errors.CODES.DEADLINE_EXCEEDED,
402
- message: 'Invocation deadline exceeded before the native stream began.',
403
- };
404
- }
405
- if (operation.signal.aborted) {
406
- return {
407
- code: errors.CODES.CANCELLED,
408
- message: 'Invocation was cancelled before the native stream began.',
409
- };
410
- }
411
- return classifyError(cause);
412
- }
413
- function sameRoute(left, right, externalOrigin) {
414
- const a = new URL(left);
415
- const b = new URL(right);
416
- return ((externalOrigin ?? a.origin) === b.origin && a.pathname === b.pathname && a.search === b.search);
417
- }
418
- function applicationBinaryHeaders(input) {
419
- const output = {};
420
- for (const [name, value] of Object.entries(input ?? {})) {
421
- if (name.toLowerCase() === routes.HTTP_ROUTE_STALE ||
422
- name.toLowerCase() === routes.HTTP_ROUTE_MISS) {
423
- throw new TypeError(`${name.toLowerCase()} is reserved for native routing control.`);
424
- }
425
- if (protocol.http.isApplicationBinaryHeader(name))
426
- output[name] = value;
427
- }
428
- return output;
429
- }
430
- function quoteEtag(digest) {
431
- return `"${digest}"`;
432
- }
433
- function response(body, status, cors) {
434
- return new Response(encodedBody(codecs.json.encode(body)), {
435
- status,
436
- headers: { ...cors, 'content-type': 'application/json' },
437
- });
438
- }
439
- function nativeError(error, status, cors) {
440
- const admitted = routes.decodeErrorResponse({ error });
441
- return response(admitted, status, cors);
442
- }
443
- function encodedBody(input) {
444
- return typeof input === 'string' ? input : toBody(input);
445
- }
446
- function toBody(input) {
447
- return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
448
34
  }
449
- //# sourceMappingURL=routes.js.map
@@ -0,0 +1,13 @@
1
+ import type { Served, ServingSource } from '../release/served.js';
2
+ import type { RouteLimits, RouteReservation } from './model.js';
3
+ /**
4
+ * Read the one snapshot a request pins, withheld while it exceeds a serving limit, names another
5
+ * serving issuer, or claims a route this Server reserves.
6
+ */
7
+ export declare function currentServed(source: ServingSource, limits: Pick<RouteLimits, 'maximumReleaseBytes' | 'maximumBundleBytes'>, serving?: {
8
+ readonly origin: string;
9
+ readonly issuer?: string;
10
+ readonly reservations: readonly RouteReservation[];
11
+ }): Served | undefined;
12
+ /** Admit the one request snapshot against its trusted origin and reserved routes. */
13
+ export declare function servingSnapshot(source: ServingSource, limits: Pick<RouteLimits, 'maximumReleaseBytes' | 'maximumBundleBytes'>, request: Request, reservations: readonly RouteReservation[], externalIssuer?: string, publicOrigin?: string): Served | undefined;
@@ -0,0 +1,39 @@
1
+ import * as routes from '@astrale-os/kernel-protocol/routes';
2
+ import { readServed, servedDocument } from '../release/served.js';
3
+ /**
4
+ * Read the one snapshot a request pins, withheld while it exceeds a serving limit, names another
5
+ * serving issuer, or claims a route this Server reserves.
6
+ */
7
+ export function currentServed(source, limits, serving) {
8
+ const prepared = readServed(source);
9
+ if (prepared === undefined ||
10
+ prepared.releaseBytes.byteLength > limits.maximumReleaseBytes ||
11
+ prepared.bundle.byteLength > limits.maximumBundleBytes ||
12
+ (serving?.issuer !== undefined &&
13
+ servedDocument(prepared).identity.issuer !== serving.issuer) ||
14
+ (serving !== undefined && conflictsWithServingRoutes(prepared, serving))) {
15
+ return undefined;
16
+ }
17
+ return prepared;
18
+ }
19
+ /** Admit the one request snapshot against its trusted origin and reserved routes. */
20
+ export function servingSnapshot(source, limits, request, reservations, externalIssuer, publicOrigin) {
21
+ return currentServed(source, limits, {
22
+ origin: publicOrigin ??
23
+ (externalIssuer === undefined ? new URL(request.url).origin : new URL(externalIssuer).origin),
24
+ ...(externalIssuer === undefined ? {} : { issuer: externalIssuer }),
25
+ reservations,
26
+ });
27
+ }
28
+ function conflictsWithServingRoutes(snapshot, serving) {
29
+ for (const reservation of serving.reservations) {
30
+ const href = `${serving.origin}${reservation.path}`;
31
+ for (const entry of snapshot.table.entries) {
32
+ if (reservation.methods.some((method) => entry.method === '*' || entry.method === method) &&
33
+ routes.templatesOverlap(entry.href, href)) {
34
+ return true;
35
+ }
36
+ }
37
+ }
38
+ return false;
39
+ }
@@ -0,0 +1,6 @@
1
+ import type { ActiveOperation } from '../lifecycle/index.js';
2
+ import type { CorsHeaders } from './model.js';
3
+ /** Render native item frames while retaining cancellation and producer cleanup. */
4
+ export declare function streamResponse(stream: AsyncIterable<unknown> & {
5
+ cancel(reason?: string): Promise<void>;
6
+ }, operation: ActiveOperation, cors: CorsHeaders): Promise<Response>;
@@ -0,0 +1,122 @@
1
+ import * as errors from '@astrale-os/kernel-protocol/errors';
2
+ import * as routes from '@astrale-os/kernel-protocol/routes';
3
+ import { classifyError, classifyRequestError } from '../errors/index.js';
4
+ import { interruptedStreamError } from '../transport/stream-error.js';
5
+ import { nativeError, response } from './response.js';
6
+ /** Render native item frames while retaining cancellation and producer cleanup. */
7
+ export async function streamResponse(stream, operation, cors) {
8
+ const iterator = stream[Symbol.asyncIterator]();
9
+ let rejectSetup;
10
+ const setupInterrupted = new Promise((_resolve, reject) => {
11
+ rejectSetup = reject;
12
+ });
13
+ let setupCleanup;
14
+ const cancelSetup = (reason) => {
15
+ rejectSetup(operation.signal.reason ?? new Error(reason));
16
+ setupCleanup ??= Promise.allSettled([
17
+ Promise.resolve().then(() => stream.cancel(reason)),
18
+ Promise.resolve().then(() => iterator.return?.()),
19
+ ]).then(() => operation.settle());
20
+ return setupCleanup;
21
+ };
22
+ const unregisterSetupCancellation = operation.onCancel(cancelSetup);
23
+ let first;
24
+ try {
25
+ first = await Promise.race([iterator.next(), setupInterrupted]);
26
+ }
27
+ catch (cause) {
28
+ unregisterSetupCancellation();
29
+ const cleanup = cancelSetup('native stream setup failed');
30
+ if (!operation.signal.aborted)
31
+ await cleanup;
32
+ const payload = classifyRequestError(cause, operation);
33
+ return nativeError(payload, errors.httpStatusOf(payload), cors);
34
+ }
35
+ unregisterSetupCancellation();
36
+ if (operation.signal.aborted) {
37
+ void cancelSetup('native stream setup cancelled');
38
+ const payload = classifyRequestError(operation.signal.reason, operation);
39
+ return response({ error: payload }, errors.httpStatusOf(payload), cors());
40
+ }
41
+ const encoder = new TextEncoder();
42
+ let pending = first;
43
+ let terminal = false;
44
+ let controller;
45
+ let stopping;
46
+ const encoded = (frame) => encoder.encode(routes.encodeStreamFrame(frame));
47
+ const stop = (reason, error) => {
48
+ stopping ??= (async () => {
49
+ if (terminal)
50
+ return;
51
+ terminal = true;
52
+ if (controller !== undefined) {
53
+ if (error !== undefined) {
54
+ try {
55
+ controller.enqueue(encoded({ kind: 'error', error }));
56
+ }
57
+ catch {
58
+ // The transport consumer may already have disconnected.
59
+ }
60
+ }
61
+ try {
62
+ controller.close();
63
+ }
64
+ catch {
65
+ // A cancelled response body is already terminal.
66
+ }
67
+ }
68
+ try {
69
+ await Promise.allSettled([
70
+ Promise.resolve().then(() => stream.cancel(reason)),
71
+ Promise.resolve().then(() => iterator.return?.()),
72
+ ]);
73
+ }
74
+ finally {
75
+ operation.settle();
76
+ }
77
+ })();
78
+ return stopping;
79
+ };
80
+ const body = new ReadableStream({
81
+ start(output) {
82
+ controller = output;
83
+ },
84
+ async pull(output) {
85
+ if (terminal)
86
+ return;
87
+ try {
88
+ const next = pending ?? (await iterator.next());
89
+ pending = undefined;
90
+ if (terminal)
91
+ return;
92
+ if (next.done) {
93
+ terminal = true;
94
+ output.enqueue(encoded({ kind: 'end' }));
95
+ output.close();
96
+ operation.settle();
97
+ }
98
+ else {
99
+ output.enqueue(encoded({ kind: 'item', value: next.value }));
100
+ }
101
+ }
102
+ catch (cause) {
103
+ await stop('native stream failed', operation.signal.aborted
104
+ ? interruptedStreamError(operation.timedOut)
105
+ : classifyError(cause));
106
+ }
107
+ },
108
+ async cancel(reason) {
109
+ const message = typeof reason === 'string' ? reason : 'native response cancelled';
110
+ operation.cancel(message);
111
+ await stop(message);
112
+ },
113
+ });
114
+ operation.onCancel((reason) => stop(reason, interruptedStreamError(operation.timedOut)));
115
+ return new Response(body, {
116
+ headers: {
117
+ ...cors(),
118
+ 'content-type': routes.STREAM_MEDIA_TYPE,
119
+ 'cache-control': 'no-cache',
120
+ },
121
+ });
122
+ }
package/dist/server.d.ts CHANGED
@@ -2,4 +2,3 @@ import type { Server, ServerConfig, ServerOptions, PreparedServer } from './type
2
2
  export declare function createServer(config: ServerConfig): Server;
3
3
  /** Validate all static transport configuration before a Host starts Runtime recovery/bootstrap. */
4
4
  export declare function prepareServer(options: ServerOptions): PreparedServer;
5
- //# sourceMappingURL=server.d.ts.map