@astrale-os/kernel-server 0.5.0-beta.7 → 0.5.0-beta.70

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 (215) 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 +8 -4
  22. package/dist/index.js +7 -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/legacy/index.d.ts +20 -0
  30. package/dist/invocation/legacy/index.js +20 -0
  31. package/dist/invocation/{publication-route.d.ts → legacy/publication-route.d.ts} +7 -5
  32. package/dist/invocation/{publication-route.js → legacy/publication-route.js} +7 -13
  33. package/dist/invocation/release-route.d.ts +26 -0
  34. package/dist/invocation/release-route.js +55 -0
  35. package/dist/invocation/types.d.ts +20 -7
  36. package/dist/invocation/types.js +0 -1
  37. package/dist/issuer/exchange.d.ts +0 -1
  38. package/dist/issuer/exchange.js +20 -10
  39. package/dist/issuer/index.d.ts +0 -1
  40. package/dist/issuer/index.js +0 -1
  41. package/dist/issuer/routes.d.ts +0 -1
  42. package/dist/issuer/routes.js +33 -5
  43. package/dist/issuer/types.d.ts +5 -1
  44. package/dist/issuer/types.js +0 -1
  45. package/dist/lifecycle/active.d.ts +4 -1
  46. package/dist/lifecycle/active.js +48 -11
  47. package/dist/lifecycle/index.d.ts +0 -1
  48. package/dist/lifecycle/index.js +0 -1
  49. package/dist/release/delivery/handler.d.ts +13 -0
  50. package/dist/{publication → release}/delivery/handler.js +42 -15
  51. package/dist/{publication → release}/delivery/index.d.ts +0 -1
  52. package/dist/{publication → release}/delivery/index.js +0 -1
  53. package/dist/release/delivery/types.d.ts +21 -0
  54. package/dist/release/delivery/types.js +1 -0
  55. package/dist/release/index.d.ts +3 -0
  56. package/dist/release/index.js +2 -0
  57. package/dist/release/legacy/delivery.d.ts +18 -0
  58. package/dist/release/legacy/delivery.js +2 -0
  59. package/dist/release/legacy/index.d.ts +39 -0
  60. package/dist/release/legacy/index.js +38 -0
  61. package/dist/release/legacy/snapshot.d.ts +26 -0
  62. package/dist/release/legacy/snapshot.js +55 -0
  63. package/dist/release/legacy/table.d.ts +5 -0
  64. package/dist/{publication → release/legacy}/table.js +5 -6
  65. package/dist/release/legacy/types.d.ts +31 -0
  66. package/dist/release/legacy/types.js +1 -0
  67. package/dist/release/served.d.ts +30 -0
  68. package/dist/release/served.js +14 -0
  69. package/dist/release/snapshot.d.ts +26 -0
  70. package/dist/release/snapshot.js +54 -0
  71. package/dist/release/table.d.ts +4 -0
  72. package/dist/release/table.js +11 -0
  73. package/dist/release/types.d.ts +33 -0
  74. package/dist/release/types.js +1 -0
  75. package/dist/routes/index.d.ts +1 -2
  76. package/dist/routes/index.js +1 -2
  77. package/dist/routes/routes.d.ts +9 -7
  78. package/dist/routes/routes.js +256 -123
  79. package/dist/server.d.ts +0 -1
  80. package/dist/server.js +110 -48
  81. package/dist/transport/body.d.ts +0 -1
  82. package/dist/transport/body.js +0 -1
  83. package/dist/transport/codecs.d.ts +2 -3
  84. package/dist/transport/codecs.js +4 -4
  85. package/dist/transport/credential.d.ts +0 -1
  86. package/dist/transport/credential.js +0 -1
  87. package/dist/transport/http/binary-request.d.ts +11 -0
  88. package/dist/transport/http/binary-request.js +102 -0
  89. package/dist/transport/http/handler.d.ts +8 -3
  90. package/dist/transport/http/handler.js +95 -45
  91. package/dist/transport/http/index.d.ts +0 -1
  92. package/dist/transport/http/index.js +0 -1
  93. package/dist/transport/http/render.d.ts +9 -4
  94. package/dist/transport/http/render.js +150 -5
  95. package/dist/transport/index.d.ts +0 -1
  96. package/dist/transport/index.js +0 -1
  97. package/dist/transport/router.d.ts +0 -1
  98. package/dist/transport/router.js +0 -1
  99. package/dist/transport/stream-error.d.ts +0 -1
  100. package/dist/transport/stream-error.js +0 -1
  101. package/dist/transport/types.d.ts +1 -2
  102. package/dist/transport/types.js +0 -1
  103. package/dist/transport/websocket/index.d.ts +0 -1
  104. package/dist/transport/websocket/index.js +0 -1
  105. package/dist/transport/websocket/session.d.ts +3 -3
  106. package/dist/transport/websocket/session.js +30 -30
  107. package/dist/transport/websocket/upgrade.d.ts +2 -3
  108. package/dist/transport/websocket/upgrade.js +7 -7
  109. package/dist/types.d.ts +32 -6
  110. package/dist/types.js +0 -1
  111. package/package.json +12 -15
  112. package/dist/capture.d.ts.map +0 -1
  113. package/dist/capture.js.map +0 -1
  114. package/dist/cors/cors.d.ts.map +0 -1
  115. package/dist/cors/cors.js.map +0 -1
  116. package/dist/cors/index.d.ts.map +0 -1
  117. package/dist/cors/index.js.map +0 -1
  118. package/dist/cors/types.d.ts.map +0 -1
  119. package/dist/cors/types.js.map +0 -1
  120. package/dist/errors/availability.d.ts.map +0 -1
  121. package/dist/errors/availability.js.map +0 -1
  122. package/dist/errors/classify.d.ts.map +0 -1
  123. package/dist/errors/classify.js.map +0 -1
  124. package/dist/errors/index.d.ts.map +0 -1
  125. package/dist/errors/index.js.map +0 -1
  126. package/dist/errors/request-body.d.ts.map +0 -1
  127. package/dist/errors/request-body.js.map +0 -1
  128. package/dist/errors/response-body.d.ts.map +0 -1
  129. package/dist/errors/response-body.js.map +0 -1
  130. package/dist/index.d.ts.map +0 -1
  131. package/dist/index.js.map +0 -1
  132. package/dist/invocation/context.d.ts.map +0 -1
  133. package/dist/invocation/context.js.map +0 -1
  134. package/dist/invocation/identity.d.ts.map +0 -1
  135. package/dist/invocation/identity.js.map +0 -1
  136. package/dist/invocation/index.d.ts.map +0 -1
  137. package/dist/invocation/index.js.map +0 -1
  138. package/dist/invocation/publication-route.d.ts.map +0 -1
  139. package/dist/invocation/publication-route.js.map +0 -1
  140. package/dist/invocation/types.d.ts.map +0 -1
  141. package/dist/invocation/types.js.map +0 -1
  142. package/dist/issuer/exchange.d.ts.map +0 -1
  143. package/dist/issuer/exchange.js.map +0 -1
  144. package/dist/issuer/index.d.ts.map +0 -1
  145. package/dist/issuer/index.js.map +0 -1
  146. package/dist/issuer/routes.d.ts.map +0 -1
  147. package/dist/issuer/routes.js.map +0 -1
  148. package/dist/issuer/types.d.ts.map +0 -1
  149. package/dist/issuer/types.js.map +0 -1
  150. package/dist/lifecycle/active.d.ts.map +0 -1
  151. package/dist/lifecycle/active.js.map +0 -1
  152. package/dist/lifecycle/index.d.ts.map +0 -1
  153. package/dist/lifecycle/index.js.map +0 -1
  154. package/dist/publication/delivery/handler.d.ts +0 -10
  155. package/dist/publication/delivery/handler.d.ts.map +0 -1
  156. package/dist/publication/delivery/handler.js.map +0 -1
  157. package/dist/publication/delivery/index.d.ts.map +0 -1
  158. package/dist/publication/delivery/index.js.map +0 -1
  159. package/dist/publication/delivery/types.d.ts +0 -12
  160. package/dist/publication/delivery/types.d.ts.map +0 -1
  161. package/dist/publication/delivery/types.js +0 -2
  162. package/dist/publication/delivery/types.js.map +0 -1
  163. package/dist/publication/index.d.ts +0 -4
  164. package/dist/publication/index.d.ts.map +0 -1
  165. package/dist/publication/index.js +0 -3
  166. package/dist/publication/index.js.map +0 -1
  167. package/dist/publication/snapshot.d.ts +0 -16
  168. package/dist/publication/snapshot.d.ts.map +0 -1
  169. package/dist/publication/snapshot.js +0 -39
  170. package/dist/publication/snapshot.js.map +0 -1
  171. package/dist/publication/table.d.ts +0 -4
  172. package/dist/publication/table.d.ts.map +0 -1
  173. package/dist/publication/table.js.map +0 -1
  174. package/dist/publication/types.d.ts +0 -23
  175. package/dist/publication/types.d.ts.map +0 -1
  176. package/dist/publication/types.js +0 -2
  177. package/dist/publication/types.js.map +0 -1
  178. package/dist/publication/validator.d.ts +0 -4
  179. package/dist/publication/validator.d.ts.map +0 -1
  180. package/dist/publication/validator.js +0 -17
  181. package/dist/publication/validator.js.map +0 -1
  182. package/dist/routes/index.d.ts.map +0 -1
  183. package/dist/routes/index.js.map +0 -1
  184. package/dist/routes/routes.d.ts.map +0 -1
  185. package/dist/routes/routes.js.map +0 -1
  186. package/dist/server.d.ts.map +0 -1
  187. package/dist/server.js.map +0 -1
  188. package/dist/transport/body.d.ts.map +0 -1
  189. package/dist/transport/body.js.map +0 -1
  190. package/dist/transport/codecs.d.ts.map +0 -1
  191. package/dist/transport/codecs.js.map +0 -1
  192. package/dist/transport/credential.d.ts.map +0 -1
  193. package/dist/transport/credential.js.map +0 -1
  194. package/dist/transport/http/handler.d.ts.map +0 -1
  195. package/dist/transport/http/handler.js.map +0 -1
  196. package/dist/transport/http/index.d.ts.map +0 -1
  197. package/dist/transport/http/index.js.map +0 -1
  198. package/dist/transport/http/render.d.ts.map +0 -1
  199. package/dist/transport/http/render.js.map +0 -1
  200. package/dist/transport/index.d.ts.map +0 -1
  201. package/dist/transport/index.js.map +0 -1
  202. package/dist/transport/router.d.ts.map +0 -1
  203. package/dist/transport/router.js.map +0 -1
  204. package/dist/transport/stream-error.d.ts.map +0 -1
  205. package/dist/transport/stream-error.js.map +0 -1
  206. package/dist/transport/types.d.ts.map +0 -1
  207. package/dist/transport/types.js.map +0 -1
  208. package/dist/transport/websocket/index.d.ts.map +0 -1
  209. package/dist/transport/websocket/index.js.map +0 -1
  210. package/dist/transport/websocket/session.d.ts.map +0 -1
  211. package/dist/transport/websocket/session.js.map +0 -1
  212. package/dist/transport/websocket/upgrade.d.ts.map +0 -1
  213. package/dist/transport/websocket/upgrade.js.map +0 -1
  214. package/dist/types.d.ts.map +0 -1
  215. package/dist/types.js.map +0 -1
@@ -1,57 +1,46 @@
1
1
  import * as codecs from '@astrale-os/kernel-protocol/codecs';
2
2
  import * as errors from '@astrale-os/kernel-protocol/errors';
3
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';
4
+ import { bundleUrl, MEDIA_TYPE as RELEASE_MEDIA_TYPE, PATH as RELEASE_PATH, url as releaseUrl, } from '@astrale-os/kernel-protocol/release';
5
+ import { mediaType as publicationMediaType, PATH as PUBLICATION_PATH, reference as publicationReference, url as publicationUrl, } from '@astrale-os/kernel-protocol/release/legacy';
6
6
  import * as routes from '@astrale-os/kernel-protocol/routes';
7
- import { classifyError, RequestBodyInvalidError, RequestBodyTooLargeError, } from '../errors/index.js';
7
+ import { admissionRefusal, classifyError, classifyRequestError, RequestBodyInvalidError, RequestBodyTooLargeError, retryAfterHeaders, } from '../errors/index.js';
8
8
  import { invocationContext } from '../invocation/index.js';
9
- import { handleDelivery } from '../publication/delivery/index.js';
10
- import { readPreparedPublicationSnapshot, } from '../publication/index.js';
9
+ import { handleDelivery } from '../release/delivery/index.js';
10
+ import { pinned, readServed, servedDocument } from '../release/served.js';
11
11
  import { readBoundedBody } from '../transport/body.js';
12
12
  import {} from '../transport/router.js';
13
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
- },
14
+ export function mountRoutes(app, source, deliveryProvider, handler, active, nextInvocation, limits, cors, hostFor, reservations, externalIssuer, publicOrigin) {
15
+ const externalOrigin = publicOrigin ?? (externalIssuer === undefined ? undefined : new URL(externalIssuer).origin);
16
+ const served = (request) => servingSnapshot(source, limits, request, reservations, externalIssuer, externalOrigin);
17
+ if (source.kind === 'release') {
18
+ mountDocument(app, documentPath(RELEASE_PATH, releaseUrl, externalIssuer), 'Release', {
19
+ cors,
20
+ active,
21
+ served,
22
+ read: (snapshot) => snapshot.kind === 'release'
23
+ ? {
24
+ bytes: snapshot.releaseBytes,
25
+ mediaType: RELEASE_MEDIA_TYPE,
26
+ etag: snapshot.release.digest,
27
+ }
28
+ : undefined,
51
29
  });
30
+ }
31
+ // At a release-backed destination `domain.json` serves the frozen v3 projection.
32
+ mountDocument(app, documentPath(PUBLICATION_PATH, publicationUrl, externalIssuer), 'Publication', {
33
+ cors,
34
+ active,
35
+ served,
36
+ read: (snapshot) => ({
37
+ bytes: snapshot.publicationBytes,
38
+ mediaType: publicationMediaType(snapshot.publication),
39
+ etag: snapshot.publication.etag,
40
+ }),
52
41
  });
53
42
  app.all('*', async (context) => {
54
- const snapshot = servingSnapshot(source, limits, context.req.raw, reservations, externalIssuer);
43
+ const snapshot = served(context.req.raw);
55
44
  if (snapshot === undefined)
56
45
  return context.notFound();
57
46
  const request = context.req.raw;
@@ -59,48 +48,80 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
59
48
  return publicationPreflight(cors, request, snapshot, externalOrigin);
60
49
  }
61
50
  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);
51
+ const corsFor = (exposed) => cors.response(request, exposed);
52
+ const document = servedDocument(snapshot);
53
+ const descriptor = document.schema.bundle;
54
+ const addressed = bundleUrl(document.identity.issuer, descriptor.ref);
55
+ const immutable = descriptor.href === addressed;
56
+ if (sameRoute(request.url, descriptor.href, externalOrigin)) {
57
+ // A release names its own document; a v3-input destination keeps its recorded bytes.
58
+ const releaseBacked = snapshot.kind === 'release';
59
+ if (!active.isAccepting) {
60
+ return drained(releaseBacked
61
+ ? 'Release artifact is unavailable while draining.'
62
+ : 'Publication artifact is unavailable while draining.', corsFor);
63
+ }
65
64
  if (request.method !== 'GET')
66
65
  return response({ error: 'Bundle retrieval requires GET.' }, 405, responseCors);
67
66
  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);
67
+ if (body.byteLength !== descriptor.ref.size) {
68
+ return response({
69
+ error: releaseBacked
70
+ ? 'Bundle differs from its release.'
71
+ : 'Bundle differs from its Publication.',
72
+ }, 500, responseCors);
70
73
  }
71
- return new Response(toBody(body), {
72
- status: 200,
74
+ const etag = quoteEtag(descriptor.ref.digest);
75
+ const unchanged = request.headers.get('if-none-match') === etag;
76
+ return new Response(unchanged ? null : toBody(body), {
77
+ status: unchanged ? 304 : 200,
73
78
  headers: {
74
79
  ...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',
80
+ 'content-type': descriptor.ref.mediaType,
81
+ etag,
82
+ 'cache-control': immutable
83
+ ? 'public, max-age=31536000, immutable, no-transform'
84
+ : 'public, max-age=0, must-revalidate, no-transform',
78
85
  },
79
86
  });
80
87
  }
81
- const deliveryUrl = snapshot.publication.delivery?.href;
88
+ if (request.method === 'GET' && immutable) {
89
+ const pathname = new URL(addressed).pathname;
90
+ const directory = pathname.slice(0, pathname.lastIndexOf('/') + 1);
91
+ const requested = new URL(request.url);
92
+ if (requested.pathname.startsWith(directory) &&
93
+ /^[0-9a-f]{64}\.json$/u.test(requested.pathname.slice(directory.length))) {
94
+ return new Response(null, {
95
+ status: 404,
96
+ headers: { ...responseCors, 'cache-control': 'no-store' },
97
+ });
98
+ }
99
+ }
100
+ const deliveryUrl = document.delivery?.href;
82
101
  if (deliveryUrl !== undefined && sameRoute(request.url, deliveryUrl, externalOrigin)) {
83
- return handleDelivery(request, snapshot, deliveryProvider, active, limits, cors.response(request));
102
+ return handleDelivery(request, snapshot, deliveryProvider, active, limits, corsFor);
84
103
  }
85
104
  let routing;
86
105
  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
- });
106
+ if (snapshot.kind === 'release') {
107
+ const decoded = routes.decodeRouting(request, snapshot.references);
108
+ if (decoded?.kind === 'stale')
109
+ return stale(request, decoded.current, cors);
110
+ routing = decoded;
111
+ }
112
+ else {
113
+ const ifMatch = request.headers.get(routes.HTTP_ROUTE_DIGEST);
114
+ if (ifMatch !== null &&
115
+ /^"sha256:[0-9a-f]{64}"$/u.test(ifMatch) &&
116
+ ifMatch !== quoteEtag(snapshot.publication.etag)) {
117
+ return stale(request, snapshot.publication.etag, cors);
118
+ }
119
+ routing = routes.legacy.decodeRouting(request, publicationReference(snapshot.publication));
98
120
  }
99
- routing = routes.decodeRouting(request, publicationReference(snapshot.publication));
100
121
  }
101
122
  catch (cause) {
102
123
  if (cause instanceof routes.RouteError) {
103
- return nativeError({ code: errors.CODES.INVALID_REQUEST, message: 'Native route routing is invalid.' }, 400, responseCors);
124
+ return nativeError({ code: errors.CODES.INVALID_REQUEST, message: 'Native route routing is invalid.' }, 400, corsFor);
104
125
  }
105
126
  throw cause;
106
127
  }
@@ -110,14 +131,14 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
110
131
  const assigned = nextInvocation();
111
132
  const operation = active.begin(limits.invocationTimeoutMs, request.signal);
112
133
  if (operation === undefined)
113
- return nativeError({
114
- code: errors.CODES.BACKEND_UNAVAILABLE,
115
- message: 'Server is draining.',
116
- }, 503, responseCors);
134
+ return nativeError(admissionRefusal(active.isAccepting), 503, corsFor);
117
135
  let handedOff = false;
118
136
  try {
119
137
  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);
138
+ const native = { url: request.url, method: request.method, headers: request.headers, body };
139
+ const invocation = snapshot.kind === 'release'
140
+ ? routes.decode(route, native, snapshot.domain, snapshot.references)
141
+ : routes.legacy.decode(route, native, snapshot.domain, snapshot.table.publication);
121
142
  const idempotencyHeader = request.headers.get(protocol.HTTP_IDEMPOTENCY_KEY);
122
143
  const idempotencyKey = idempotencyHeader === null
123
144
  ? undefined
@@ -126,7 +147,7 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
126
147
  call: invocation.call,
127
148
  ...(invocation.credential === undefined ? {} : { credential: invocation.credential }),
128
149
  ...(routing === undefined ? {} : { via: routing }),
129
- publication: snapshot.snapshot,
150
+ ...pinned(snapshot),
130
151
  context: invocationContext(assigned, operation, active, hostFor(request), body, idempotencyKey),
131
152
  }));
132
153
  if (result.kind === 'value') {
@@ -138,8 +159,13 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
138
159
  });
139
160
  }
140
161
  if (result.kind === 'binary') {
162
+ if (isStreamingBinary(result.value)) {
163
+ handedOff = true;
164
+ return streamingBinaryResponse(result.value, operation, cors, request);
165
+ }
141
166
  const applicationHeaders = applicationBinaryHeaders(result.value.headers);
142
167
  return new Response(toBody(result.value.body), {
168
+ status: result.value.status ?? 200,
143
169
  headers: {
144
170
  ...cors.response(request, [
145
171
  protocol.http.BINARY_HEADERS_FIELD,
@@ -153,7 +179,7 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
153
179
  }
154
180
  if (result.kind === 'stream') {
155
181
  handedOff = true;
156
- return await streamResponse(result.stream, operation, responseCors);
182
+ return await streamResponse(result.stream, operation, corsFor);
157
183
  }
158
184
  if (result.kind === 'miss') {
159
185
  return new Response(null, {
@@ -167,29 +193,20 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
167
193
  return nativeError({
168
194
  code: errors.CODES.UNSUPPORTED,
169
195
  message: 'Native routes do not forward Runtime redirects.',
170
- }, 502, responseCors);
196
+ }, 502, corsFor);
171
197
  }
172
198
  catch (cause) {
173
199
  if (cause instanceof RequestBodyTooLargeError) {
174
- return nativeError({ code: errors.CODES.LIMIT_EXCEEDED, message: 'Request body is too large.' }, 413, responseCors);
200
+ return nativeError({ code: errors.CODES.LIMIT_EXCEEDED, message: 'Request body is too large.' }, 413, corsFor);
175
201
  }
176
202
  if (cause instanceof RequestBodyInvalidError) {
177
- return nativeError({ code: errors.CODES.INVALID_REQUEST, message: 'Request body is invalid.' }, 400, responseCors);
203
+ return nativeError({ code: errors.CODES.INVALID_REQUEST, message: 'Request body is invalid.' }, 400, corsFor);
178
204
  }
179
205
  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);
206
+ return nativeError({ code: errors.CODES.INVALID_REQUEST, message: 'Native route request is invalid.' }, 400, corsFor);
187
207
  }
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);
208
+ const payload = classifyRequestError(cause, operation);
209
+ return nativeError(payload, errors.httpStatusOf(payload), corsFor);
193
210
  }
194
211
  finally {
195
212
  if (!handedOff)
@@ -197,11 +214,79 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
197
214
  }
198
215
  });
199
216
  }
217
+ function streamingBinaryResponse(binary, operation, cors, request) {
218
+ const iterator = binary.body[Symbol.asyncIterator]();
219
+ const applicationHeaders = applicationBinaryHeaders(binary.headers);
220
+ let terminal = false;
221
+ let controller;
222
+ let stopping;
223
+ const stop = (reason = 'Binary response was interrupted.') => {
224
+ if (terminal)
225
+ return stopping ?? Promise.resolve();
226
+ terminal = true;
227
+ controller?.error(new Error(reason));
228
+ stopping = Promise.resolve().then(async () => {
229
+ try {
230
+ await iterator.return?.();
231
+ }
232
+ finally {
233
+ operation.settle();
234
+ }
235
+ });
236
+ return stopping;
237
+ };
238
+ const body = new ReadableStream({
239
+ start(output) {
240
+ controller = output;
241
+ },
242
+ async pull(controller) {
243
+ if (terminal)
244
+ return;
245
+ try {
246
+ const next = await iterator.next();
247
+ if (terminal)
248
+ return;
249
+ if (next.done) {
250
+ terminal = true;
251
+ controller.close();
252
+ operation.settle();
253
+ return;
254
+ }
255
+ controller.enqueue(next.value);
256
+ }
257
+ catch (cause) {
258
+ if (terminal)
259
+ return;
260
+ terminal = true;
261
+ controller.error(cause);
262
+ operation.settle();
263
+ }
264
+ },
265
+ async cancel(reason) {
266
+ const message = typeof reason === 'string' ? reason : 'native binary response cancelled';
267
+ operation.cancel(message);
268
+ await stop();
269
+ },
270
+ });
271
+ operation.onCancel(stop);
272
+ return new Response(body, {
273
+ status: binary.status ?? 200,
274
+ headers: {
275
+ ...cors.response(request, Object.keys(applicationHeaders)),
276
+ ...applicationHeaders,
277
+ 'content-type': binary.mediaType,
278
+ },
279
+ });
280
+ }
281
+ function isStreamingBinary(input) {
282
+ return !(input.body instanceof Uint8Array);
283
+ }
200
284
  function publicationPreflight(cors, request, snapshot, externalOrigin) {
201
- if (sameRoute(request.url, snapshot.publication.schema.bundle.href, externalOrigin)) {
285
+ const document = servedDocument(snapshot);
286
+ if (sameRoute(request.url, document.schema.bundle.href, externalOrigin)) {
202
287
  return cors.preflight(request, { methods: ['GET'], headers: ['if-none-match'] });
203
288
  }
204
- const deliveryUrl = snapshot.publication.delivery?.href;
289
+ const deliveryUrl = document.delivery?.href;
205
290
  if (deliveryUrl !== undefined && sameRoute(request.url, deliveryUrl, externalOrigin)) {
206
291
  return cors.preflight(request, {
207
292
  methods: ['POST'],
@@ -217,7 +302,7 @@ function publicationPreflight(cors, request, snapshot, externalOrigin) {
217
302
  function nativeCorsRoute(entry, requestedMethod) {
218
303
  const headers = new Set([
219
304
  'accept',
220
- routes.HTTP_ROUTE_PUBLICATION,
305
+ routes.HTTP_ROUTE_DIGEST,
221
306
  routes.HTTP_ROUTE_ISSUER,
222
307
  protocol.HTTP_IDEMPOTENCY_KEY,
223
308
  ]);
@@ -246,23 +331,28 @@ function nativeCorsRoute(entry, requestedMethod) {
246
331
  ...(allowAnyHeaders ? { allowAnyHeaders: true } : {}),
247
332
  });
248
333
  }
249
- export function currentPublicationSnapshot(source, limits, serving) {
250
- const snapshot = source.current();
251
- if (snapshot === undefined)
252
- return undefined;
253
- const prepared = readPreparedPublicationSnapshot(snapshot);
334
+ /**
335
+ * Read the one snapshot a request pins, withheld while it exceeds a serving limit, names another
336
+ * serving issuer, or claims a route this Server reserves.
337
+ */
338
+ export function currentServed(source, limits, serving) {
339
+ const prepared = readServed(source);
254
340
  if (prepared === undefined ||
341
+ (prepared.kind === 'release' &&
342
+ prepared.releaseBytes.byteLength > limits.maximumPublicationBytes) ||
255
343
  prepared.publicationBytes.byteLength > limits.maximumPublicationBytes ||
256
344
  prepared.bundle.byteLength > limits.maximumBundleBytes ||
257
- (serving?.issuer !== undefined && prepared.publication.identity.issuer !== serving.issuer) ||
345
+ (serving?.issuer !== undefined &&
346
+ servedDocument(prepared).identity.issuer !== serving.issuer) ||
258
347
  (serving !== undefined && conflictsWithServingRoutes(prepared, serving))) {
259
348
  return undefined;
260
349
  }
261
350
  return prepared;
262
351
  }
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,
352
+ function servingSnapshot(source, limits, request, reservations, externalIssuer, publicOrigin) {
353
+ return currentServed(source, limits, {
354
+ origin: publicOrigin ??
355
+ (externalIssuer === undefined ? new URL(request.url).origin : new URL(externalIssuer).origin),
266
356
  ...(externalIssuer === undefined ? {} : { issuer: externalIssuer }),
267
357
  reservations,
268
358
  });
@@ -279,6 +369,58 @@ function conflictsWithServingRoutes(snapshot, serving) {
279
369
  }
280
370
  return false;
281
371
  }
372
+ function documentPath(path, url, externalIssuer) {
373
+ return externalIssuer === undefined ? path : new URL(url(externalIssuer)).pathname;
374
+ }
375
+ function mountDocument(app, path, name, serving) {
376
+ const { cors, active } = serving;
377
+ if (cors.enabled) {
378
+ app.options(path, (context) => cors.preflight(context.req.raw, {
379
+ methods: ['GET'],
380
+ headers: ['if-none-match'],
381
+ }));
382
+ }
383
+ app.get(path, (context) => {
384
+ const responseCors = cors.response(context.req.raw, ['etag']);
385
+ if (!active.isAccepting) {
386
+ return drained(`${name} is unavailable while draining.`, (exposed) => cors.response(context.req.raw, exposed));
387
+ }
388
+ const snapshot = serving.served(context.req.raw);
389
+ const document = snapshot === undefined ? undefined : serving.read(snapshot);
390
+ if (document === undefined)
391
+ return response({ error: `${name} is not ready.` }, 503, responseCors);
392
+ const etag = quoteEtag(document.etag);
393
+ if (context.req.raw.headers.get('if-none-match') === etag) {
394
+ return new Response(null, {
395
+ status: 304,
396
+ headers: {
397
+ ...responseCors,
398
+ etag,
399
+ 'cache-control': 'public, max-age=0, must-revalidate, no-transform',
400
+ },
401
+ });
402
+ }
403
+ return new Response(toBody(document.bytes), {
404
+ status: 200,
405
+ headers: {
406
+ ...responseCors,
407
+ 'content-type': document.mediaType,
408
+ etag,
409
+ 'cache-control': 'public, max-age=0, must-revalidate, no-transform',
410
+ },
411
+ });
412
+ });
413
+ }
414
+ /** Reserved native stale control naming the digest the destination serves (`StaleControl.current`). */
415
+ function stale(request, current, cors) {
416
+ return new Response(null, {
417
+ status: routes.HTTP_ROUTE_STALE_STATUS,
418
+ headers: {
419
+ ...cors.response(request, [routes.HTTP_ROUTE_STALE]),
420
+ ...routes.staleHeaders(current),
421
+ },
422
+ });
423
+ }
282
424
  async function streamResponse(stream, operation, cors) {
283
425
  const iterator = stream[Symbol.asyncIterator]();
284
426
  let rejectSetup;
@@ -304,14 +446,14 @@ async function streamResponse(stream, operation, cors) {
304
446
  const cleanup = cancelSetup('native stream setup failed');
305
447
  if (!operation.signal.aborted)
306
448
  await cleanup;
307
- const payload = preStreamError(cause, operation);
449
+ const payload = classifyRequestError(cause, operation);
308
450
  return nativeError(payload, errors.httpStatusOf(payload), cors);
309
451
  }
310
452
  unregisterSetupCancellation();
311
453
  if (operation.signal.aborted) {
312
454
  void cancelSetup('native stream setup cancelled');
313
- const payload = preStreamError(operation.signal.reason, operation);
314
- return response({ error: payload }, errors.httpStatusOf(payload), cors);
455
+ const payload = classifyRequestError(operation.signal.reason, operation);
456
+ return response({ error: payload }, errors.httpStatusOf(payload), cors());
315
457
  }
316
458
  const encoder = new TextEncoder();
317
459
  let pending = first;
@@ -389,27 +531,12 @@ async function streamResponse(stream, operation, cors) {
389
531
  operation.onCancel((reason) => stop(reason, interruptedStreamError(operation.timedOut)));
390
532
  return new Response(body, {
391
533
  headers: {
392
- ...cors,
534
+ ...cors(),
393
535
  'content-type': routes.STREAM_MEDIA_TYPE,
394
536
  'cache-control': 'no-cache',
395
537
  },
396
538
  });
397
539
  }
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
540
  function sameRoute(left, right, externalOrigin) {
414
541
  const a = new URL(left);
415
542
  const b = new URL(right);
@@ -436,9 +563,16 @@ function response(body, status, cors) {
436
563
  headers: { ...cors, 'content-type': 'application/json' },
437
564
  });
438
565
  }
566
+ /** A served document or artifact refused while draining presents the lifecycle retry delay. */
567
+ function drained(message, cors) {
568
+ const retry = retryAfterHeaders(admissionRefusal(false));
569
+ return response({ error: message }, 503, { ...cors(Object.keys(retry)), ...retry });
570
+ }
571
+ /** Every native error, including a capacity refusal raised by dispatch, presents its retry delay. */
439
572
  function nativeError(error, status, cors) {
440
573
  const admitted = routes.decodeErrorResponse({ error });
441
- return response(admitted, status, cors);
574
+ const retry = retryAfterHeaders(error);
575
+ return response(admitted, status, { ...cors(Object.keys(retry)), ...retry });
442
576
  }
443
577
  function encodedBody(input) {
444
578
  return typeof input === 'string' ? input : toBody(input);
@@ -446,4 +580,3 @@ function encodedBody(input) {
446
580
  function toBody(input) {
447
581
  return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
448
582
  }
449
- //# sourceMappingURL=routes.js.map
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