rivetkit 2.3.12-rc.4 → 2.3.12
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/browser/client.d.ts +17 -4
- package/dist/browser/client.js +32 -24
- package/dist/browser/client.js.map +1 -1
- package/dist/browser/inspector/client.js +2 -1
- package/dist/browser/inspector/client.js.map +1 -1
- package/dist/tsup/actor/errors.cjs +2 -2
- package/dist/tsup/actor/errors.js +1 -1
- package/dist/tsup/agent-os/index.cjs +3 -19
- package/dist/tsup/agent-os/index.cjs.map +1 -1
- package/dist/tsup/agent-os/index.d.cts +17 -4
- package/dist/tsup/agent-os/index.d.ts +17 -4
- package/dist/tsup/agent-os/index.js +3 -19
- package/dist/tsup/agent-os/index.js.map +1 -1
- package/dist/tsup/{chunk-SPJORZMM.cjs → chunk-2JSYYH6P.cjs} +1 -1
- package/dist/tsup/chunk-2JSYYH6P.cjs.map +1 -0
- package/dist/tsup/{chunk-IAH47VDZ.cjs → chunk-4KGXFC6M.cjs} +2 -19
- package/dist/tsup/chunk-4KGXFC6M.cjs.map +1 -0
- package/dist/tsup/{chunk-DLQSZ6Q7.js → chunk-6W5VGLFT.js} +2 -19
- package/dist/tsup/chunk-6W5VGLFT.js.map +1 -0
- package/dist/tsup/{chunk-JIOLC5I2.cjs → chunk-AN7WO7BM.cjs} +3 -3
- package/dist/tsup/{chunk-JIOLC5I2.cjs.map → chunk-AN7WO7BM.cjs.map} +1 -1
- package/dist/tsup/{chunk-AF2VKCFA.js → chunk-EBXARMGI.js} +2 -2
- package/dist/tsup/{chunk-4LRWLBTR.cjs → chunk-FGQABMXZ.cjs} +3 -3
- package/dist/tsup/{chunk-4LRWLBTR.cjs.map → chunk-FGQABMXZ.cjs.map} +1 -1
- package/dist/tsup/{chunk-BQ7YJ3PG.js → chunk-GEN4NHXM.js} +2 -2
- package/dist/tsup/{chunk-NVMHEG5J.js → chunk-JYTXOMM7.js} +2 -2
- package/dist/tsup/{chunk-NKLM6C5A.cjs → chunk-MEF7RVQ7.cjs} +10 -10
- package/dist/tsup/{chunk-NKLM6C5A.cjs.map → chunk-MEF7RVQ7.cjs.map} +1 -1
- package/dist/tsup/{chunk-64DT44V2.js → chunk-MF42PRF2.js} +9 -3
- package/dist/tsup/chunk-MF42PRF2.js.map +1 -0
- package/dist/tsup/{chunk-IXBD7BXC.js → chunk-OUQUIBVW.js} +1 -1
- package/dist/tsup/chunk-OUQUIBVW.js.map +1 -0
- package/dist/tsup/{chunk-S3GXVCIM.cjs → chunk-PW2VSGUR.cjs} +4 -4
- package/dist/tsup/{chunk-S3GXVCIM.cjs.map → chunk-PW2VSGUR.cjs.map} +1 -1
- package/dist/tsup/{chunk-GMBCKV6C.js → chunk-RBXJWAJB.js} +3 -3
- package/dist/tsup/{chunk-XDYECGCT.cjs → chunk-SSPTEGTX.cjs} +7 -7
- package/dist/tsup/{chunk-XDYECGCT.cjs.map → chunk-SSPTEGTX.cjs.map} +1 -1
- package/dist/tsup/{chunk-ARFTQ5HY.cjs → chunk-TAG6UEL5.cjs} +17 -11
- package/dist/tsup/chunk-TAG6UEL5.cjs.map +1 -0
- package/dist/tsup/{chunk-OCXHNHGQ.cjs → chunk-TXUHVBKH.cjs} +3 -3
- package/dist/tsup/{chunk-OCXHNHGQ.cjs.map → chunk-TXUHVBKH.cjs.map} +1 -1
- package/dist/tsup/{chunk-F7G5WHOD.cjs → chunk-TZPNSOG2.cjs} +203 -195
- package/dist/tsup/chunk-TZPNSOG2.cjs.map +1 -0
- package/dist/tsup/{chunk-O74736N5.js → chunk-VOYNLY57.js} +41 -33
- package/dist/tsup/chunk-VOYNLY57.js.map +1 -0
- package/dist/tsup/{chunk-3CBQIP37.js → chunk-XUODFCRJ.js} +4 -4
- package/dist/tsup/{chunk-P5JJG4KY.js → chunk-ZIPXAVKR.js} +2 -2
- package/dist/tsup/client/mod.cjs +6 -6
- package/dist/tsup/client/mod.d.cts +2 -2
- package/dist/tsup/client/mod.d.ts +2 -2
- package/dist/tsup/client/mod.js +5 -5
- package/dist/tsup/common/log.cjs +3 -3
- package/dist/tsup/common/log.js +2 -2
- package/dist/tsup/common/websocket.cjs +4 -4
- package/dist/tsup/common/websocket.js +3 -3
- package/dist/tsup/{config-DzaddF-j.d.ts → config-BGTanB_3.d.ts} +19 -10
- package/dist/tsup/{config-VxDc6yMu.d.cts → config-NHz6oMb9.d.cts} +19 -10
- package/dist/tsup/{context-Cn8CIrOR.d.cts → context-DB8zG_Ib.d.cts} +1 -1
- package/dist/tsup/{context-B6HYokje.d.ts → context-SHomdVV8.d.ts} +1 -1
- package/dist/tsup/db/drizzle.cjs +3 -3
- package/dist/tsup/db/drizzle.js +2 -2
- package/dist/tsup/db/mod.cjs +4 -4
- package/dist/tsup/db/mod.js +3 -3
- package/dist/tsup/dynamic/mod.cjs +2 -2
- package/dist/tsup/dynamic/mod.d.cts +1 -1
- package/dist/tsup/dynamic/mod.d.ts +1 -1
- package/dist/tsup/dynamic/mod.js +1 -1
- package/dist/tsup/inspector/mod.cjs +6 -6
- package/dist/tsup/inspector/mod.js +5 -5
- package/dist/tsup/inspector/workflow.cjs +5 -5
- package/dist/tsup/inspector/workflow.js +4 -4
- package/dist/tsup/inspector-tab/mod.cjs +2 -2
- package/dist/tsup/inspector-tab/mod.d.cts +2 -2
- package/dist/tsup/inspector-tab/mod.d.ts +2 -2
- package/dist/tsup/inspector-tab/mod.js +1 -1
- package/dist/tsup/mod.cjs +444 -199
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +3 -3
- package/dist/tsup/mod.d.ts +3 -3
- package/dist/tsup/mod.js +323 -78
- package/dist/tsup/mod.js.map +1 -1
- package/dist/tsup/test/mod.cjs +7 -7
- package/dist/tsup/test/mod.d.cts +1 -1
- package/dist/tsup/test/mod.d.ts +1 -1
- package/dist/tsup/test/mod.js +5 -5
- package/dist/tsup/utils.cjs +3 -3
- package/dist/tsup/utils.js +2 -2
- package/dist/tsup/workflow/mod.cjs +14 -14
- package/dist/tsup/workflow/mod.d.cts +3 -3
- package/dist/tsup/workflow/mod.d.ts +3 -3
- package/dist/tsup/workflow/mod.js +6 -6
- package/package.json +9 -8
- package/src/actor/config.test.ts +0 -8
- package/src/actor/config.ts +1 -24
- package/src/actor/errors.ts +2 -1
- package/src/client/actor-handle.ts +10 -3
- package/src/client/raw-utils.ts +8 -0
- package/src/common/actor-router-consts.ts +2 -0
- package/src/engine-client/actor-http-client.ts +6 -22
- package/src/engine-client/mod.ts +1 -1
- package/src/registry/config/index.ts +27 -1
- package/src/registry/index.ts +48 -14
- package/src/registry/napi-runtime.ts +32 -5
- package/src/registry/native-http.ts +225 -0
- package/src/registry/native.ts +130 -65
- package/src/registry/runtime.ts +30 -3
- package/src/utils/env-vars.ts +4 -0
- package/dist/tsup/chunk-64DT44V2.js.map +0 -1
- package/dist/tsup/chunk-ARFTQ5HY.cjs.map +0 -1
- package/dist/tsup/chunk-DLQSZ6Q7.js.map +0 -1
- package/dist/tsup/chunk-F7G5WHOD.cjs.map +0 -1
- package/dist/tsup/chunk-IAH47VDZ.cjs.map +0 -1
- package/dist/tsup/chunk-IXBD7BXC.js.map +0 -1
- package/dist/tsup/chunk-O74736N5.js.map +0 -1
- package/dist/tsup/chunk-SPJORZMM.cjs.map +0 -1
- /package/dist/tsup/{chunk-AF2VKCFA.js.map → chunk-EBXARMGI.js.map} +0 -0
- /package/dist/tsup/{chunk-BQ7YJ3PG.js.map → chunk-GEN4NHXM.js.map} +0 -0
- /package/dist/tsup/{chunk-NVMHEG5J.js.map → chunk-JYTXOMM7.js.map} +0 -0
- /package/dist/tsup/{chunk-GMBCKV6C.js.map → chunk-RBXJWAJB.js.map} +0 -0
- /package/dist/tsup/{chunk-3CBQIP37.js.map → chunk-XUODFCRJ.js.map} +0 -0
- /package/dist/tsup/{chunk-P5JJG4KY.js.map → chunk-ZIPXAVKR.js.map} +0 -0
package/src/registry/index.ts
CHANGED
|
@@ -16,8 +16,11 @@ import {
|
|
|
16
16
|
} from "./config";
|
|
17
17
|
import { logger } from "./log";
|
|
18
18
|
import { buildConfiguredRegistry } from "./native";
|
|
19
|
+
import { convertNativeHttpResponse } from "./native-http";
|
|
19
20
|
import type {
|
|
21
|
+
CoreRuntime,
|
|
20
22
|
RuntimeApplicationFetch,
|
|
23
|
+
RuntimeHttpResponseBodyStream,
|
|
21
24
|
RuntimeServerlessResponseHead,
|
|
22
25
|
} from "./runtime";
|
|
23
26
|
|
|
@@ -43,18 +46,33 @@ function finishShutdownSignal(signal: ShutdownSignal): void {
|
|
|
43
46
|
|
|
44
47
|
function createApplicationFetch(
|
|
45
48
|
application: RegistryApplication,
|
|
49
|
+
runtime: CoreRuntime,
|
|
46
50
|
): RuntimeApplicationFetch {
|
|
47
|
-
return async (
|
|
51
|
+
return async (
|
|
52
|
+
request,
|
|
53
|
+
responseBodyStream?: RuntimeHttpResponseBodyStream,
|
|
54
|
+
) => {
|
|
48
55
|
const method = request.method.toUpperCase();
|
|
49
56
|
const body =
|
|
50
57
|
method === "GET" || method === "HEAD" || request.body.length === 0
|
|
51
58
|
? undefined
|
|
52
59
|
: Uint8Array.from(request.body).buffer;
|
|
60
|
+
const abortController = new AbortController();
|
|
61
|
+
if (request.cancelToken) {
|
|
62
|
+
if (runtime.cancellationTokenAborted(request.cancelToken)) {
|
|
63
|
+
abortController.abort();
|
|
64
|
+
} else {
|
|
65
|
+
runtime.onCancellationTokenCancelled(request.cancelToken, () =>
|
|
66
|
+
abortController.abort(),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
53
70
|
const response = await application.fetch(
|
|
54
71
|
new Request(request.url, {
|
|
55
72
|
method,
|
|
56
73
|
headers: request.headers,
|
|
57
74
|
body,
|
|
75
|
+
signal: abortController.signal,
|
|
58
76
|
}),
|
|
59
77
|
);
|
|
60
78
|
if (!isResponseLike(response)) {
|
|
@@ -62,10 +80,25 @@ function createApplicationFetch(
|
|
|
62
80
|
"registry.listen() application fetch must return a Response",
|
|
63
81
|
);
|
|
64
82
|
}
|
|
83
|
+
const conversion = await convertNativeHttpResponse(
|
|
84
|
+
response,
|
|
85
|
+
responseBodyStream,
|
|
86
|
+
);
|
|
87
|
+
if (conversion.bodyCompletion) {
|
|
88
|
+
void conversion.bodyCompletion.catch((error) => {
|
|
89
|
+
logger().error({
|
|
90
|
+
msg: "application response stream failed",
|
|
91
|
+
error,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
65
95
|
return {
|
|
66
|
-
status: response.status,
|
|
67
|
-
headers:
|
|
68
|
-
|
|
96
|
+
status: conversion.response.status ?? response.status,
|
|
97
|
+
headers:
|
|
98
|
+
conversion.response.headers ??
|
|
99
|
+
Object.fromEntries(response.headers.entries()),
|
|
100
|
+
body: conversion.response.body,
|
|
101
|
+
stream: conversion.response.stream,
|
|
69
102
|
};
|
|
70
103
|
};
|
|
71
104
|
}
|
|
@@ -406,11 +439,8 @@ export class Registry<A extends RegistryActors> {
|
|
|
406
439
|
const port = opts.port ?? parsePortEnv(process.env.RIVET_PORT) ?? 3000;
|
|
407
440
|
const publicDir = opts.publicDir ?? getRivetkitPublicDir();
|
|
408
441
|
const config = this.parseConfig();
|
|
409
|
-
const application = opts.application
|
|
410
|
-
? createApplicationFetch(opts.application)
|
|
411
|
-
: undefined;
|
|
412
442
|
|
|
413
|
-
if (application && getRivetkitRuntimeMode() !== "serverless") {
|
|
443
|
+
if (opts.application && getRivetkitRuntimeMode() !== "serverless") {
|
|
414
444
|
if (config.runtime === "wasm") {
|
|
415
445
|
throw new Error(
|
|
416
446
|
"registry.listen() requires the native runtime; use an application-owned HTTP server with WebAssembly",
|
|
@@ -431,6 +461,11 @@ export class Registry<A extends RegistryActors> {
|
|
|
431
461
|
}
|
|
432
462
|
const { runtime, registry, serveConfig } =
|
|
433
463
|
await configuredRegistryPromise;
|
|
464
|
+
const application = createApplicationFetch(
|
|
465
|
+
opts.application,
|
|
466
|
+
runtime,
|
|
467
|
+
);
|
|
468
|
+
await readyPromise;
|
|
434
469
|
const listenerPromise = runtime.serveApplicationListener(
|
|
435
470
|
registry,
|
|
436
471
|
{
|
|
@@ -448,16 +483,12 @@ export class Registry<A extends RegistryActors> {
|
|
|
448
483
|
"registry envoy serve lifecycle did not initialize",
|
|
449
484
|
);
|
|
450
485
|
}
|
|
451
|
-
await Promise.all([
|
|
452
|
-
readyPromise,
|
|
453
|
-
listenerPromise,
|
|
454
|
-
serveLifecyclePromise,
|
|
455
|
-
]);
|
|
486
|
+
await Promise.all([listenerPromise, serveLifecyclePromise]);
|
|
456
487
|
return;
|
|
457
488
|
}
|
|
458
489
|
|
|
459
490
|
// Cache on both promise fields so the shutdown drain sees Mode A and B.
|
|
460
|
-
const configuredRegistryPromise = buildConfiguredRegistry(config);
|
|
491
|
+
const configuredRegistryPromise = this.#buildConfiguredRegistry(config);
|
|
461
492
|
this.#runtimeServeConfiguredPromise = configuredRegistryPromise;
|
|
462
493
|
this.#runtimeServerlessPromise = configuredRegistryPromise;
|
|
463
494
|
this.#installSignalHandlers(config);
|
|
@@ -473,6 +504,9 @@ export class Registry<A extends RegistryActors> {
|
|
|
473
504
|
|
|
474
505
|
const { runtime, registry, serveConfig } =
|
|
475
506
|
await configuredRegistryPromise;
|
|
507
|
+
const application = opts.application
|
|
508
|
+
? createApplicationFetch(opts.application, runtime)
|
|
509
|
+
: undefined;
|
|
476
510
|
await runtime.serveListener(
|
|
477
511
|
registry,
|
|
478
512
|
{
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
CancellationToken as NativeCancellationToken,
|
|
5
5
|
ConnHandle as NativeConnHandle,
|
|
6
6
|
CoreRegistry as NativeCoreRegistry,
|
|
7
|
+
HttpResponseBodyStream as NativeHttpResponseBodyStream,
|
|
7
8
|
WebSocket as NativeWebSocket,
|
|
8
9
|
} from "@rivetkit/rivetkit-napi";
|
|
9
10
|
import type {
|
|
@@ -21,6 +22,7 @@ import type {
|
|
|
21
22
|
RuntimeCronFire,
|
|
22
23
|
RuntimeCronJobInfo,
|
|
23
24
|
RuntimeHttpRequest,
|
|
25
|
+
RuntimeHttpResponseBodyStream,
|
|
24
26
|
RuntimeKvEntry,
|
|
25
27
|
RuntimeKvListOptions,
|
|
26
28
|
RuntimeListenerConfig,
|
|
@@ -154,23 +156,48 @@ function toNapiApplication(application: RuntimeApplicationFetch) {
|
|
|
154
156
|
url: string;
|
|
155
157
|
headers: Record<string, string>;
|
|
156
158
|
body: Buffer;
|
|
159
|
+
cancelToken?: NativeCancellationToken;
|
|
160
|
+
responseBodyStream?: RuntimeHttpResponseBodyStream;
|
|
157
161
|
},
|
|
158
162
|
) => {
|
|
159
163
|
if (error) throw error;
|
|
160
164
|
if (!request) {
|
|
161
165
|
throw new Error("application fetch callback received no request");
|
|
162
166
|
}
|
|
163
|
-
const response = await application(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
+
const response = await application(
|
|
168
|
+
{
|
|
169
|
+
...request,
|
|
170
|
+
body: new Uint8Array(request.body),
|
|
171
|
+
cancelToken:
|
|
172
|
+
request.cancelToken as unknown as CancellationTokenHandle,
|
|
173
|
+
},
|
|
174
|
+
request.responseBodyStream
|
|
175
|
+
? fromNapiHttpResponseBodyStream(
|
|
176
|
+
request.responseBodyStream as unknown as NativeHttpResponseBodyStream,
|
|
177
|
+
)
|
|
178
|
+
: undefined,
|
|
179
|
+
);
|
|
167
180
|
return {
|
|
168
181
|
...response,
|
|
169
|
-
body:
|
|
182
|
+
body:
|
|
183
|
+
response.body === undefined
|
|
184
|
+
? undefined
|
|
185
|
+
: toNapiBuffer(response.body),
|
|
170
186
|
};
|
|
171
187
|
};
|
|
172
188
|
}
|
|
173
189
|
|
|
190
|
+
function fromNapiHttpResponseBodyStream(
|
|
191
|
+
stream: NativeHttpResponseBodyStream,
|
|
192
|
+
): RuntimeHttpResponseBodyStream {
|
|
193
|
+
return {
|
|
194
|
+
cancelled: () => stream.cancelled(),
|
|
195
|
+
write: (chunk) => stream.write(Buffer.from(chunk)),
|
|
196
|
+
end: () => stream.end(),
|
|
197
|
+
error: (message) => stream.error(message),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
174
201
|
function toNapiHttpRequest(
|
|
175
202
|
request?: RuntimeHttpRequest | undefined | null,
|
|
176
203
|
): Parameters<NativeActorContext["connectConn"]>[1] {
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import type { ResponseLike } from "@/common/fetch-like";
|
|
2
|
+
import { HEADER_ORIGINAL_REQUEST_URL } from "@/common/actor-router-consts";
|
|
3
|
+
import { stringifyError } from "@/common/utils";
|
|
4
|
+
import { logger } from "./log";
|
|
5
|
+
import type { RuntimeBytes, RuntimeHttpResponse } from "./runtime";
|
|
6
|
+
|
|
7
|
+
const HTTP_BODY_CHUNK_SIZE = 64 * 1024;
|
|
8
|
+
|
|
9
|
+
// Structural types keep the HTTP adapter independent of the N-API package.
|
|
10
|
+
export interface NativeHttpResponseBodyStream {
|
|
11
|
+
cancelled(): Promise<void>;
|
|
12
|
+
write(chunk: Uint8Array): Promise<void>;
|
|
13
|
+
end(): Promise<void>;
|
|
14
|
+
error(message: string): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface NativeHttpRequestBodyStream {
|
|
18
|
+
read(): Promise<Uint8Array | null | undefined>;
|
|
19
|
+
cancel(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface NativeHttpRequestInit {
|
|
23
|
+
method: string;
|
|
24
|
+
uri: string;
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
body?: RuntimeBytes;
|
|
27
|
+
bodyStream?: NativeHttpRequestBodyStream;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
abortController?: AbortController;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface NativeHttpResponseConversion {
|
|
33
|
+
response: RuntimeHttpResponse;
|
|
34
|
+
// Keeps request-scoped actor resources alive until the response body closes.
|
|
35
|
+
bodyCompletion?: Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function runtimeBytesToArrayBuffer(value: RuntimeBytes): ArrayBuffer {
|
|
39
|
+
return value.buffer.slice(
|
|
40
|
+
value.byteOffset,
|
|
41
|
+
value.byteOffset + value.byteLength,
|
|
42
|
+
) as ArrayBuffer;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function buildNativeHttpRequest(init: NativeHttpRequestInit): Request {
|
|
46
|
+
const headers = new Headers(init.headers);
|
|
47
|
+
const originalUrl = headers.get(HEADER_ORIGINAL_REQUEST_URL);
|
|
48
|
+
headers.delete(HEADER_ORIGINAL_REQUEST_URL);
|
|
49
|
+
const url =
|
|
50
|
+
originalUrl ??
|
|
51
|
+
(init.uri.startsWith("http")
|
|
52
|
+
? init.uri
|
|
53
|
+
: new URL(init.uri, "http://127.0.0.1").toString());
|
|
54
|
+
const method = init.method.toUpperCase();
|
|
55
|
+
const bodyForbidden = method === "GET" || method === "HEAD";
|
|
56
|
+
const body = bodyForbidden
|
|
57
|
+
? undefined
|
|
58
|
+
: init.bodyStream
|
|
59
|
+
? new ReadableStream<Uint8Array>({
|
|
60
|
+
async pull(controller) {
|
|
61
|
+
try {
|
|
62
|
+
if (init.body && init.body.length > 0) {
|
|
63
|
+
controller.enqueue(new Uint8Array(init.body));
|
|
64
|
+
init.body = undefined;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const chunk = await init.bodyStream?.read();
|
|
68
|
+
if (!chunk) {
|
|
69
|
+
controller.close();
|
|
70
|
+
} else {
|
|
71
|
+
controller.enqueue(new Uint8Array(chunk));
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
init.abortController?.abort(error);
|
|
75
|
+
controller.error(error);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
async cancel() {
|
|
79
|
+
await init.bodyStream?.cancel();
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
: init.body && init.body.length > 0
|
|
83
|
+
? runtimeBytesToArrayBuffer(init.body)
|
|
84
|
+
: undefined;
|
|
85
|
+
const streamInit =
|
|
86
|
+
init.bodyStream && !bodyForbidden ? { duplex: "half" } : {};
|
|
87
|
+
return new Request(url, {
|
|
88
|
+
method,
|
|
89
|
+
headers,
|
|
90
|
+
body,
|
|
91
|
+
signal: init.abortController?.signal ?? init.signal,
|
|
92
|
+
...streamInit,
|
|
93
|
+
} as RequestInit);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function cancelNativeHttpRequestBody(
|
|
97
|
+
bodyStream?: NativeHttpRequestBodyStream,
|
|
98
|
+
) {
|
|
99
|
+
await bodyStream?.cancel();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function writeResponseChunk(
|
|
103
|
+
stream: NativeHttpResponseBodyStream,
|
|
104
|
+
chunk: Uint8Array,
|
|
105
|
+
) {
|
|
106
|
+
for (
|
|
107
|
+
let offset = 0;
|
|
108
|
+
offset < chunk.byteLength;
|
|
109
|
+
offset += HTTP_BODY_CHUNK_SIZE
|
|
110
|
+
) {
|
|
111
|
+
await stream.write(
|
|
112
|
+
chunk.subarray(offset, offset + HTTP_BODY_CHUNK_SIZE),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function pumpResponseBody(
|
|
118
|
+
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
119
|
+
stream: NativeHttpResponseBodyStream,
|
|
120
|
+
) {
|
|
121
|
+
// Awaiting each write applies native-side backpressure. Racing the next read
|
|
122
|
+
// against cancellation also stops an otherwise unbounded SSE response when
|
|
123
|
+
// its downstream receiver disappears.
|
|
124
|
+
const cancelled = stream
|
|
125
|
+
.cancelled()
|
|
126
|
+
.then(() => ({ cancelled: true }) as const);
|
|
127
|
+
try {
|
|
128
|
+
for (;;) {
|
|
129
|
+
const read = reader
|
|
130
|
+
.read()
|
|
131
|
+
.then((next) => ({ cancelled: false, next }) as const);
|
|
132
|
+
const result = await Promise.race([read, cancelled]);
|
|
133
|
+
if (result.cancelled) {
|
|
134
|
+
// Web Streams closes pending reads before waiting for the source's cancel
|
|
135
|
+
// callback. Do not let an arbitrary user cancel promise retain the native
|
|
136
|
+
// request and connection cleanup indefinitely.
|
|
137
|
+
void reader
|
|
138
|
+
.cancel(
|
|
139
|
+
new Error(
|
|
140
|
+
"native http response stream receiver dropped",
|
|
141
|
+
),
|
|
142
|
+
)
|
|
143
|
+
.catch((error) => {
|
|
144
|
+
logger().debug({
|
|
145
|
+
msg: "failed to cancel native http response reader",
|
|
146
|
+
error,
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const { next } = result;
|
|
152
|
+
if (next.done) {
|
|
153
|
+
await stream.end();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (next.value?.byteLength) {
|
|
157
|
+
await writeResponseChunk(stream, next.value);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
try {
|
|
162
|
+
await stream.error(stringifyError(error));
|
|
163
|
+
} catch (streamError) {
|
|
164
|
+
logger().debug({
|
|
165
|
+
msg: "failed to report native http response stream error",
|
|
166
|
+
error: streamError,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
void reader.cancel(error).catch(() => {
|
|
170
|
+
// Reader may already be closed after a native-side disconnect.
|
|
171
|
+
});
|
|
172
|
+
} finally {
|
|
173
|
+
try {
|
|
174
|
+
reader.releaseLock();
|
|
175
|
+
} catch (error) {
|
|
176
|
+
logger().debug({
|
|
177
|
+
msg: "failed to release native http response reader lock",
|
|
178
|
+
error,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function convertNativeHttpResponse(
|
|
185
|
+
response: ResponseLike,
|
|
186
|
+
responseBodyStream?: NativeHttpResponseBodyStream,
|
|
187
|
+
): Promise<NativeHttpResponseConversion> {
|
|
188
|
+
const headers = Object.fromEntries(response.headers.entries());
|
|
189
|
+
if (!response.body) {
|
|
190
|
+
return {
|
|
191
|
+
response: {
|
|
192
|
+
status: response.status,
|
|
193
|
+
headers,
|
|
194
|
+
body: new Uint8Array(),
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (responseBodyStream) {
|
|
200
|
+
const reader = response.body.getReader();
|
|
201
|
+
const bodyCompletion = pumpResponseBody(reader, responseBodyStream);
|
|
202
|
+
return {
|
|
203
|
+
response: {
|
|
204
|
+
status: response.status,
|
|
205
|
+
headers,
|
|
206
|
+
stream: true,
|
|
207
|
+
},
|
|
208
|
+
bodyCompletion,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
response: {
|
|
214
|
+
status: response.status,
|
|
215
|
+
headers,
|
|
216
|
+
body: new Uint8Array(await response.arrayBuffer()),
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export const nativeHttpTestInternals = {
|
|
222
|
+
buildRequest: buildNativeHttpRequest,
|
|
223
|
+
cancelRequestBody: cancelNativeHttpRequestBody,
|
|
224
|
+
convertRuntimeHttpResponse: convertNativeHttpResponse,
|
|
225
|
+
};
|