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

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 +314 -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,12 +1,14 @@
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 { classifyError, RequestBodyInvalidError, RequestBodyTooLargeError, } from '../../errors/index.js';
5
- import { invocationContext, publicationRouteDecision, } from '../../invocation/index.js';
4
+ import { admissionRefusal, classifyError, classifyRequestError, RequestBodyInvalidError, RequestBodyTooLargeError, } from '../../errors/index.js';
5
+ import { admitVia, invocationContext, } from '../../invocation/index.js';
6
+ import { pinned } from '../../release/served.js';
6
7
  import { readBoundedBody } from '../body.js';
7
8
  import { loadCodecs } from '../codecs.js';
8
9
  import { resolveCredential } from '../credential.js';
9
- import { renderHttpError, renderHttpResult } from './render.js';
10
+ import { binaryRequestBody } from './binary-request.js';
11
+ import { isStreamingBinary, renderDeploymentRetired, renderHttpError, renderHttpResult, } from './render.js';
10
12
  export function createHttpHandler(config) {
11
13
  return async (request, host) => {
12
14
  const cors = (exposedHeaders) => config.cors.response(request, exposedHeaders);
@@ -21,6 +23,8 @@ export function createHttpHandler(config) {
21
23
  headers: [
22
24
  'authorization',
23
25
  'content-type',
26
+ protocol.http.REQUEST_ENVELOPE_FIELD,
27
+ protocol.http.BINARY_LENGTH_FIELD,
24
28
  protocol.http.TRACEPARENT_FIELD,
25
29
  protocol.http.TRACESTATE_FIELD,
26
30
  ],
@@ -37,24 +41,34 @@ export function createHttpHandler(config) {
37
41
  ...options,
38
42
  maximumResponseBytes: config.maximumResponseBytes,
39
43
  });
40
- const active = config.active.begin(config.invocationTimeoutMs, request.signal);
44
+ const binaryRequest = request.headers.get('content-type')?.trim().toLowerCase() ===
45
+ protocol.http.BINARY_REQUEST_CONTENT_TYPE;
46
+ const active = config.active.begin(binaryRequest ? config.binaryRequestTimeoutMs : config.invocationTimeoutMs, request.signal);
41
47
  if (active === undefined) {
42
- return renderError(null, {
43
- code: errors.CODES.BACKEND_UNAVAILABLE,
44
- message: 'server is unavailable.',
45
- }, codecs.json, { status: 503, negotiated: false });
48
+ if (binaryRequest && request.body !== null && !request.body.locked) {
49
+ void request.body.cancel('binary request rejected').catch(() => undefined);
50
+ }
51
+ return renderError(null, admissionRefusal(config.active.isAccepting), codecs.json, {
52
+ status: 503,
53
+ negotiated: false,
54
+ });
46
55
  }
47
56
  let handedOff = false;
57
+ let binary;
48
58
  try {
49
- const supported = await loadCodecs();
59
+ const supported = loadCodecs();
50
60
  const contentType = request.headers.get('content-type');
51
- const incoming = contentType === null || contentType.trim() === ''
61
+ // No default representation: a body without a declared Astrale type is never decoded, so a
62
+ // browser cannot send an admissible invocation from another origin without a CORS preflight.
63
+ const incoming = binaryRequest
52
64
  ? codecs.json
53
65
  : protocol.http.codecForContentType(contentType, supported);
54
66
  if (incoming === undefined) {
55
67
  return renderError(null, {
56
68
  code: errors.CODES.UNSUPPORTED,
57
- message: 'Unsupported request Content-Type.',
69
+ message: contentType === null || contentType.trim() === ''
70
+ ? 'Missing request Content-Type.'
71
+ : 'Unsupported request Content-Type.',
58
72
  }, codecs.json, { status: 415, negotiated: false });
59
73
  }
60
74
  const outgoing = protocol.http.negotiateResponseCodec(request.headers.get('accept'), incoming, supported);
@@ -66,7 +80,15 @@ export function createHttpHandler(config) {
66
80
  }
67
81
  let body;
68
82
  try {
69
- body = await readBoundedBody(request, config.maximumRequestBytes, active.signal);
83
+ if (binaryRequest)
84
+ binary = binaryRequestBody(request, config.maximumBinaryRequestBytes, active.signal, config.binaryRequestIdleTimeoutMs === undefined
85
+ ? undefined
86
+ : {
87
+ timeoutMs: config.binaryRequestIdleTimeoutMs,
88
+ cancel: () => active.cancel('binary request idle timeout exceeded'),
89
+ });
90
+ else
91
+ body = await readBoundedBody(request, config.maximumRequestBytes, active.signal);
70
92
  }
71
93
  catch (input) {
72
94
  const payload = input instanceof RequestBodyTooLargeError
@@ -87,37 +109,54 @@ export function createHttpHandler(config) {
87
109
  status: input instanceof RequestBodyTooLargeError ? 413 : errors.httpStatusOf(payload),
88
110
  });
89
111
  }
90
- let decoded;
91
- try {
92
- decoded = incoming.decode(body);
93
- }
94
- catch (input) {
95
- if (!(input instanceof codecs.DecodeError))
96
- throw input;
97
- return renderError(null, { code: errors.CODES.PARSE_ERROR, message: 'Request body is invalid.' }, outgoing);
98
- }
99
112
  let envelope;
100
113
  try {
101
- envelope = protocol.decodeRequest(decoded);
114
+ if (binaryRequest) {
115
+ envelope = protocol.http.decodeBinaryRequestEnvelope(request.headers.get(protocol.http.REQUEST_ENVELOPE_FIELD));
116
+ if (envelope.idempotencyKey !== undefined) {
117
+ return renderError(envelope.requestId, {
118
+ code: errors.CODES.INVALID_REQUEST,
119
+ message: 'Binary requests forbid idempotencyKey.',
120
+ }, outgoing);
121
+ }
122
+ }
123
+ else {
124
+ let decoded;
125
+ try {
126
+ decoded = incoming.decode(body);
127
+ }
128
+ catch (input) {
129
+ if (!(input instanceof codecs.DecodeError))
130
+ throw input;
131
+ return renderError(null, { code: errors.CODES.PARSE_ERROR, message: 'Request body is invalid.' }, outgoing);
132
+ }
133
+ envelope = protocol.decodeRequest(decoded);
134
+ }
102
135
  }
103
136
  catch (input) {
104
137
  return renderError(null, classifyError(input), outgoing);
105
138
  }
106
- const publication = config.currentPublication?.(request);
107
- if (config.currentPublication !== undefined && publication === undefined) {
139
+ if (!binaryRequest &&
140
+ config.binaryDownloadTarget !== undefined &&
141
+ protocol.targetPath(envelope.call.target) === config.binaryDownloadTarget) {
142
+ active.extend(config.binaryDownloadTimeoutMs);
143
+ }
144
+ if (active.signal.aborted) {
145
+ return renderError(envelope.requestId, classifyRequestError(active.signal.reason, active), outgoing);
146
+ }
147
+ const serving = config.serving?.(request);
148
+ const served = serving?.current();
149
+ if (serving !== undefined && served === undefined) {
108
150
  return renderError(envelope.requestId, {
109
151
  code: errors.CODES.BACKEND_UNAVAILABLE,
110
- message: 'Publication is unavailable.',
152
+ message: `${serving.kind === 'release' ? 'Release' : 'Publication'} is unavailable.`,
111
153
  }, outgoing, { status: 503 });
112
154
  }
113
155
  if (envelope.routing?.kind === 'via') {
114
- if (publication === undefined) {
115
- return renderError(envelope.requestId, {
116
- code: errors.CODES.INVALID_REQUEST,
117
- message: 'Publication routing requires a Publication-backed destination.',
118
- }, outgoing);
156
+ const decision = admitVia(served, envelope.routing);
157
+ if (decision.kind === 'retired') {
158
+ return renderDeploymentRetired(serving.deployment(), decision.release, cors);
119
159
  }
120
- const decision = publicationRouteDecision(publication.publication, envelope.routing.publication);
121
160
  if (decision.kind === 'rejected') {
122
161
  return renderError(envelope.requestId, decision.error, outgoing);
123
162
  }
@@ -135,9 +174,20 @@ export function createHttpHandler(config) {
135
174
  ...(envelope.delegate === undefined ? {} : { delegate: envelope.delegate }),
136
175
  ...(envelope.routing?.kind === 'via' ? { via: envelope.routing } : {}),
137
176
  ...(envelope.schema === undefined ? {} : { schema: envelope.schema }),
138
- ...(publication === undefined ? {} : { publication }),
139
- context: invocationContext(assigned, active, config.active, host, undefined, envelope.idempotencyKey),
177
+ ...(served === undefined ? {} : pinned(served)),
178
+ context: invocationContext(assigned, active, config.active, host, undefined, envelope.idempotencyKey, binary),
140
179
  }));
180
+ if (binary !== undefined && result.kind !== 'redirect' && result.kind !== 'miss') {
181
+ if (!binary.completed()) {
182
+ if (result.kind === 'stream') {
183
+ await result.stream.cancel('Binary request body was not fully consumed.');
184
+ }
185
+ else if (result.kind === 'binary' && isStreamingBinary(result.value)) {
186
+ await result.value.body[Symbol.asyncIterator]().return?.();
187
+ }
188
+ throw new Error('Binary Function returned before consuming its request body.');
189
+ }
190
+ }
141
191
  const response = renderHttpResult(result, {
142
192
  requestId: envelope.requestId,
143
193
  codec: outgoing,
@@ -146,20 +196,12 @@ export function createHttpHandler(config) {
146
196
  active,
147
197
  maximumResponseBytes: config.maximumResponseBytes,
148
198
  });
149
- handedOff = result.kind === 'stream';
199
+ handedOff =
200
+ result.kind === 'stream' || (result.kind === 'binary' && isStreamingBinary(result.value));
150
201
  return response;
151
202
  }
152
203
  catch (input) {
153
- if (active.timedOut) {
154
- return renderError(envelope.requestId, {
155
- code: errors.CODES.DEADLINE_EXCEEDED,
156
- message: 'Invocation deadline exceeded.',
157
- }, outgoing, { status: 504 });
158
- }
159
- if (active.signal.aborted) {
160
- return renderError(envelope.requestId, { code: errors.CODES.CANCELLED, message: 'Invocation was cancelled.' }, outgoing, { status: 408 });
161
- }
162
- return renderError(envelope.requestId, classifyError(input), outgoing);
204
+ return renderError(envelope.requestId, classifyRequestError(input, active), outgoing);
163
205
  }
164
206
  }
165
207
  catch (input) {
@@ -168,6 +210,15 @@ export function createHttpHandler(config) {
168
210
  });
169
211
  }
170
212
  finally {
213
+ if (binary !== undefined) {
214
+ binary.cancel('binary invocation completed');
215
+ }
216
+ else if (binaryRequest &&
217
+ binary === undefined &&
218
+ request.body !== null &&
219
+ !request.body.locked) {
220
+ void request.body.cancel('binary request rejected').catch(() => undefined);
221
+ }
171
222
  if (!handedOff)
172
223
  active.settle();
173
224
  }
@@ -178,4 +229,3 @@ function noStore(response) {
178
229
  headers.set('cache-control', 'no-store');
179
230
  return new Response(response.body, { status: response.status, headers });
180
231
  }
181
- //# sourceMappingURL=handler.js.map
@@ -1,3 +1,2 @@
1
1
  export { createHttpHandler, type HttpHandlerConfig } from './handler.js';
2
2
  export { encodeEnvelope, renderBinary, renderHttpError, renderHttpResult, renderHttpStale, renderRedirect, type CorsHeaders, type ErrorRenderOptions, type RenderContext, } from './render.js';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,2 @@
1
1
  export { createHttpHandler } from './handler.js';
2
2
  export { encodeEnvelope, renderBinary, renderHttpError, renderHttpResult, renderHttpStale, renderRedirect, } from './render.js';
3
- //# sourceMappingURL=index.js.map
@@ -1,4 +1,7 @@
1
+ import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
2
+ import type { BufferedBinaryResult, StreamingBinaryResult } from '@astrale-os/kernel-core/callable';
1
3
  import type { Codec } from '@astrale-os/kernel-protocol/codecs';
4
+ import type { ReleaseDigest } from '@astrale-os/kernel-protocol/release';
2
5
  import * as errors from '@astrale-os/kernel-protocol/errors';
3
6
  import * as protocol from '@astrale-os/kernel-protocol/invocation';
4
7
  import type { Handler } from '../../invocation/index.js';
@@ -19,14 +22,16 @@ export interface ErrorRenderOptions {
19
22
  }
20
23
  type HandlerResult = Awaited<ReturnType<Handler['dispatch']>>;
21
24
  export declare function renderHttpResult(result: HandlerResult, context: RenderContext): Response;
22
- export declare function renderBinary(requestId: protocol.RequestId, invocation: protocol.InvocationId, binary: Extract<HandlerResult, {
23
- readonly kind: 'binary';
24
- }>['value']): protocol.BinaryResponse;
25
+ export declare function renderBinary(requestId: protocol.RequestId, invocation: protocol.InvocationId, binary: BufferedBinaryResult): protocol.BinaryResponse;
25
26
  export declare function renderRedirect(requestId: protocol.RequestId, invocation: protocol.InvocationId, redirect: Extract<HandlerResult, {
26
27
  readonly kind: 'redirect';
27
28
  }>['redirect']): protocol.RedirectResponse;
28
29
  export declare function renderHttpError(requestId: protocol.RequestId, invocation: protocol.InvocationId, error: errors.ErrorPayload, codec: Codec, cors: CorsHeaders, options?: ErrorRenderOptions): Response;
29
30
  export declare function renderHttpStale(requestId: protocol.RequestId, invocation: protocol.InvocationId, current: protocol.Stale['current'], codec: Codec, cors: CorsHeaders, maximumResponseBytes?: number): Response;
31
+ /** Retirement controls always use JSON, regardless of the negotiated request codec. */
32
+ export declare function renderDeploymentRetired(deployment: IssuerId, release: ReleaseDigest | undefined, cors: CorsHeaders): Response;
30
33
  export declare function encodeEnvelope(codec: Codec, input: unknown): string | Uint8Array;
34
+ export declare function isStreamingBinary(input: Extract<HandlerResult, {
35
+ readonly kind: 'binary';
36
+ }>['value']): input is StreamingBinaryResult;
31
37
  export {};
32
- //# sourceMappingURL=render.d.ts.map
@@ -1,7 +1,9 @@
1
+ import { json } from '@astrale-os/kernel-protocol/codecs';
1
2
  import * as errors from '@astrale-os/kernel-protocol/errors';
2
3
  import * as protocol from '@astrale-os/kernel-protocol/invocation';
4
+ import { deploymentRetiredBody, deploymentRetiredHeaders, HTTP_DEPLOYMENT_RETIRED, HTTP_DEPLOYMENT_RETIRED_STATUS, } from '@astrale-os/kernel-protocol/routes/deployment-retired';
3
5
  import { HTTP_ROUTE_MISS, HTTP_ROUTE_STALE } from '@astrale-os/kernel-protocol/routes/routing';
4
- import { classifyError } from '../../errors/index.js';
6
+ import { classifyError, retryAfterHeaders } from '../../errors/index.js';
5
7
  import { ResponseBodyTooLargeError } from '../../errors/index.js';
6
8
  import { interruptedStreamError } from '../stream-error.js';
7
9
  export function renderHttpResult(result, context) {
@@ -19,6 +21,8 @@ export function renderHttpResult(result, context) {
19
21
  case 'miss':
20
22
  return settled(context.active, envelopeResponse(context.codec, protocol.miss(context.requestId, context.invocation), context.cors(), 200, true, context.maximumResponseBytes));
21
23
  case 'binary': {
24
+ if (isStreamingBinary(result.value))
25
+ return streamingBinaryHttpResponse(result.value, context);
22
26
  const binary = renderBinary(context.requestId, context.invocation, result.value);
23
27
  return settled(context.active, binaryHttpResponse(binary, context.cors, context.maximumResponseBytes));
24
28
  }
@@ -41,6 +45,7 @@ export function renderBinary(requestId, invocation, binary) {
41
45
  invocation,
42
46
  body: binary.body,
43
47
  mediaType: binary.mediaType,
48
+ status: binary.status ?? 200,
44
49
  ...(binary.headers === undefined ? {} : { headers: binary.headers }),
45
50
  });
46
51
  }
@@ -54,12 +59,20 @@ export function renderRedirect(requestId, invocation, redirect) {
54
59
  }
55
60
  export function renderHttpError(requestId, invocation, error, codec, cors, options = {}) {
56
61
  const status = options.status ?? errors.httpStatusOf(error);
57
- const extra = status === 401 ? { 'www-authenticate': 'Bearer realm="astrale-invocation"' } : {};
58
- return errorEnvelopeResponse(codec, { error, requestId, invocation }, { ...cors(), ...extra }, status, options.negotiated ?? true, options.maximumResponseBytes);
62
+ const retry = retryAfterHeaders(error);
63
+ const extra = status === 401 ? { 'www-authenticate': 'Bearer realm="astrale-invocation"' } : retry;
64
+ return errorEnvelopeResponse(codec, { error, requestId, invocation }, { ...cors(Object.keys(retry)), ...extra }, status, options.negotiated ?? true, options.maximumResponseBytes);
59
65
  }
60
66
  export function renderHttpStale(requestId, invocation, current, codec, cors, maximumResponseBytes) {
61
67
  return envelopeResponse(codec, protocol.stale(requestId, invocation, current), cors(), 200, true, maximumResponseBytes);
62
68
  }
69
+ /** Retirement controls always use JSON, regardless of the negotiated request codec. */
70
+ export function renderDeploymentRetired(deployment, release, cors) {
71
+ return new Response(toBody(json.encode(deploymentRetiredBody(deployment, release))), {
72
+ status: HTTP_DEPLOYMENT_RETIRED_STATUS,
73
+ headers: { ...cors([HTTP_DEPLOYMENT_RETIRED]), ...deploymentRetiredHeaders() },
74
+ });
75
+ }
63
76
  export function encodeEnvelope(codec, input) {
64
77
  return codec.encode(input);
65
78
  }
@@ -75,11 +88,141 @@ function envelopeResponse(codec, input, cors, status = 200, negotiated = true, m
75
88
  }
76
89
  function binaryHttpResponse(binary, cors, maximumResponseBytes) {
77
90
  requireResponseBound(binary.body, maximumResponseBytes);
91
+ const headers = binaryHttpHeaders(binary, cors, false);
92
+ return new Response(toBody(binary.body), { status: binary.status, headers });
93
+ }
94
+ function streamingBinaryHttpResponse(binary, context) {
95
+ if (binary.length !== undefined && (!Number.isSafeInteger(binary.length) || binary.length < 0)) {
96
+ throw new TypeError('Streaming binary length must be a non-negative safe integer.');
97
+ }
98
+ const delivery = binary.delivery;
99
+ if (delivery !== undefined) {
100
+ if (!Number.isSafeInteger(delivery.maximumDurationMs) ||
101
+ delivery.maximumDurationMs <= 0 ||
102
+ delivery.maximumDurationMs > 2_147_483_647 ||
103
+ !Number.isSafeInteger(delivery.idleTimeoutMs) ||
104
+ delivery.idleTimeoutMs <= 0 ||
105
+ delivery.idleTimeoutMs > 2_147_483_647) {
106
+ throw new TypeError('Streaming binary delivery budget is invalid.');
107
+ }
108
+ context.active.extend(delivery.maximumDurationMs);
109
+ }
110
+ const iterator = binary.body[Symbol.asyncIterator]();
111
+ const headers = binaryHttpHeaders(Object.freeze({
112
+ requestId: context.requestId,
113
+ invocation: context.invocation,
114
+ mediaType: binary.mediaType,
115
+ headers: binary.headers,
116
+ }), context.cors, true);
117
+ if (binary.length !== undefined) {
118
+ headers.set('content-length', String(binary.length));
119
+ // Content-Length belongs to HTTP and an intermediary may rewrite it; the Client counts the
120
+ // delivered bytes against this protocol field instead.
121
+ headers.set(protocol.http.BINARY_LENGTH_FIELD, String(binary.length));
122
+ }
123
+ if (delivery !== undefined)
124
+ headers.set('cache-control', 'private, no-store');
125
+ let terminal = false;
126
+ let delivered = 0;
127
+ let controller;
128
+ let stopping;
129
+ let idleTimer;
130
+ const clearIdle = () => {
131
+ if (idleTimer !== undefined)
132
+ clearTimeout(idleTimer);
133
+ };
134
+ const resetIdle = () => {
135
+ if (delivery === undefined || terminal)
136
+ return;
137
+ clearIdle();
138
+ idleTimer = setTimeout(() => context.active.cancel('binary response idle timeout exceeded'), delivery.idleTimeoutMs);
139
+ };
140
+ const stop = (reason = 'Binary response was interrupted.') => {
141
+ if (terminal)
142
+ return stopping ?? Promise.resolve();
143
+ terminal = true;
144
+ clearIdle();
145
+ controller?.error(new Error(reason));
146
+ stopping = Promise.resolve().then(async () => {
147
+ try {
148
+ await iterator.return?.();
149
+ }
150
+ finally {
151
+ context.active.settle();
152
+ }
153
+ });
154
+ return stopping;
155
+ };
156
+ const fail = (cause) => {
157
+ if (terminal)
158
+ return;
159
+ terminal = true;
160
+ clearIdle();
161
+ controller?.error(cause);
162
+ stopping = Promise.resolve()
163
+ .then(() => iterator.return?.())
164
+ .catch(() => undefined)
165
+ .then(() => context.active.settle());
166
+ };
167
+ const body = new ReadableStream({
168
+ start(output) {
169
+ controller = output;
170
+ resetIdle();
171
+ },
172
+ async pull(controller) {
173
+ if (terminal)
174
+ return;
175
+ try {
176
+ const next = await iterator.next();
177
+ if (terminal)
178
+ return;
179
+ if (next.done) {
180
+ if (binary.length !== undefined && delivered !== binary.length) {
181
+ fail(new Error('OUTCOME_UNKNOWN: streaming binary ended before its declared length.'));
182
+ return;
183
+ }
184
+ terminal = true;
185
+ clearIdle();
186
+ controller.close();
187
+ context.active.settle();
188
+ return;
189
+ }
190
+ if (!(next.value instanceof Uint8Array)) {
191
+ throw new TypeError('Streaming binary yielded a non-byte chunk.');
192
+ }
193
+ delivered += next.value.byteLength;
194
+ if (binary.length !== undefined && delivered > binary.length) {
195
+ fail(new Error('OUTCOME_UNKNOWN: streaming binary exceeded its declared length.'));
196
+ return;
197
+ }
198
+ controller.enqueue(next.value);
199
+ resetIdle();
200
+ }
201
+ catch (cause) {
202
+ fail(cause);
203
+ }
204
+ },
205
+ async cancel(reason) {
206
+ const message = typeof reason === 'string' ? reason : 'binary response consumer cancelled';
207
+ context.active.cancel(message);
208
+ await stop();
209
+ },
210
+ });
211
+ context.active.onCancel(stop);
212
+ return new Response(body, { status: binary.status ?? 200, headers });
213
+ }
214
+ export function isStreamingBinary(input) {
215
+ return !(input.body instanceof Uint8Array);
216
+ }
217
+ function binaryHttpHeaders(binary, cors, streaming) {
78
218
  const applicationHeaders = Object.keys(binary.headers ?? {}).filter((name) => protocol.http.isApplicationBinaryHeader(name));
79
219
  const headers = new Headers(cors([
80
220
  protocol.http.REQUEST_ID_FIELD,
81
221
  protocol.http.INVOCATION_FIELD,
82
222
  protocol.http.BINARY_HEADERS_FIELD,
223
+ ...(streaming
224
+ ? [protocol.http.BINARY_DELIVERY_FIELD, protocol.http.BINARY_LENGTH_FIELD]
225
+ : []),
83
226
  ...applicationHeaders,
84
227
  ]));
85
228
  for (const [name, value] of Object.entries(binary.headers ?? {})) {
@@ -90,9 +233,12 @@ function binaryHttpResponse(binary, cors, maximumResponseBytes) {
90
233
  headers.set(protocol.http.BINARY_HEADERS_FIELD, protocol.http.encodeBinaryHeaderNames(binary.headers));
91
234
  headers.set(protocol.http.REQUEST_ID_FIELD, String(binary.requestId));
92
235
  headers.set(protocol.http.INVOCATION_FIELD, protocol.http.encodeInvocationId(binary.invocation));
236
+ if (streaming) {
237
+ headers.set(protocol.http.BINARY_DELIVERY_FIELD, protocol.http.STREAMING_BINARY_DELIVERY);
238
+ }
93
239
  headers.set('cache-control', 'no-store');
94
240
  appendVary(headers, 'accept');
95
- return new Response(toBody(binary.body), { status: 200, headers });
241
+ return headers;
96
242
  }
97
243
  function streamHttpResponse(stream, context) {
98
244
  const iterator = stream[Symbol.asyncIterator]();
@@ -286,4 +432,3 @@ function toBody(input) {
286
432
  const copy = new Uint8Array(input);
287
433
  return copy.buffer;
288
434
  }
289
- //# sourceMappingURL=render.js.map
@@ -7,4 +7,3 @@ export { interruptedStreamError } from './stream-error.js';
7
7
  export { createServerRouter, type ServerRouter } from './router.js';
8
8
  export { createWebSocketSession, handleWebSocketUpgrade, type WebSocketHandlerConfig, type WebSocketSessionConfig, } from './websocket/index.js';
9
9
  export type { Application, WebSocketAdapter, WebSocketHandlers, WebSocketLike, WebSocketUpgradeOptions, } from './types.js';
10
- //# sourceMappingURL=index.d.ts.map
@@ -6,4 +6,3 @@ export { encodeEnvelope, renderBinary, renderHttpError, renderHttpResult, render
6
6
  export { interruptedStreamError } from './stream-error.js';
7
7
  export { createServerRouter } from './router.js';
8
8
  export { createWebSocketSession, handleWebSocketUpgrade, } from './websocket/index.js';
9
- //# sourceMappingURL=index.js.map
@@ -2,4 +2,3 @@ import { type Hono as HonoType } from 'hono';
2
2
  /** Private Web-standard router capability shared by Server route-owning children. */
3
3
  export type ServerRouter = HonoType;
4
4
  export declare function createServerRouter(): ServerRouter;
5
- //# sourceMappingURL=router.d.ts.map
@@ -2,4 +2,3 @@ import { Hono } from 'hono';
2
2
  export function createServerRouter() {
3
3
  return new Hono();
4
4
  }
5
- //# sourceMappingURL=router.js.map
@@ -1,4 +1,3 @@
1
1
  import type { ErrorPayload } from '@astrale-os/kernel-protocol/errors';
2
2
  /** A transport cancellation after stream.start necessarily follows the Function effect boundary. */
3
3
  export declare function interruptedStreamError(timedOut: boolean): ErrorPayload;
4
- //# sourceMappingURL=stream-error.d.ts.map
@@ -11,4 +11,3 @@ export function interruptedStreamError(timedOut) {
11
11
  message: 'Function stream was cancelled after execution began; outcome is unknown.',
12
12
  };
13
13
  }
14
- //# sourceMappingURL=stream-error.js.map
@@ -1,4 +1,4 @@
1
- import type { invocation as protocol } from '@astrale-os/kernel-protocol';
1
+ import type * as protocol from '@astrale-os/kernel-protocol/invocation';
2
2
  import type { ApplicationHost } from '../invocation/index.js';
3
3
  /** Portable Fetch application; framework and listener APIs are adapter-owned. */
4
4
  export interface Application {
@@ -26,4 +26,3 @@ export interface WebSocketUpgradeOptions {
26
26
  export interface WebSocketAdapter {
27
27
  upgrade(request: Request, options: WebSocketUpgradeOptions): Response | Promise<Response>;
28
28
  }
29
- //# sourceMappingURL=types.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=types.js.map
@@ -1,3 +1,2 @@
1
1
  export { createWebSocketSession, type WebSocketSessionConfig } from './session.js';
2
2
  export { handleWebSocketUpgrade, type WebSocketHandlerConfig } from './upgrade.js';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,2 @@
1
1
  export { createWebSocketSession } from './session.js';
2
2
  export { handleWebSocketUpgrade } from './upgrade.js';
3
- //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  import type { Codec } from '@astrale-os/kernel-protocol/codecs';
2
2
  import type { NextInvocationId } from '../../invocation/index.js';
3
3
  import type { ActiveOperations } from '../../lifecycle/index.js';
4
- import type { PublicationSnapshot } from '../../publication/index.js';
4
+ import type { RequestServing } from '../../release/served.js';
5
5
  import type { WebSocketHandlers } from '../types.js';
6
6
  import { type ApplicationHost, type Handler } from '../../invocation/index.js';
7
7
  export interface WebSocketSessionConfig {
@@ -13,9 +13,9 @@ export interface WebSocketSessionConfig {
13
13
  readonly maximumRequestBytes: number;
14
14
  readonly maximumResponseBytes: number;
15
15
  readonly invocationTimeoutMs: number;
16
- readonly currentPublication?: () => PublicationSnapshot | undefined;
16
+ /** Present exactly when the Server serves a release or a Publication; bound to the upgrade. */
17
+ readonly serving?: RequestServing;
17
18
  readonly host?: ApplicationHost;
18
19
  }
19
20
  /** Build one socket-pinned transport session. */
20
21
  export declare function createWebSocketSession(config: WebSocketSessionConfig): WebSocketHandlers;
21
- //# sourceMappingURL=session.d.ts.map