@astrale-os/kernel-server 0.5.0-beta.6 → 0.5.0-beta.61

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 (185) 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 +2 -1
  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/availability.d.ts +0 -1
  10. package/dist/errors/availability.js +0 -1
  11. package/dist/errors/classify.d.ts +0 -1
  12. package/dist/errors/classify.js +0 -1
  13. package/dist/errors/index.d.ts +0 -1
  14. package/dist/errors/index.js +0 -1
  15. package/dist/errors/request-body.d.ts +0 -1
  16. package/dist/errors/request-body.js +0 -1
  17. package/dist/errors/response-body.d.ts +0 -1
  18. package/dist/errors/response-body.js +0 -1
  19. package/dist/index.d.ts +0 -1
  20. package/dist/index.js +0 -1
  21. package/dist/invocation/context.d.ts +1 -2
  22. package/dist/invocation/context.js +0 -1
  23. package/dist/invocation/identity.d.ts +1 -2
  24. package/dist/invocation/identity.js +1 -2
  25. package/dist/invocation/index.d.ts +0 -1
  26. package/dist/invocation/index.js +0 -1
  27. package/dist/invocation/publication-route.d.ts +1 -4
  28. package/dist/invocation/publication-route.js +2 -12
  29. package/dist/invocation/types.d.ts +3 -4
  30. package/dist/invocation/types.js +0 -1
  31. package/dist/issuer/exchange.d.ts +0 -1
  32. package/dist/issuer/exchange.js +9 -1
  33. package/dist/issuer/index.d.ts +0 -1
  34. package/dist/issuer/index.js +0 -1
  35. package/dist/issuer/routes.d.ts +0 -1
  36. package/dist/issuer/routes.js +0 -1
  37. package/dist/issuer/types.d.ts +0 -1
  38. package/dist/issuer/types.js +0 -1
  39. package/dist/lifecycle/active.d.ts +2 -1
  40. package/dist/lifecycle/active.js +5 -3
  41. package/dist/lifecycle/index.d.ts +0 -1
  42. package/dist/lifecycle/index.js +0 -1
  43. package/dist/publication/delivery/handler.d.ts +0 -1
  44. package/dist/publication/delivery/handler.js +2 -2
  45. package/dist/publication/delivery/index.d.ts +0 -1
  46. package/dist/publication/delivery/index.js +0 -1
  47. package/dist/publication/delivery/types.d.ts +1 -2
  48. package/dist/publication/delivery/types.js +0 -1
  49. package/dist/publication/index.d.ts +0 -1
  50. package/dist/publication/index.js +0 -1
  51. package/dist/publication/snapshot.d.ts +4 -4
  52. package/dist/publication/snapshot.js +15 -8
  53. package/dist/publication/table.d.ts +4 -4
  54. package/dist/publication/table.js +3 -4
  55. package/dist/publication/types.d.ts +4 -5
  56. package/dist/publication/types.js +0 -1
  57. package/dist/routes/index.d.ts +0 -1
  58. package/dist/routes/index.js +0 -1
  59. package/dist/routes/routes.d.ts +1 -2
  60. package/dist/routes/routes.js +106 -14
  61. package/dist/server.d.ts +0 -1
  62. package/dist/server.js +18 -4
  63. package/dist/transport/body.d.ts +0 -1
  64. package/dist/transport/body.js +0 -1
  65. package/dist/transport/codecs.d.ts +0 -1
  66. package/dist/transport/codecs.js +0 -1
  67. package/dist/transport/credential.d.ts +0 -1
  68. package/dist/transport/credential.js +0 -1
  69. package/dist/transport/http/handler.d.ts +0 -1
  70. package/dist/transport/http/handler.js +3 -3
  71. package/dist/transport/http/index.d.ts +0 -1
  72. package/dist/transport/http/index.js +0 -1
  73. package/dist/transport/http/render.d.ts +5 -4
  74. package/dist/transport/http/render.js +77 -2
  75. package/dist/transport/index.d.ts +0 -1
  76. package/dist/transport/index.js +0 -1
  77. package/dist/transport/router.d.ts +0 -1
  78. package/dist/transport/router.js +0 -1
  79. package/dist/transport/stream-error.d.ts +0 -1
  80. package/dist/transport/stream-error.js +0 -1
  81. package/dist/transport/types.d.ts +1 -2
  82. package/dist/transport/types.js +0 -1
  83. package/dist/transport/websocket/index.d.ts +0 -1
  84. package/dist/transport/websocket/index.js +0 -1
  85. package/dist/transport/websocket/session.d.ts +0 -1
  86. package/dist/transport/websocket/session.js +12 -2
  87. package/dist/transport/websocket/upgrade.d.ts +0 -1
  88. package/dist/transport/websocket/upgrade.js +0 -1
  89. package/dist/types.d.ts +3 -2
  90. package/dist/types.js +0 -1
  91. package/package.json +10 -12
  92. package/dist/capture.d.ts.map +0 -1
  93. package/dist/capture.js.map +0 -1
  94. package/dist/cors/cors.d.ts.map +0 -1
  95. package/dist/cors/cors.js.map +0 -1
  96. package/dist/cors/index.d.ts.map +0 -1
  97. package/dist/cors/index.js.map +0 -1
  98. package/dist/cors/types.d.ts.map +0 -1
  99. package/dist/cors/types.js.map +0 -1
  100. package/dist/errors/availability.d.ts.map +0 -1
  101. package/dist/errors/availability.js.map +0 -1
  102. package/dist/errors/classify.d.ts.map +0 -1
  103. package/dist/errors/classify.js.map +0 -1
  104. package/dist/errors/index.d.ts.map +0 -1
  105. package/dist/errors/index.js.map +0 -1
  106. package/dist/errors/request-body.d.ts.map +0 -1
  107. package/dist/errors/request-body.js.map +0 -1
  108. package/dist/errors/response-body.d.ts.map +0 -1
  109. package/dist/errors/response-body.js.map +0 -1
  110. package/dist/index.d.ts.map +0 -1
  111. package/dist/index.js.map +0 -1
  112. package/dist/invocation/context.d.ts.map +0 -1
  113. package/dist/invocation/context.js.map +0 -1
  114. package/dist/invocation/identity.d.ts.map +0 -1
  115. package/dist/invocation/identity.js.map +0 -1
  116. package/dist/invocation/index.d.ts.map +0 -1
  117. package/dist/invocation/index.js.map +0 -1
  118. package/dist/invocation/publication-route.d.ts.map +0 -1
  119. package/dist/invocation/publication-route.js.map +0 -1
  120. package/dist/invocation/types.d.ts.map +0 -1
  121. package/dist/invocation/types.js.map +0 -1
  122. package/dist/issuer/exchange.d.ts.map +0 -1
  123. package/dist/issuer/exchange.js.map +0 -1
  124. package/dist/issuer/index.d.ts.map +0 -1
  125. package/dist/issuer/index.js.map +0 -1
  126. package/dist/issuer/routes.d.ts.map +0 -1
  127. package/dist/issuer/routes.js.map +0 -1
  128. package/dist/issuer/types.d.ts.map +0 -1
  129. package/dist/issuer/types.js.map +0 -1
  130. package/dist/lifecycle/active.d.ts.map +0 -1
  131. package/dist/lifecycle/active.js.map +0 -1
  132. package/dist/lifecycle/index.d.ts.map +0 -1
  133. package/dist/lifecycle/index.js.map +0 -1
  134. package/dist/publication/delivery/handler.d.ts.map +0 -1
  135. package/dist/publication/delivery/handler.js.map +0 -1
  136. package/dist/publication/delivery/index.d.ts.map +0 -1
  137. package/dist/publication/delivery/index.js.map +0 -1
  138. package/dist/publication/delivery/types.d.ts.map +0 -1
  139. package/dist/publication/delivery/types.js.map +0 -1
  140. package/dist/publication/index.d.ts.map +0 -1
  141. package/dist/publication/index.js.map +0 -1
  142. package/dist/publication/snapshot.d.ts.map +0 -1
  143. package/dist/publication/snapshot.js.map +0 -1
  144. package/dist/publication/table.d.ts.map +0 -1
  145. package/dist/publication/table.js.map +0 -1
  146. package/dist/publication/types.d.ts.map +0 -1
  147. package/dist/publication/types.js.map +0 -1
  148. package/dist/publication/validator.d.ts +0 -4
  149. package/dist/publication/validator.d.ts.map +0 -1
  150. package/dist/publication/validator.js +0 -17
  151. package/dist/publication/validator.js.map +0 -1
  152. package/dist/routes/index.d.ts.map +0 -1
  153. package/dist/routes/index.js.map +0 -1
  154. package/dist/routes/routes.d.ts.map +0 -1
  155. package/dist/routes/routes.js.map +0 -1
  156. package/dist/server.d.ts.map +0 -1
  157. package/dist/server.js.map +0 -1
  158. package/dist/transport/body.d.ts.map +0 -1
  159. package/dist/transport/body.js.map +0 -1
  160. package/dist/transport/codecs.d.ts.map +0 -1
  161. package/dist/transport/codecs.js.map +0 -1
  162. package/dist/transport/credential.d.ts.map +0 -1
  163. package/dist/transport/credential.js.map +0 -1
  164. package/dist/transport/http/handler.d.ts.map +0 -1
  165. package/dist/transport/http/handler.js.map +0 -1
  166. package/dist/transport/http/index.d.ts.map +0 -1
  167. package/dist/transport/http/index.js.map +0 -1
  168. package/dist/transport/http/render.d.ts.map +0 -1
  169. package/dist/transport/http/render.js.map +0 -1
  170. package/dist/transport/index.d.ts.map +0 -1
  171. package/dist/transport/index.js.map +0 -1
  172. package/dist/transport/router.d.ts.map +0 -1
  173. package/dist/transport/router.js.map +0 -1
  174. package/dist/transport/stream-error.d.ts.map +0 -1
  175. package/dist/transport/stream-error.js.map +0 -1
  176. package/dist/transport/types.d.ts.map +0 -1
  177. package/dist/transport/types.js.map +0 -1
  178. package/dist/transport/websocket/index.d.ts.map +0 -1
  179. package/dist/transport/websocket/index.js.map +0 -1
  180. package/dist/transport/websocket/session.d.ts.map +0 -1
  181. package/dist/transport/websocket/session.js.map +0 -1
  182. package/dist/transport/websocket/upgrade.d.ts.map +0 -1
  183. package/dist/transport/websocket/upgrade.js.map +0 -1
  184. package/dist/types.d.ts.map +0 -1
  185. package/dist/types.js.map +0 -1
@@ -1,7 +1,8 @@
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';
4
+ import { bundleUrl } from '@astrale-os/kernel-protocol/publication';
5
+ import { mediaType as publicationMediaType, PATH as PUBLICATION_PATH, url as publicationUrl, } from '@astrale-os/kernel-protocol/publication/publication';
5
6
  import { reference as publicationReference } from '@astrale-os/kernel-protocol/publication/reference';
6
7
  import * as routes from '@astrale-os/kernel-protocol/routes';
7
8
  import { classifyError, RequestBodyInvalidError, RequestBodyTooLargeError, } from '../errors/index.js';
@@ -11,11 +12,11 @@ import { readPreparedPublicationSnapshot, } from '../publication/index.js';
11
12
  import { readBoundedBody } from '../transport/body.js';
12
13
  import {} from '../transport/router.js';
13
14
  import { interruptedStreamError } from '../transport/stream-error.js';
14
- export function mountRoutes(app, source, deliveryProvider, handler, active, nextInvocation, limits, cors, hostFor, reservations, externalIssuer) {
15
+ export function mountRoutes(app, source, deliveryProvider, handler, active, nextInvocation, limits, cors, hostFor, reservations, externalIssuer, publicOrigin) {
15
16
  const publicationPath = externalIssuer === undefined
16
17
  ? PUBLICATION_PATH
17
18
  : new URL(publicationUrl(externalIssuer)).pathname;
18
- const externalOrigin = externalIssuer === undefined ? undefined : new URL(externalIssuer).origin;
19
+ const externalOrigin = publicOrigin ?? (externalIssuer === undefined ? undefined : new URL(externalIssuer).origin);
19
20
  if (cors.enabled) {
20
21
  app.options(publicationPath, (context) => cors.preflight(context.req.raw, {
21
22
  methods: ['GET'],
@@ -26,7 +27,7 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
26
27
  const responseCors = cors.response(context.req.raw, ['etag']);
27
28
  if (!active.isAccepting)
28
29
  return response({ error: 'Publication is unavailable while draining.' }, 503, responseCors);
29
- const snapshot = servingSnapshot(source, limits, context.req.raw, reservations, externalIssuer);
30
+ const snapshot = servingSnapshot(source, limits, context.req.raw, reservations, externalIssuer, externalOrigin);
30
31
  if (snapshot === undefined)
31
32
  return response({ error: 'Publication is not ready.' }, 503, responseCors);
32
33
  const body = snapshot.publicationBytes;
@@ -44,14 +45,14 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
44
45
  status: 200,
45
46
  headers: {
46
47
  ...responseCors,
47
- 'content-type': 'application/json',
48
+ 'content-type': publicationMediaType(snapshot.publication),
48
49
  etag: quoteEtag(snapshot.publication.etag),
49
50
  'cache-control': 'public, max-age=0, must-revalidate, no-transform',
50
51
  },
51
52
  });
52
53
  });
53
54
  app.all('*', async (context) => {
54
- const snapshot = servingSnapshot(source, limits, context.req.raw, reservations, externalIssuer);
55
+ const snapshot = servingSnapshot(source, limits, context.req.raw, reservations, externalIssuer, externalOrigin);
55
56
  if (snapshot === undefined)
56
57
  return context.notFound();
57
58
  const request = context.req.raw;
@@ -59,6 +60,9 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
59
60
  return publicationPreflight(cors, request, snapshot, externalOrigin);
60
61
  }
61
62
  const responseCors = cors.response(request);
63
+ const descriptor = snapshot.publication.schema.bundle;
64
+ const addressed = bundleUrl(snapshot.publication.identity.issuer, descriptor.ref);
65
+ const immutable = descriptor.href === addressed;
62
66
  if (sameRoute(request.url, snapshot.publication.schema.bundle.href, externalOrigin)) {
63
67
  if (!active.isAccepting)
64
68
  return response({ error: 'Publication artifact is unavailable while draining.' }, 503, responseCors);
@@ -68,16 +72,32 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
68
72
  if (body.byteLength !== snapshot.publication.schema.bundle.ref.size) {
69
73
  return response({ error: 'Bundle differs from its Publication.' }, 500, responseCors);
70
74
  }
71
- return new Response(toBody(body), {
72
- status: 200,
75
+ const etag = quoteEtag(descriptor.ref.digest);
76
+ const unchanged = request.headers.get('if-none-match') === etag;
77
+ return new Response(unchanged ? null : toBody(body), {
78
+ status: unchanged ? 304 : 200,
73
79
  headers: {
74
80
  ...responseCors,
75
81
  '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',
82
+ etag,
83
+ 'cache-control': immutable
84
+ ? 'public, max-age=31536000, immutable, no-transform'
85
+ : 'public, max-age=0, must-revalidate, no-transform',
78
86
  },
79
87
  });
80
88
  }
89
+ if (request.method === 'GET' && immutable) {
90
+ const pathname = new URL(addressed).pathname;
91
+ const directory = pathname.slice(0, pathname.lastIndexOf('/') + 1);
92
+ const requested = new URL(request.url);
93
+ if (requested.pathname.startsWith(directory) &&
94
+ /^[0-9a-f]{64}\.json$/u.test(requested.pathname.slice(directory.length))) {
95
+ return new Response(null, {
96
+ status: 404,
97
+ headers: { ...responseCors, 'cache-control': 'no-store' },
98
+ });
99
+ }
100
+ }
81
101
  const deliveryUrl = snapshot.publication.delivery?.href;
82
102
  if (deliveryUrl !== undefined && sameRoute(request.url, deliveryUrl, externalOrigin)) {
83
103
  return handleDelivery(request, snapshot, deliveryProvider, active, limits, cors.response(request));
@@ -117,7 +137,7 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
117
137
  let handedOff = false;
118
138
  try {
119
139
  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);
140
+ const invocation = routes.decode(route, { url: request.url, method: request.method, headers: request.headers, body }, snapshot.domain, snapshot.table.publication);
121
141
  const idempotencyHeader = request.headers.get(protocol.HTTP_IDEMPOTENCY_KEY);
122
142
  const idempotencyKey = idempotencyHeader === null
123
143
  ? undefined
@@ -138,8 +158,13 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
138
158
  });
139
159
  }
140
160
  if (result.kind === 'binary') {
161
+ if (isStreamingBinary(result.value)) {
162
+ handedOff = true;
163
+ return streamingBinaryResponse(result.value, operation, cors, request);
164
+ }
141
165
  const applicationHeaders = applicationBinaryHeaders(result.value.headers);
142
166
  return new Response(toBody(result.value.body), {
167
+ status: result.value.status ?? 200,
143
168
  headers: {
144
169
  ...cors.response(request, [
145
170
  protocol.http.BINARY_HEADERS_FIELD,
@@ -197,6 +222,73 @@ export function mountRoutes(app, source, deliveryProvider, handler, active, next
197
222
  }
198
223
  });
199
224
  }
225
+ function streamingBinaryResponse(binary, operation, cors, request) {
226
+ const iterator = binary.body[Symbol.asyncIterator]();
227
+ const applicationHeaders = applicationBinaryHeaders(binary.headers);
228
+ let terminal = false;
229
+ let controller;
230
+ let stopping;
231
+ const stop = (reason = 'Binary response was interrupted.') => {
232
+ if (terminal)
233
+ return stopping ?? Promise.resolve();
234
+ terminal = true;
235
+ controller?.error(new Error(reason));
236
+ stopping = Promise.resolve().then(async () => {
237
+ try {
238
+ await iterator.return?.();
239
+ }
240
+ finally {
241
+ operation.settle();
242
+ }
243
+ });
244
+ return stopping;
245
+ };
246
+ const body = new ReadableStream({
247
+ start(output) {
248
+ controller = output;
249
+ },
250
+ async pull(controller) {
251
+ if (terminal)
252
+ return;
253
+ try {
254
+ const next = await iterator.next();
255
+ if (terminal)
256
+ return;
257
+ if (next.done) {
258
+ terminal = true;
259
+ controller.close();
260
+ operation.settle();
261
+ return;
262
+ }
263
+ controller.enqueue(next.value);
264
+ }
265
+ catch (cause) {
266
+ if (terminal)
267
+ return;
268
+ terminal = true;
269
+ controller.error(cause);
270
+ operation.settle();
271
+ }
272
+ },
273
+ async cancel(reason) {
274
+ const message = typeof reason === 'string' ? reason : 'native binary response cancelled';
275
+ operation.cancel(message);
276
+ await stop();
277
+ },
278
+ });
279
+ operation.onCancel(stop);
280
+ return new Response(body, {
281
+ status: binary.status ?? 200,
282
+ headers: {
283
+ ...cors.response(request, Object.keys(applicationHeaders)),
284
+ ...applicationHeaders,
285
+ 'content-type': binary.mediaType,
286
+ },
287
+ });
288
+ }
289
+ function isStreamingBinary(input) {
290
+ return !(input.body instanceof Uint8Array);
291
+ }
200
292
  function publicationPreflight(cors, request, snapshot, externalOrigin) {
201
293
  if (sameRoute(request.url, snapshot.publication.schema.bundle.href, externalOrigin)) {
202
294
  return cors.preflight(request, { methods: ['GET'], headers: ['if-none-match'] });
@@ -260,9 +352,10 @@ export function currentPublicationSnapshot(source, limits, serving) {
260
352
  }
261
353
  return prepared;
262
354
  }
263
- function servingSnapshot(source, limits, request, reservations, externalIssuer) {
355
+ function servingSnapshot(source, limits, request, reservations, externalIssuer, publicOrigin) {
264
356
  return currentPublicationSnapshot(source, limits, {
265
- origin: externalIssuer === undefined ? new URL(request.url).origin : new URL(externalIssuer).origin,
357
+ origin: publicOrigin ??
358
+ (externalIssuer === undefined ? new URL(request.url).origin : new URL(externalIssuer).origin),
266
359
  ...(externalIssuer === undefined ? {} : { issuer: externalIssuer }),
267
360
  reservations,
268
361
  });
@@ -446,4 +539,3 @@ function encodedBody(input) {
446
539
  function toBody(input) {
447
540
  return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
448
541
  }
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
package/dist/server.js CHANGED
@@ -26,7 +26,8 @@ function createPreparedServer(input, config) {
26
26
  const app = createServerRouter();
27
27
  const hosts = new WeakMap();
28
28
  const externalIssuer = config.issuer?.issuer;
29
- const externalOrigin = externalIssuer === undefined ? undefined : new URL(externalIssuer).origin;
29
+ const externalOrigin = config.publicOrigin ??
30
+ (externalIssuer === undefined ? undefined : new URL(externalIssuer).origin);
30
31
  const handleHttp = createHttpHandler({
31
32
  handler,
32
33
  active,
@@ -68,7 +69,7 @@ function createPreparedServer(input, config) {
68
69
  app.all(config.path, (context) => handleHttp(context.req.raw, hosts.get(context.req.raw)));
69
70
  if (config.health !== undefined) {
70
71
  app.get(config.health.path, (context) => {
71
- const ready = active.isAccepting && config.health.probe.check();
72
+ const ready = active.isAvailable && config.health.probe.check();
72
73
  return context.json({ status: ready ? 'ready' : 'not-ready' }, ready ? 200 : 503, {
73
74
  ...config.cors.response(context.req.raw),
74
75
  'cache-control': 'no-store',
@@ -82,7 +83,7 @@ function createPreparedServer(input, config) {
82
83
  mountIssuer(app, config.issuer, config.cors, active, config.limits.invocationTimeoutMs, config.tokenExchange);
83
84
  }
84
85
  if (config.publication !== undefined) {
85
- mountRoutes(app, config.publication, config.delivery, handler, active, config.invocation, config.limits, config.cors, (request) => hosts.get(request), config.routeReservations, externalIssuer);
86
+ mountRoutes(app, config.publication, config.delivery, handler, active, config.invocation, config.limits, config.cors, (request) => hosts.get(request), config.routeReservations, externalIssuer, externalOrigin);
86
87
  }
87
88
  app.onError((_error, context) => new Response('{"error":"Internal server error."}', {
88
89
  status: 500,
@@ -131,6 +132,7 @@ function captureHandler(input) {
131
132
  return Object.freeze({ dispatch });
132
133
  }
133
134
  function admitOptions(config) {
135
+ const publicOrigin = config.publicOrigin === undefined ? undefined : admitPublicOrigin(config.publicOrigin);
134
136
  const invocation = captureInvocationSource(config.invocation);
135
137
  const limits = admitLimits(config.limits);
136
138
  const path = admitRoute(config.path ?? '/', 'invocation path');
@@ -180,6 +182,7 @@ function admitOptions(config) {
180
182
  }
181
183
  return Object.freeze({
182
184
  invocation,
185
+ ...(publicOrigin === undefined ? {} : { publicOrigin }),
183
186
  limits,
184
187
  path,
185
188
  cors: prepareCors(config.cors),
@@ -264,4 +267,15 @@ function admitRoute(path, label) {
264
267
  }
265
268
  return path;
266
269
  }
267
- //# sourceMappingURL=server.js.map
270
+ function admitPublicOrigin(input) {
271
+ const url = new URL(input);
272
+ if (!['http:', 'https:'].includes(url.protocol) ||
273
+ url.username !== '' ||
274
+ url.password !== '' ||
275
+ url.pathname !== '/' ||
276
+ url.search !== '' ||
277
+ url.hash !== '') {
278
+ throw new TypeError('Server publicOrigin must be an HTTP(S) origin.');
279
+ }
280
+ return url.origin;
281
+ }
@@ -2,4 +2,3 @@ export { RequestBodyInvalidError, RequestBodyTooLargeError } from '../errors/ind
2
2
  /** Read a request body while enforcing the limit before retaining excess bytes. */
3
3
  export declare function readBoundedBody(request: Request, maximumBytes: number, signal: AbortSignal): Promise<Uint8Array>;
4
4
  export declare function byteLength(input: string | Uint8Array): number;
5
- //# sourceMappingURL=body.d.ts.map
@@ -48,4 +48,3 @@ export async function readBoundedBody(request, maximumBytes, signal) {
48
48
  export function byteLength(input) {
49
49
  return typeof input === 'string' ? new TextEncoder().encode(input).byteLength : input.byteLength;
50
50
  }
51
- //# sourceMappingURL=body.js.map
@@ -2,4 +2,3 @@ import * as codecs from '@astrale-os/kernel-protocol/codecs';
2
2
  export type LoadedCodec = codecs.Codec;
3
3
  /** Load the built-in Server representation set before pure Protocol binding selection. */
4
4
  export declare function loadCodecs(): Promise<readonly [codecs.Codec, codecs.Codec]>;
5
- //# sourceMappingURL=codecs.d.ts.map
@@ -3,4 +3,3 @@ import * as codecs from '@astrale-os/kernel-protocol/codecs';
3
3
  export async function loadCodecs() {
4
4
  return Object.freeze([codecs.json, await codecs.loadMsgpack()]);
5
5
  }
6
- //# sourceMappingURL=codecs.js.map
@@ -8,4 +8,3 @@ export type CredentialResult = {
8
8
  };
9
9
  /** Resolve exactly zero or one untrusted credential channel without authenticating it. */
10
10
  export declare function resolveCredential(envelope: string | undefined, authorization: string | null): CredentialResult;
11
- //# sourceMappingURL=credential.d.ts.map
@@ -26,4 +26,3 @@ function failure(message) {
26
26
  error: { code: errors.CODES.INVALID_REQUEST, message },
27
27
  };
28
28
  }
29
- //# sourceMappingURL=credential.js.map
@@ -15,4 +15,3 @@ export interface HttpHandlerConfig {
15
15
  readonly currentPublication?: (request: Request) => PublicationSnapshot | undefined;
16
16
  }
17
17
  export declare function createHttpHandler(config: HttpHandlerConfig): (request: Request, host?: ApplicationHost) => Promise<Response>;
18
- //# sourceMappingURL=handler.d.ts.map
@@ -6,7 +6,7 @@ import { invocationContext, publicationRouteDecision, } from '../../invocation/i
6
6
  import { readBoundedBody } from '../body.js';
7
7
  import { loadCodecs } from '../codecs.js';
8
8
  import { resolveCredential } from '../credential.js';
9
- import { renderHttpError, renderHttpResult } from './render.js';
9
+ import { isStreamingBinary, renderHttpError, renderHttpResult, } from './render.js';
10
10
  export function createHttpHandler(config) {
11
11
  return async (request, host) => {
12
12
  const cors = (exposedHeaders) => config.cors.response(request, exposedHeaders);
@@ -146,7 +146,8 @@ export function createHttpHandler(config) {
146
146
  active,
147
147
  maximumResponseBytes: config.maximumResponseBytes,
148
148
  });
149
- handedOff = result.kind === 'stream';
149
+ handedOff =
150
+ result.kind === 'stream' || (result.kind === 'binary' && isStreamingBinary(result.value));
150
151
  return response;
151
152
  }
152
153
  catch (input) {
@@ -178,4 +179,3 @@ function noStore(response) {
178
179
  headers.set('cache-control', 'no-store');
179
180
  return new Response(response.body, { status: response.status, headers });
180
181
  }
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,3 +1,4 @@
1
+ import type { BufferedBinaryResult, StreamingBinaryResult } from '@astrale-os/kernel-core/callable';
1
2
  import type { Codec } from '@astrale-os/kernel-protocol/codecs';
2
3
  import * as errors from '@astrale-os/kernel-protocol/errors';
3
4
  import * as protocol from '@astrale-os/kernel-protocol/invocation';
@@ -19,14 +20,14 @@ export interface ErrorRenderOptions {
19
20
  }
20
21
  type HandlerResult = Awaited<ReturnType<Handler['dispatch']>>;
21
22
  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;
23
+ export declare function renderBinary(requestId: protocol.RequestId, invocation: protocol.InvocationId, binary: BufferedBinaryResult): protocol.BinaryResponse;
25
24
  export declare function renderRedirect(requestId: protocol.RequestId, invocation: protocol.InvocationId, redirect: Extract<HandlerResult, {
26
25
  readonly kind: 'redirect';
27
26
  }>['redirect']): protocol.RedirectResponse;
28
27
  export declare function renderHttpError(requestId: protocol.RequestId, invocation: protocol.InvocationId, error: errors.ErrorPayload, codec: Codec, cors: CorsHeaders, options?: ErrorRenderOptions): Response;
29
28
  export declare function renderHttpStale(requestId: protocol.RequestId, invocation: protocol.InvocationId, current: protocol.Stale['current'], codec: Codec, cors: CorsHeaders, maximumResponseBytes?: number): Response;
30
29
  export declare function encodeEnvelope(codec: Codec, input: unknown): string | Uint8Array;
30
+ export declare function isStreamingBinary(input: Extract<HandlerResult, {
31
+ readonly kind: 'binary';
32
+ }>['value']): input is StreamingBinaryResult;
31
33
  export {};
32
- //# sourceMappingURL=render.d.ts.map
@@ -19,6 +19,8 @@ export function renderHttpResult(result, context) {
19
19
  case 'miss':
20
20
  return settled(context.active, envelopeResponse(context.codec, protocol.miss(context.requestId, context.invocation), context.cors(), 200, true, context.maximumResponseBytes));
21
21
  case 'binary': {
22
+ if (isStreamingBinary(result.value))
23
+ return streamingBinaryHttpResponse(result.value, context);
22
24
  const binary = renderBinary(context.requestId, context.invocation, result.value);
23
25
  return settled(context.active, binaryHttpResponse(binary, context.cors, context.maximumResponseBytes));
24
26
  }
@@ -41,6 +43,7 @@ export function renderBinary(requestId, invocation, binary) {
41
43
  invocation,
42
44
  body: binary.body,
43
45
  mediaType: binary.mediaType,
46
+ status: binary.status ?? 200,
44
47
  ...(binary.headers === undefined ? {} : { headers: binary.headers }),
45
48
  });
46
49
  }
@@ -75,11 +78,81 @@ function envelopeResponse(codec, input, cors, status = 200, negotiated = true, m
75
78
  }
76
79
  function binaryHttpResponse(binary, cors, maximumResponseBytes) {
77
80
  requireResponseBound(binary.body, maximumResponseBytes);
81
+ const headers = binaryHttpHeaders(binary, cors, false);
82
+ return new Response(toBody(binary.body), { status: binary.status, headers });
83
+ }
84
+ function streamingBinaryHttpResponse(binary, context) {
85
+ const iterator = binary.body[Symbol.asyncIterator]();
86
+ const headers = binaryHttpHeaders(Object.freeze({
87
+ requestId: context.requestId,
88
+ invocation: context.invocation,
89
+ mediaType: binary.mediaType,
90
+ headers: binary.headers,
91
+ }), context.cors, true);
92
+ let terminal = false;
93
+ let controller;
94
+ let stopping;
95
+ const stop = (reason = 'Binary response was interrupted.') => {
96
+ if (terminal)
97
+ return stopping ?? Promise.resolve();
98
+ terminal = true;
99
+ controller?.error(new Error(reason));
100
+ stopping = Promise.resolve().then(async () => {
101
+ try {
102
+ await iterator.return?.();
103
+ }
104
+ finally {
105
+ context.active.settle();
106
+ }
107
+ });
108
+ return stopping;
109
+ };
110
+ const body = new ReadableStream({
111
+ start(output) {
112
+ controller = output;
113
+ },
114
+ async pull(controller) {
115
+ if (terminal)
116
+ return;
117
+ try {
118
+ const next = await iterator.next();
119
+ if (terminal)
120
+ return;
121
+ if (next.done) {
122
+ terminal = true;
123
+ controller.close();
124
+ context.active.settle();
125
+ return;
126
+ }
127
+ controller.enqueue(next.value);
128
+ }
129
+ catch (cause) {
130
+ if (terminal)
131
+ return;
132
+ terminal = true;
133
+ controller.error(cause);
134
+ context.active.settle();
135
+ }
136
+ },
137
+ async cancel(reason) {
138
+ const message = typeof reason === 'string' ? reason : 'binary response consumer cancelled';
139
+ context.active.cancel(message);
140
+ await stop();
141
+ },
142
+ });
143
+ context.active.onCancel(stop);
144
+ return new Response(body, { status: binary.status ?? 200, headers });
145
+ }
146
+ export function isStreamingBinary(input) {
147
+ return !(input.body instanceof Uint8Array);
148
+ }
149
+ function binaryHttpHeaders(binary, cors, streaming) {
78
150
  const applicationHeaders = Object.keys(binary.headers ?? {}).filter((name) => protocol.http.isApplicationBinaryHeader(name));
79
151
  const headers = new Headers(cors([
80
152
  protocol.http.REQUEST_ID_FIELD,
81
153
  protocol.http.INVOCATION_FIELD,
82
154
  protocol.http.BINARY_HEADERS_FIELD,
155
+ ...(streaming ? [protocol.http.BINARY_DELIVERY_FIELD] : []),
83
156
  ...applicationHeaders,
84
157
  ]));
85
158
  for (const [name, value] of Object.entries(binary.headers ?? {})) {
@@ -90,9 +163,12 @@ function binaryHttpResponse(binary, cors, maximumResponseBytes) {
90
163
  headers.set(protocol.http.BINARY_HEADERS_FIELD, protocol.http.encodeBinaryHeaderNames(binary.headers));
91
164
  headers.set(protocol.http.REQUEST_ID_FIELD, String(binary.requestId));
92
165
  headers.set(protocol.http.INVOCATION_FIELD, protocol.http.encodeInvocationId(binary.invocation));
166
+ if (streaming) {
167
+ headers.set(protocol.http.BINARY_DELIVERY_FIELD, protocol.http.STREAMING_BINARY_DELIVERY);
168
+ }
93
169
  headers.set('cache-control', 'no-store');
94
170
  appendVary(headers, 'accept');
95
- return new Response(toBody(binary.body), { status: 200, headers });
171
+ return headers;
96
172
  }
97
173
  function streamHttpResponse(stream, context) {
98
174
  const iterator = stream[Symbol.asyncIterator]();
@@ -286,4 +362,3 @@ function toBody(input) {
286
362
  const copy = new Uint8Array(input);
287
363
  return copy.buffer;
288
364
  }
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
@@ -18,4 +18,3 @@ export interface WebSocketSessionConfig {
18
18
  }
19
19
  /** Build one socket-pinned transport session. */
20
20
  export declare function createWebSocketSession(config: WebSocketSessionConfig): WebSocketHandlers;
21
- //# sourceMappingURL=session.d.ts.map
@@ -5,7 +5,7 @@ import { ResponseBodyTooLargeError } from '../../errors/index.js';
5
5
  import { invocationContext, publicationRouteDecision, } from '../../invocation/index.js';
6
6
  import { byteLength } from '../body.js';
7
7
  import { resolveCredential } from '../credential.js';
8
- import { renderBinary, renderRedirect } from '../http/render.js';
8
+ import { isStreamingBinary, renderBinary, renderRedirect } from '../http/render.js';
9
9
  import { interruptedStreamError } from '../stream-error.js';
10
10
  const WS_OPEN = 1;
11
11
  /** Build one socket-pinned transport session. */
@@ -211,6 +211,17 @@ async function sendResult(socket, codec, requestId, assigned, result, operation,
211
211
  await send(socket, codec, protocol.miss(requestId, assigned), sendOutput);
212
212
  return;
213
213
  case 'binary': {
214
+ if (isStreamingBinary(result.value)) {
215
+ const streaming = result.value;
216
+ await Promise.allSettled([
217
+ sendError(socket, codec, requestId, assigned, {
218
+ code: errors.CODES.UNSUPPORTED,
219
+ message: 'Streaming binary output requires the HTTP transport.',
220
+ }, sendOutput),
221
+ Promise.resolve().then(() => streaming.body[Symbol.asyncIterator]().return?.()),
222
+ ]);
223
+ return;
224
+ }
214
225
  const binary = renderBinary(requestId, assigned, result.value);
215
226
  if (socket.readyState === WS_OPEN) {
216
227
  await sendOutput(socket, protocol.websocket.encodeBinary(binary));
@@ -333,4 +344,3 @@ function isCancelRequest(input) {
333
344
  !Array.isArray(input) &&
334
345
  input.kind === 'cancel');
335
346
  }
336
- //# sourceMappingURL=session.js.map
@@ -18,4 +18,3 @@ export interface WebSocketHandlerConfig {
18
18
  readonly host?: ApplicationHost;
19
19
  }
20
20
  export declare function handleWebSocketUpgrade(config: WebSocketHandlerConfig): Promise<Response>;
21
- //# sourceMappingURL=upgrade.d.ts.map
@@ -44,4 +44,3 @@ function failure(status, input = {}) {
44
44
  headers: { ...input, 'cache-control': 'no-store' },
45
45
  });
46
46
  }
47
- //# sourceMappingURL=upgrade.js.map
package/dist/types.d.ts CHANGED
@@ -6,7 +6,7 @@ import type { PublicationSource } from './publication/index.js';
6
6
  import type { Application, WebSocketAdapter } from './transport/index.js';
7
7
  export interface ServerLimits {
8
8
  readonly maximumRequestBytes: number;
9
- /** Maximum bytes in one encoded envelope, finite-binary body, or stream frame. */
9
+ /** Maximum bytes in one encoded envelope, buffered binary body, or stream frame. */
10
10
  readonly maximumResponseBytes?: number;
11
11
  /** Maximum Server-owned active operations across all mounted protocol surfaces. */
12
12
  readonly maximumConcurrentOperations?: number;
@@ -37,6 +37,8 @@ export interface ServerOptions {
37
37
  readonly probe: HealthProbe;
38
38
  } | false;
39
39
  readonly issuer?: Issuer;
40
+ /** Public HTTP origin for transport placement, independent of the stable issuer. */
41
+ readonly publicOrigin?: string;
40
42
  /** Present only for the configured self issuer; owns verification and signing. */
41
43
  readonly tokenExchange?: exchange.Provider;
42
44
  readonly publication?: PublicationSource;
@@ -63,4 +65,3 @@ export type { ApplicationHost, ExecutionControl, Handler, HandlerRequest, Handle
63
65
  export type { exchange, Issuer } from './issuer/index.js';
64
66
  export type { PublicationSnapshot, PublicationSnapshotInput, PublicationSource, } from './publication/index.js';
65
67
  export type { Application, WebSocketAdapter, WebSocketHandlers, WebSocketLike, WebSocketUpgradeOptions, } from './transport/index.js';
66
- //# sourceMappingURL=types.d.ts.map
package/dist/types.js CHANGED
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=types.js.map