@astrale-os/kernel-server 0.5.0-beta.2 → 0.5.0-beta.20
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.
- package/dist/capture.d.ts +0 -1
- package/dist/capture.js +0 -1
- package/dist/cors/cors.d.ts +0 -1
- package/dist/cors/cors.js +0 -1
- package/dist/cors/index.d.ts +0 -1
- package/dist/cors/index.js +0 -1
- package/dist/cors/types.d.ts +0 -1
- package/dist/cors/types.js +0 -1
- package/dist/errors/availability.d.ts +0 -1
- package/dist/errors/availability.js +0 -1
- package/dist/errors/classify.d.ts +2 -3
- package/dist/errors/classify.js +4 -279
- package/dist/errors/index.d.ts +0 -1
- package/dist/errors/index.js +0 -1
- package/dist/errors/request-body.d.ts +0 -1
- package/dist/errors/request-body.js +0 -1
- package/dist/errors/response-body.d.ts +0 -1
- package/dist/errors/response-body.js +0 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +0 -1
- package/dist/invocation/context.d.ts +2 -3
- package/dist/invocation/context.js +2 -3
- package/dist/invocation/identity.d.ts +9 -0
- package/dist/invocation/identity.js +11 -0
- package/dist/invocation/index.d.ts +2 -2
- package/dist/invocation/index.js +1 -1
- package/dist/invocation/publication-route.d.ts +5 -4
- package/dist/invocation/publication-route.js +8 -7
- package/dist/invocation/types.d.ts +33 -9
- package/dist/invocation/types.js +0 -1
- package/dist/issuer/exchange.d.ts +2 -3
- package/dist/issuer/exchange.js +13 -12
- package/dist/issuer/index.d.ts +0 -1
- package/dist/issuer/index.js +0 -1
- package/dist/issuer/routes.d.ts +0 -1
- package/dist/issuer/routes.js +14 -12
- package/dist/issuer/types.d.ts +2 -2
- package/dist/issuer/types.js +0 -1
- package/dist/lifecycle/active.d.ts +2 -1
- package/dist/lifecycle/active.js +5 -3
- package/dist/lifecycle/index.d.ts +0 -1
- package/dist/lifecycle/index.js +0 -1
- package/dist/publication/delivery/handler.d.ts +0 -1
- package/dist/publication/delivery/handler.js +2 -2
- package/dist/publication/delivery/index.d.ts +0 -1
- package/dist/publication/delivery/index.js +0 -1
- package/dist/publication/delivery/types.d.ts +1 -2
- package/dist/publication/delivery/types.js +0 -1
- package/dist/publication/index.d.ts +0 -1
- package/dist/publication/index.js +0 -1
- package/dist/publication/snapshot.d.ts +5 -3
- package/dist/publication/snapshot.js +14 -7
- package/dist/publication/table.d.ts +4 -0
- package/dist/publication/table.js +16 -0
- package/dist/publication/types.d.ts +5 -2
- package/dist/publication/types.js +0 -1
- package/dist/routes/index.d.ts +0 -1
- package/dist/routes/index.js +0 -1
- package/dist/routes/routes.d.ts +3 -3
- package/dist/routes/routes.js +91 -26
- package/dist/server.d.ts +0 -1
- package/dist/server.js +13 -7
- package/dist/transport/body.d.ts +0 -1
- package/dist/transport/body.js +0 -1
- package/dist/transport/codecs.d.ts +1 -2
- package/dist/transport/codecs.js +1 -2
- package/dist/transport/credential.d.ts +1 -2
- package/dist/transport/credential.js +1 -2
- package/dist/transport/http/handler.d.ts +2 -1
- package/dist/transport/http/handler.js +36 -33
- package/dist/transport/http/index.d.ts +0 -1
- package/dist/transport/http/index.js +0 -1
- package/dist/transport/http/render.d.ts +17 -13
- package/dist/transport/http/render.js +120 -41
- package/dist/transport/index.d.ts +0 -1
- package/dist/transport/index.js +0 -1
- package/dist/transport/router.d.ts +0 -1
- package/dist/transport/router.js +0 -1
- package/dist/transport/stream-error.d.ts +2 -3
- package/dist/transport/stream-error.js +3 -4
- package/dist/transport/types.d.ts +1 -2
- package/dist/transport/types.js +0 -1
- package/dist/transport/websocket/index.d.ts +0 -1
- package/dist/transport/websocket/index.js +0 -1
- package/dist/transport/websocket/session.d.ts +4 -3
- package/dist/transport/websocket/session.js +56 -39
- package/dist/transport/websocket/upgrade.d.ts +2 -1
- package/dist/transport/websocket/upgrade.js +2 -2
- package/dist/types.d.ts +6 -4
- package/dist/types.js +0 -1
- package/package.json +23 -13
- package/dist/capture.d.ts.map +0 -1
- package/dist/capture.js.map +0 -1
- package/dist/cors/cors.d.ts.map +0 -1
- package/dist/cors/cors.js.map +0 -1
- package/dist/cors/index.d.ts.map +0 -1
- package/dist/cors/index.js.map +0 -1
- package/dist/cors/types.d.ts.map +0 -1
- package/dist/cors/types.js.map +0 -1
- package/dist/errors/availability.d.ts.map +0 -1
- package/dist/errors/availability.js.map +0 -1
- package/dist/errors/classify.d.ts.map +0 -1
- package/dist/errors/classify.js.map +0 -1
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/index.js.map +0 -1
- package/dist/errors/request-body.d.ts.map +0 -1
- package/dist/errors/request-body.js.map +0 -1
- package/dist/errors/response-body.d.ts.map +0 -1
- package/dist/errors/response-body.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invocation/context.d.ts.map +0 -1
- package/dist/invocation/context.js.map +0 -1
- package/dist/invocation/index.d.ts.map +0 -1
- package/dist/invocation/index.js.map +0 -1
- package/dist/invocation/publication-route.d.ts.map +0 -1
- package/dist/invocation/publication-route.js.map +0 -1
- package/dist/invocation/types.d.ts.map +0 -1
- package/dist/invocation/types.js.map +0 -1
- package/dist/issuer/exchange.d.ts.map +0 -1
- package/dist/issuer/exchange.js.map +0 -1
- package/dist/issuer/index.d.ts.map +0 -1
- package/dist/issuer/index.js.map +0 -1
- package/dist/issuer/routes.d.ts.map +0 -1
- package/dist/issuer/routes.js.map +0 -1
- package/dist/issuer/types.d.ts.map +0 -1
- package/dist/issuer/types.js.map +0 -1
- package/dist/lifecycle/active.d.ts.map +0 -1
- package/dist/lifecycle/active.js.map +0 -1
- package/dist/lifecycle/index.d.ts.map +0 -1
- package/dist/lifecycle/index.js.map +0 -1
- package/dist/publication/delivery/handler.d.ts.map +0 -1
- package/dist/publication/delivery/handler.js.map +0 -1
- package/dist/publication/delivery/index.d.ts.map +0 -1
- package/dist/publication/delivery/index.js.map +0 -1
- package/dist/publication/delivery/types.d.ts.map +0 -1
- package/dist/publication/delivery/types.js.map +0 -1
- package/dist/publication/index.d.ts.map +0 -1
- package/dist/publication/index.js.map +0 -1
- package/dist/publication/snapshot.d.ts.map +0 -1
- package/dist/publication/snapshot.js.map +0 -1
- package/dist/publication/types.d.ts.map +0 -1
- package/dist/publication/types.js.map +0 -1
- package/dist/routes/index.d.ts.map +0 -1
- package/dist/routes/index.js.map +0 -1
- package/dist/routes/routes.d.ts.map +0 -1
- package/dist/routes/routes.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/transport/body.d.ts.map +0 -1
- package/dist/transport/body.js.map +0 -1
- package/dist/transport/codecs.d.ts.map +0 -1
- package/dist/transport/codecs.js.map +0 -1
- package/dist/transport/credential.d.ts.map +0 -1
- package/dist/transport/credential.js.map +0 -1
- package/dist/transport/http/handler.d.ts.map +0 -1
- package/dist/transport/http/handler.js.map +0 -1
- package/dist/transport/http/index.d.ts.map +0 -1
- package/dist/transport/http/index.js.map +0 -1
- package/dist/transport/http/render.d.ts.map +0 -1
- package/dist/transport/http/render.js.map +0 -1
- package/dist/transport/index.d.ts.map +0 -1
- package/dist/transport/index.js.map +0 -1
- package/dist/transport/router.d.ts.map +0 -1
- package/dist/transport/router.js.map +0 -1
- package/dist/transport/stream-error.d.ts.map +0 -1
- package/dist/transport/stream-error.js.map +0 -1
- package/dist/transport/types.d.ts.map +0 -1
- package/dist/transport/types.js.map +0 -1
- package/dist/transport/websocket/index.d.ts.map +0 -1
- package/dist/transport/websocket/index.js.map +0 -1
- package/dist/transport/websocket/session.d.ts.map +0 -1
- package/dist/transport/websocket/session.js.map +0 -1
- package/dist/transport/websocket/upgrade.d.ts.map +0 -1
- package/dist/transport/websocket/upgrade.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
|
@@ -1,56 +1,67 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
2
|
+
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
3
|
+
import { HTTP_ROUTE_MISS, HTTP_ROUTE_STALE } from '@astrale-os/kernel-protocol/routes/routing';
|
|
2
4
|
import { classifyError } from '../../errors/index.js';
|
|
3
5
|
import { ResponseBodyTooLargeError } from '../../errors/index.js';
|
|
4
6
|
import { interruptedStreamError } from '../stream-error.js';
|
|
5
7
|
export function renderHttpResult(result, context) {
|
|
6
8
|
switch (result.kind) {
|
|
7
9
|
case 'value':
|
|
8
|
-
return settled(context.
|
|
10
|
+
return settled(context.active, envelopeResponse(context.codec, {
|
|
11
|
+
result: result.value,
|
|
12
|
+
requestId: context.requestId,
|
|
13
|
+
invocation: context.invocation,
|
|
14
|
+
}, context.cors(), 200, true, context.maximumResponseBytes));
|
|
9
15
|
case 'redirect': {
|
|
10
|
-
const response = renderRedirect(context.
|
|
11
|
-
return settled(context.
|
|
16
|
+
const response = renderRedirect(context.requestId, context.invocation, result.redirect);
|
|
17
|
+
return settled(context.active, envelopeResponse(context.codec, response, context.cors(), 200, true, context.maximumResponseBytes));
|
|
12
18
|
}
|
|
13
19
|
case 'miss':
|
|
14
|
-
return settled(context.
|
|
20
|
+
return settled(context.active, envelopeResponse(context.codec, protocol.miss(context.requestId, context.invocation), context.cors(), 200, true, context.maximumResponseBytes));
|
|
15
21
|
case 'binary': {
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
if (isStreamingBinary(result.value))
|
|
23
|
+
return streamingBinaryHttpResponse(result.value, context);
|
|
24
|
+
const binary = renderBinary(context.requestId, context.invocation, result.value);
|
|
25
|
+
return settled(context.active, binaryHttpResponse(binary, context.cors, context.maximumResponseBytes));
|
|
18
26
|
}
|
|
19
27
|
case 'stream':
|
|
20
28
|
return streamHttpResponse(result.stream, context);
|
|
21
29
|
}
|
|
22
30
|
}
|
|
23
|
-
export function renderBinary(
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
31
|
+
export function renderBinary(requestId, invocation, binary) {
|
|
32
|
+
const reserved = Object.keys(binary.headers ?? {}).find((name) => {
|
|
33
|
+
const normalized = name.toLowerCase();
|
|
34
|
+
return (normalized === protocol.http.INVOCATION_FIELD ||
|
|
35
|
+
normalized === HTTP_ROUTE_STALE ||
|
|
36
|
+
normalized === HTTP_ROUTE_MISS);
|
|
37
|
+
});
|
|
38
|
+
if (reserved !== undefined)
|
|
39
|
+
throw new TypeError(`${reserved} is reserved by Protocol.`);
|
|
30
40
|
return protocol.decodeBinaryResponse({
|
|
31
41
|
kind: 'binary',
|
|
32
|
-
|
|
42
|
+
requestId,
|
|
43
|
+
invocation,
|
|
33
44
|
body: binary.body,
|
|
34
45
|
mediaType: binary.mediaType,
|
|
46
|
+
status: binary.status ?? 200,
|
|
35
47
|
...(binary.headers === undefined ? {} : { headers: binary.headers }),
|
|
36
48
|
});
|
|
37
49
|
}
|
|
38
|
-
export function renderRedirect(
|
|
39
|
-
return protocol.
|
|
50
|
+
export function renderRedirect(requestId, invocation, redirect) {
|
|
51
|
+
return protocol.decodeRedirectResponse({
|
|
40
52
|
kind: 'redirect',
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
...(redirect.trace === undefined ? {} : { trace: redirect.trace }),
|
|
53
|
+
requestId,
|
|
54
|
+
invocation,
|
|
55
|
+
redirect,
|
|
45
56
|
});
|
|
46
57
|
}
|
|
47
|
-
export function renderHttpError(
|
|
58
|
+
export function renderHttpError(requestId, invocation, error, codec, cors, options = {}) {
|
|
48
59
|
const status = options.status ?? errors.httpStatusOf(error);
|
|
49
60
|
const extra = status === 401 ? { 'www-authenticate': 'Bearer realm="astrale-invocation"' } : {};
|
|
50
|
-
return errorEnvelopeResponse(codec, { error,
|
|
61
|
+
return errorEnvelopeResponse(codec, { error, requestId, invocation }, { ...cors(), ...extra }, status, options.negotiated ?? true, options.maximumResponseBytes);
|
|
51
62
|
}
|
|
52
|
-
export function renderHttpStale(
|
|
53
|
-
return envelopeResponse(codec, protocol.stale(
|
|
63
|
+
export function renderHttpStale(requestId, invocation, current, codec, cors, maximumResponseBytes) {
|
|
64
|
+
return envelopeResponse(codec, protocol.stale(requestId, invocation, current), cors(), 200, true, maximumResponseBytes);
|
|
54
65
|
}
|
|
55
66
|
export function encodeEnvelope(codec, input) {
|
|
56
67
|
return codec.encode(input);
|
|
@@ -67,10 +78,74 @@ function envelopeResponse(codec, input, cors, status = 200, negotiated = true, m
|
|
|
67
78
|
}
|
|
68
79
|
function binaryHttpResponse(binary, cors, maximumResponseBytes) {
|
|
69
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 stopping;
|
|
94
|
+
const stop = () => {
|
|
95
|
+
stopping ??= Promise.resolve().then(async () => {
|
|
96
|
+
if (terminal)
|
|
97
|
+
return;
|
|
98
|
+
terminal = true;
|
|
99
|
+
try {
|
|
100
|
+
await iterator.return?.();
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
context.active.settle();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return stopping;
|
|
107
|
+
};
|
|
108
|
+
const body = new ReadableStream({
|
|
109
|
+
async pull(controller) {
|
|
110
|
+
if (terminal)
|
|
111
|
+
return;
|
|
112
|
+
try {
|
|
113
|
+
const next = await iterator.next();
|
|
114
|
+
if (terminal)
|
|
115
|
+
return;
|
|
116
|
+
if (next.done) {
|
|
117
|
+
terminal = true;
|
|
118
|
+
controller.close();
|
|
119
|
+
context.active.settle();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
controller.enqueue(next.value);
|
|
123
|
+
}
|
|
124
|
+
catch (cause) {
|
|
125
|
+
terminal = true;
|
|
126
|
+
controller.error(cause);
|
|
127
|
+
context.active.settle();
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
async cancel(reason) {
|
|
131
|
+
const message = typeof reason === 'string' ? reason : 'binary response consumer cancelled';
|
|
132
|
+
context.active.cancel(message);
|
|
133
|
+
await stop();
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
context.active.onCancel(stop);
|
|
137
|
+
return new Response(body, { status: binary.status ?? 200, headers });
|
|
138
|
+
}
|
|
139
|
+
export function isStreamingBinary(input) {
|
|
140
|
+
return !(input.body instanceof Uint8Array);
|
|
141
|
+
}
|
|
142
|
+
function binaryHttpHeaders(binary, cors, streaming) {
|
|
70
143
|
const applicationHeaders = Object.keys(binary.headers ?? {}).filter((name) => protocol.http.isApplicationBinaryHeader(name));
|
|
71
144
|
const headers = new Headers(cors([
|
|
72
145
|
protocol.http.REQUEST_ID_FIELD,
|
|
146
|
+
protocol.http.INVOCATION_FIELD,
|
|
73
147
|
protocol.http.BINARY_HEADERS_FIELD,
|
|
148
|
+
...(streaming ? [protocol.http.BINARY_DELIVERY_FIELD] : []),
|
|
74
149
|
...applicationHeaders,
|
|
75
150
|
]));
|
|
76
151
|
for (const [name, value] of Object.entries(binary.headers ?? {})) {
|
|
@@ -79,14 +154,18 @@ function binaryHttpResponse(binary, cors, maximumResponseBytes) {
|
|
|
79
154
|
}
|
|
80
155
|
headers.set('content-type', binary.mediaType);
|
|
81
156
|
headers.set(protocol.http.BINARY_HEADERS_FIELD, protocol.http.encodeBinaryHeaderNames(binary.headers));
|
|
82
|
-
headers.set(protocol.http.REQUEST_ID_FIELD, String(binary.
|
|
157
|
+
headers.set(protocol.http.REQUEST_ID_FIELD, String(binary.requestId));
|
|
158
|
+
headers.set(protocol.http.INVOCATION_FIELD, protocol.http.encodeInvocationId(binary.invocation));
|
|
159
|
+
if (streaming) {
|
|
160
|
+
headers.set(protocol.http.BINARY_DELIVERY_FIELD, protocol.http.STREAMING_BINARY_DELIVERY);
|
|
161
|
+
}
|
|
83
162
|
headers.set('cache-control', 'no-store');
|
|
84
163
|
appendVary(headers, 'accept');
|
|
85
|
-
return
|
|
164
|
+
return headers;
|
|
86
165
|
}
|
|
87
166
|
function streamHttpResponse(stream, context) {
|
|
88
167
|
const iterator = stream[Symbol.asyncIterator]();
|
|
89
|
-
const startFrame = encodeBoundedStreamFrame(context.codec, { kind: 'stream.start',
|
|
168
|
+
const startFrame = encodeBoundedStreamFrame(context.codec, { kind: 'stream.start', requestId: context.requestId, invocation: context.invocation }, context.maximumResponseBytes);
|
|
90
169
|
let count = 0;
|
|
91
170
|
let terminal = false;
|
|
92
171
|
let controller;
|
|
@@ -122,7 +201,7 @@ function streamHttpResponse(stream, context) {
|
|
|
122
201
|
]);
|
|
123
202
|
}
|
|
124
203
|
finally {
|
|
125
|
-
context.
|
|
204
|
+
context.active.settle();
|
|
126
205
|
}
|
|
127
206
|
}
|
|
128
207
|
const body = new ReadableStream({
|
|
@@ -141,16 +220,16 @@ function streamHttpResponse(stream, context) {
|
|
|
141
220
|
terminal = true;
|
|
142
221
|
output.enqueue(encodeBoundedStreamFrame(context.codec, {
|
|
143
222
|
kind: 'stream.end',
|
|
144
|
-
|
|
223
|
+
requestId: context.requestId,
|
|
145
224
|
count,
|
|
146
225
|
}, context.maximumResponseBytes));
|
|
147
226
|
output.close();
|
|
148
|
-
context.
|
|
227
|
+
context.active.settle();
|
|
149
228
|
return;
|
|
150
229
|
}
|
|
151
230
|
const frame = protocol.decodeStreamFrame({
|
|
152
231
|
kind: 'stream.item',
|
|
153
|
-
|
|
232
|
+
requestId: context.requestId,
|
|
154
233
|
index: count,
|
|
155
234
|
value: next.value,
|
|
156
235
|
});
|
|
@@ -161,11 +240,11 @@ function streamHttpResponse(stream, context) {
|
|
|
161
240
|
if (terminal)
|
|
162
241
|
return;
|
|
163
242
|
terminal = true;
|
|
164
|
-
if (!context.
|
|
243
|
+
if (!context.active.signal.aborted) {
|
|
165
244
|
try {
|
|
166
245
|
output.enqueue(encodeBoundedStreamFrame(context.codec, {
|
|
167
246
|
kind: 'stream.error',
|
|
168
|
-
|
|
247
|
+
requestId: context.requestId,
|
|
169
248
|
error: classifyError(input),
|
|
170
249
|
}, context.maximumResponseBytes));
|
|
171
250
|
}
|
|
@@ -178,16 +257,16 @@ function streamHttpResponse(stream, context) {
|
|
|
178
257
|
Promise.resolve().then(() => stream.cancel('stream failed')),
|
|
179
258
|
Promise.resolve().then(() => iterator.return?.()),
|
|
180
259
|
]);
|
|
181
|
-
context.
|
|
260
|
+
context.active.settle();
|
|
182
261
|
}
|
|
183
262
|
},
|
|
184
263
|
async cancel(reason) {
|
|
185
264
|
const message = typeof reason === 'string' ? reason : 'response consumer cancelled';
|
|
186
|
-
context.
|
|
265
|
+
context.active.cancel(message);
|
|
187
266
|
await stop(message);
|
|
188
267
|
},
|
|
189
268
|
});
|
|
190
|
-
context.
|
|
269
|
+
context.active.onCancel(stop);
|
|
191
270
|
return new Response(body, {
|
|
192
271
|
status: 200,
|
|
193
272
|
headers: {
|
|
@@ -214,7 +293,8 @@ function errorEnvelopeResponse(codec, response, cors, status, negotiated, maximu
|
|
|
214
293
|
if (!(error instanceof ResponseBodyTooLargeError))
|
|
215
294
|
throw error;
|
|
216
295
|
const fallback = {
|
|
217
|
-
|
|
296
|
+
requestId: response.requestId,
|
|
297
|
+
invocation: response.invocation,
|
|
218
298
|
error: {
|
|
219
299
|
code: errors.CODES.LIMIT_EXCEEDED,
|
|
220
300
|
message: 'Response body exceeds the configured limit.',
|
|
@@ -265,8 +345,8 @@ function mergeVary(headers, value) {
|
|
|
265
345
|
function cancellationFrame(context) {
|
|
266
346
|
return {
|
|
267
347
|
kind: 'stream.error',
|
|
268
|
-
|
|
269
|
-
error: interruptedStreamError(context.
|
|
348
|
+
requestId: context.requestId,
|
|
349
|
+
error: interruptedStreamError(context.active.timedOut),
|
|
270
350
|
};
|
|
271
351
|
}
|
|
272
352
|
function toBody(input) {
|
|
@@ -275,4 +355,3 @@ function toBody(input) {
|
|
|
275
355
|
const copy = new Uint8Array(input);
|
|
276
356
|
return copy.buffer;
|
|
277
357
|
}
|
|
278
|
-
//# 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
|
package/dist/transport/index.js
CHANGED
|
@@ -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
|
package/dist/transport/router.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import {
|
|
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
|
-
export declare function interruptedStreamError(timedOut: boolean):
|
|
4
|
-
//# sourceMappingURL=stream-error.d.ts.map
|
|
3
|
+
export declare function interruptedStreamError(timedOut: boolean): ErrorPayload;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CODES } from '@astrale-os/kernel-protocol/errors';
|
|
2
2
|
/** A transport cancellation after stream.start necessarily follows the Function effect boundary. */
|
|
3
3
|
export function interruptedStreamError(timedOut) {
|
|
4
4
|
return timedOut
|
|
5
5
|
? {
|
|
6
|
-
code:
|
|
6
|
+
code: CODES.OUTCOME_UNKNOWN,
|
|
7
7
|
message: 'Invocation deadline elapsed after the Function stream began; outcome is unknown.',
|
|
8
8
|
}
|
|
9
9
|
: {
|
|
10
|
-
code:
|
|
10
|
+
code: CODES.OUTCOME_UNKNOWN,
|
|
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
|
|
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
|
package/dist/transport/types.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Codec } from '@astrale-os/kernel-protocol/codecs';
|
|
2
|
+
import type { NextInvocationId } from '../../invocation/index.js';
|
|
2
3
|
import type { ActiveOperations } from '../../lifecycle/index.js';
|
|
3
4
|
import type { PublicationSnapshot } from '../../publication/index.js';
|
|
4
5
|
import type { WebSocketHandlers } from '../types.js';
|
|
5
6
|
import { type ApplicationHost, type Handler } from '../../invocation/index.js';
|
|
6
7
|
export interface WebSocketSessionConfig {
|
|
7
|
-
readonly codec:
|
|
8
|
+
readonly codec: Codec;
|
|
8
9
|
readonly authorization: string | null;
|
|
9
10
|
readonly handler: Handler;
|
|
10
11
|
readonly active: ActiveOperations;
|
|
12
|
+
readonly invocation: NextInvocationId;
|
|
11
13
|
readonly maximumRequestBytes: number;
|
|
12
14
|
readonly maximumResponseBytes: number;
|
|
13
15
|
readonly invocationTimeoutMs: number;
|
|
@@ -16,4 +18,3 @@ export interface WebSocketSessionConfig {
|
|
|
16
18
|
}
|
|
17
19
|
/** Build one socket-pinned transport session. */
|
|
18
20
|
export declare function createWebSocketSession(config: WebSocketSessionConfig): WebSocketHandlers;
|
|
19
|
-
//# sourceMappingURL=session.d.ts.map
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
2
|
+
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
2
3
|
import { classifyError } from '../../errors/index.js';
|
|
3
4
|
import { ResponseBodyTooLargeError } from '../../errors/index.js';
|
|
4
5
|
import { invocationContext, publicationRouteDecision, } from '../../invocation/index.js';
|
|
5
6
|
import { byteLength } from '../body.js';
|
|
6
7
|
import { resolveCredential } from '../credential.js';
|
|
7
|
-
import { renderBinary, renderRedirect } from '../http/render.js';
|
|
8
|
+
import { isStreamingBinary, renderBinary, renderRedirect } from '../http/render.js';
|
|
8
9
|
import { interruptedStreamError } from '../stream-error.js';
|
|
9
10
|
const WS_OPEN = 1;
|
|
10
11
|
/** Build one socket-pinned transport session. */
|
|
@@ -71,9 +72,9 @@ export function createWebSocketSession(config) {
|
|
|
71
72
|
socket.close(1002, 'Invalid cancellation message');
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
74
|
-
const operation = operations.get(requestKey(cancellation.
|
|
75
|
+
const operation = operations.get(requestKey(cancellation.requestId));
|
|
75
76
|
if (operation === undefined) {
|
|
76
|
-
await sendError(socket, admittedCodec, cancellation.
|
|
77
|
+
await sendError(socket, admittedCodec, cancellation.requestId, config.invocation(), {
|
|
77
78
|
code: errors.CODES.INVALID_REQUEST,
|
|
78
79
|
message: 'Request ID is not active on this connection.',
|
|
79
80
|
}, sendOutput);
|
|
@@ -82,17 +83,18 @@ export function createWebSocketSession(config) {
|
|
|
82
83
|
operation.cancel('client cancelled invocation');
|
|
83
84
|
return;
|
|
84
85
|
}
|
|
85
|
-
let
|
|
86
|
+
let envelope;
|
|
86
87
|
try {
|
|
87
|
-
|
|
88
|
+
envelope = protocol.decodeRequest(decoded);
|
|
88
89
|
}
|
|
89
90
|
catch {
|
|
90
91
|
socket.close(1002, 'Invalid Invocation request');
|
|
91
92
|
return;
|
|
92
93
|
}
|
|
93
|
-
const
|
|
94
|
+
const assigned = config.invocation();
|
|
95
|
+
const key = requestKey(envelope.requestId);
|
|
94
96
|
if (operations.has(key)) {
|
|
95
|
-
await sendError(socket, admittedCodec,
|
|
97
|
+
await sendError(socket, admittedCodec, envelope.requestId, assigned, {
|
|
96
98
|
code: errors.CODES.INVALID_REQUEST,
|
|
97
99
|
message: 'Request ID is already active on this connection.',
|
|
98
100
|
}, sendOutput);
|
|
@@ -100,34 +102,34 @@ export function createWebSocketSession(config) {
|
|
|
100
102
|
}
|
|
101
103
|
const publication = config.currentPublication?.();
|
|
102
104
|
if (config.currentPublication !== undefined && publication === undefined) {
|
|
103
|
-
await sendError(socket, admittedCodec,
|
|
105
|
+
await sendError(socket, admittedCodec, envelope.requestId, assigned, {
|
|
104
106
|
code: errors.CODES.BACKEND_UNAVAILABLE,
|
|
105
107
|
message: 'Publication is unavailable.',
|
|
106
108
|
}, sendOutput);
|
|
107
109
|
return;
|
|
108
110
|
}
|
|
109
|
-
if (
|
|
111
|
+
if (envelope.routing?.kind === 'via') {
|
|
110
112
|
if (publication === undefined) {
|
|
111
|
-
await sendError(socket, admittedCodec,
|
|
113
|
+
await sendError(socket, admittedCodec, envelope.requestId, assigned, {
|
|
112
114
|
code: errors.CODES.INVALID_REQUEST,
|
|
113
115
|
message: 'Publication routing requires a Publication-backed destination.',
|
|
114
116
|
}, sendOutput);
|
|
115
117
|
return;
|
|
116
118
|
}
|
|
117
|
-
const decision = publicationRouteDecision(publication.publication,
|
|
119
|
+
const decision = publicationRouteDecision(publication.publication, envelope.routing.publication);
|
|
118
120
|
if (decision.kind === 'rejected') {
|
|
119
|
-
await sendError(socket, admittedCodec,
|
|
121
|
+
await sendError(socket, admittedCodec, envelope.requestId, assigned, decision.error, sendOutput);
|
|
120
122
|
return;
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
|
-
const credential = resolveCredential(
|
|
125
|
+
const credential = resolveCredential(envelope.credential, config.authorization);
|
|
124
126
|
if (!credential.ok) {
|
|
125
|
-
await sendError(socket, admittedCodec,
|
|
127
|
+
await sendError(socket, admittedCodec, envelope.requestId, assigned, credential.error, sendOutput);
|
|
126
128
|
return;
|
|
127
129
|
}
|
|
128
130
|
const operation = config.active.begin(config.invocationTimeoutMs);
|
|
129
131
|
if (operation === undefined) {
|
|
130
|
-
await sendError(socket, admittedCodec,
|
|
132
|
+
await sendError(socket, admittedCodec, envelope.requestId, assigned, {
|
|
131
133
|
code: errors.CODES.BACKEND_UNAVAILABLE,
|
|
132
134
|
message: 'server is unavailable.',
|
|
133
135
|
}, sendOutput);
|
|
@@ -137,16 +139,16 @@ export function createWebSocketSession(config) {
|
|
|
137
139
|
scopes.add(operation);
|
|
138
140
|
try {
|
|
139
141
|
const result = await config.handler.dispatch(Object.freeze({
|
|
140
|
-
call:
|
|
141
|
-
...(
|
|
142
|
+
call: envelope.call,
|
|
143
|
+
...(envelope.trace === undefined ? {} : { trace: envelope.trace }),
|
|
142
144
|
...(credential.credential === undefined ? {} : { credential: credential.credential }),
|
|
143
|
-
...(
|
|
144
|
-
...(
|
|
145
|
-
...(
|
|
145
|
+
...(envelope.delegate === undefined ? {} : { delegate: envelope.delegate }),
|
|
146
|
+
...(envelope.routing?.kind === 'via' ? { via: envelope.routing } : {}),
|
|
147
|
+
...(envelope.schema === undefined ? {} : { schema: envelope.schema }),
|
|
146
148
|
...(publication === undefined ? {} : { publication }),
|
|
147
|
-
context: invocationContext(operation, config.active, config.host, undefined,
|
|
149
|
+
context: invocationContext(assigned, operation, config.active, config.host, undefined, envelope.idempotencyKey),
|
|
148
150
|
}));
|
|
149
|
-
await sendResult(socket, admittedCodec,
|
|
151
|
+
await sendResult(socket, admittedCodec, envelope.requestId, assigned, result, operation, sendOutput);
|
|
150
152
|
}
|
|
151
153
|
catch (input) {
|
|
152
154
|
if (input instanceof ResponseBodyTooLargeError || input instanceof WebSocketOutputError) {
|
|
@@ -165,7 +167,7 @@ export function createWebSocketSession(config) {
|
|
|
165
167
|
socket.close(1011, 'Internal error');
|
|
166
168
|
return;
|
|
167
169
|
}
|
|
168
|
-
await sendError(socket, admittedCodec,
|
|
170
|
+
await sendError(socket, admittedCodec, envelope.requestId, assigned, error, sendOutput);
|
|
169
171
|
}
|
|
170
172
|
}
|
|
171
173
|
finally {
|
|
@@ -193,22 +195,34 @@ export function createWebSocketSession(config) {
|
|
|
193
195
|
};
|
|
194
196
|
return handlers;
|
|
195
197
|
}
|
|
196
|
-
async function sendResult(socket, codec,
|
|
198
|
+
async function sendResult(socket, codec, requestId, assigned, result, operation, sendOutput) {
|
|
197
199
|
switch (result.kind) {
|
|
198
200
|
case 'value':
|
|
199
201
|
await send(socket, codec, {
|
|
200
202
|
result: result.value,
|
|
201
|
-
|
|
203
|
+
requestId,
|
|
204
|
+
invocation: assigned,
|
|
202
205
|
}, sendOutput);
|
|
203
206
|
return;
|
|
204
207
|
case 'redirect':
|
|
205
|
-
await send(socket, codec, renderRedirect(
|
|
208
|
+
await send(socket, codec, renderRedirect(requestId, assigned, result.redirect), sendOutput);
|
|
206
209
|
return;
|
|
207
210
|
case 'miss':
|
|
208
|
-
await send(socket, codec, protocol.miss(
|
|
211
|
+
await send(socket, codec, protocol.miss(requestId, assigned), sendOutput);
|
|
209
212
|
return;
|
|
210
213
|
case 'binary': {
|
|
211
|
-
|
|
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
|
+
}
|
|
225
|
+
const binary = renderBinary(requestId, assigned, result.value);
|
|
212
226
|
if (socket.readyState === WS_OPEN) {
|
|
213
227
|
await sendOutput(socket, protocol.websocket.encodeBinary(binary));
|
|
214
228
|
}
|
|
@@ -234,7 +248,7 @@ async function sendResult(socket, codec, id, result, operation, sendOutput) {
|
|
|
234
248
|
terminal = true;
|
|
235
249
|
const terminalOutput = emit({
|
|
236
250
|
kind: 'stream.error',
|
|
237
|
-
|
|
251
|
+
requestId,
|
|
238
252
|
error: interruptedStreamError(operation.timedOut),
|
|
239
253
|
});
|
|
240
254
|
await Promise.allSettled([
|
|
@@ -243,7 +257,11 @@ async function sendResult(socket, codec, id, result, operation, sendOutput) {
|
|
|
243
257
|
Promise.resolve().then(() => iterator.return?.()),
|
|
244
258
|
]);
|
|
245
259
|
};
|
|
246
|
-
const started = emit({
|
|
260
|
+
const started = emit({
|
|
261
|
+
kind: 'stream.start',
|
|
262
|
+
requestId,
|
|
263
|
+
invocation: assigned,
|
|
264
|
+
});
|
|
247
265
|
operation.onCancel(cancel);
|
|
248
266
|
let count = 0;
|
|
249
267
|
try {
|
|
@@ -254,12 +272,12 @@ async function sendResult(socket, codec, id, result, operation, sendOutput) {
|
|
|
254
272
|
break;
|
|
255
273
|
if (next.done) {
|
|
256
274
|
terminal = true;
|
|
257
|
-
await emit({ kind: 'stream.end',
|
|
275
|
+
await emit({ kind: 'stream.end', requestId, count });
|
|
258
276
|
break;
|
|
259
277
|
}
|
|
260
278
|
await emit(protocol.decodeStreamFrame({
|
|
261
279
|
kind: 'stream.item',
|
|
262
|
-
|
|
280
|
+
requestId,
|
|
263
281
|
index: count,
|
|
264
282
|
value: next.value,
|
|
265
283
|
}));
|
|
@@ -295,7 +313,7 @@ async function sendResult(socket, codec, id, result, operation, sendOutput) {
|
|
|
295
313
|
await Promise.allSettled([
|
|
296
314
|
emit({
|
|
297
315
|
kind: 'stream.error',
|
|
298
|
-
|
|
316
|
+
requestId,
|
|
299
317
|
error,
|
|
300
318
|
}),
|
|
301
319
|
Promise.resolve().then(() => result.stream.cancel('stream failed')),
|
|
@@ -305,8 +323,8 @@ async function sendResult(socket, codec, id, result, operation, sendOutput) {
|
|
|
305
323
|
}
|
|
306
324
|
}
|
|
307
325
|
}
|
|
308
|
-
function sendError(socket, codec,
|
|
309
|
-
return send(socket, codec, { error,
|
|
326
|
+
function sendError(socket, codec, requestId, invocation, error, output) {
|
|
327
|
+
return send(socket, codec, { error, requestId, invocation }, output);
|
|
310
328
|
}
|
|
311
329
|
function send(socket, codec, value, output) {
|
|
312
330
|
return output(socket, codec.encode(value));
|
|
@@ -317,8 +335,8 @@ class WebSocketOutputError extends Error {
|
|
|
317
335
|
this.name = 'WebSocketOutputError';
|
|
318
336
|
}
|
|
319
337
|
}
|
|
320
|
-
function requestKey(
|
|
321
|
-
return
|
|
338
|
+
function requestKey(requestId) {
|
|
339
|
+
return requestId === null ? 'null:' : `${typeof requestId}:${String(requestId)}`;
|
|
322
340
|
}
|
|
323
341
|
function isCancelRequest(input) {
|
|
324
342
|
return (typeof input === 'object' &&
|
|
@@ -326,4 +344,3 @@ function isCancelRequest(input) {
|
|
|
326
344
|
!Array.isArray(input) &&
|
|
327
345
|
input.kind === 'cancel');
|
|
328
346
|
}
|
|
329
|
-
//# sourceMappingURL=session.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CorsPolicy } from '../../cors/index.js';
|
|
2
2
|
import type { ApplicationHost, Handler } from '../../invocation/index.js';
|
|
3
|
+
import type { NextInvocationId } from '../../invocation/index.js';
|
|
3
4
|
import type { ActiveOperations } from '../../lifecycle/index.js';
|
|
4
5
|
import type { PublicationSnapshot } from '../../publication/index.js';
|
|
5
6
|
import type { WebSocketAdapter } from '../types.js';
|
|
@@ -8,6 +9,7 @@ export interface WebSocketHandlerConfig {
|
|
|
8
9
|
readonly adapter: WebSocketAdapter;
|
|
9
10
|
readonly handler: Handler;
|
|
10
11
|
readonly active: ActiveOperations;
|
|
12
|
+
readonly invocation: NextInvocationId;
|
|
11
13
|
readonly maximumRequestBytes: number;
|
|
12
14
|
readonly maximumResponseBytes: number;
|
|
13
15
|
readonly invocationTimeoutMs: number;
|
|
@@ -16,4 +18,3 @@ export interface WebSocketHandlerConfig {
|
|
|
16
18
|
readonly host?: ApplicationHost;
|
|
17
19
|
}
|
|
18
20
|
export declare function handleWebSocketUpgrade(config: WebSocketHandlerConfig): Promise<Response>;
|
|
19
|
-
//# sourceMappingURL=upgrade.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as protocol from '@astrale-os/kernel-protocol/invocation';
|
|
2
2
|
import { loadCodecs } from '../codecs.js';
|
|
3
3
|
import { createWebSocketSession } from './session.js';
|
|
4
4
|
export async function handleWebSocketUpgrade(config) {
|
|
@@ -19,6 +19,7 @@ export async function handleWebSocketUpgrade(config) {
|
|
|
19
19
|
authorization: config.request.headers.get('authorization'),
|
|
20
20
|
handler: config.handler,
|
|
21
21
|
active: config.active,
|
|
22
|
+
invocation: config.invocation,
|
|
22
23
|
maximumRequestBytes: config.maximumRequestBytes,
|
|
23
24
|
maximumResponseBytes: config.maximumResponseBytes,
|
|
24
25
|
invocationTimeoutMs: config.invocationTimeoutMs,
|
|
@@ -43,4 +44,3 @@ function failure(status, input = {}) {
|
|
|
43
44
|
headers: { ...input, 'cache-control': 'no-store' },
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
|
-
//# sourceMappingURL=upgrade.js.map
|