rivetkit 2.3.12-rc.3 → 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.
Files changed (109) hide show
  1. package/dist/browser/client.d.ts +17 -2
  2. package/dist/browser/client.js +32 -24
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +2 -1
  5. package/dist/browser/inspector/client.js.map +1 -1
  6. package/dist/tsup/actor/errors.cjs +2 -2
  7. package/dist/tsup/actor/errors.js +1 -1
  8. package/dist/tsup/agent-os/index.cjs +2 -1
  9. package/dist/tsup/agent-os/index.cjs.map +1 -1
  10. package/dist/tsup/agent-os/index.d.cts +17 -2
  11. package/dist/tsup/agent-os/index.d.ts +17 -2
  12. package/dist/tsup/agent-os/index.js +2 -1
  13. package/dist/tsup/agent-os/index.js.map +1 -1
  14. package/dist/tsup/{chunk-SPJORZMM.cjs → chunk-2JSYYH6P.cjs} +1 -1
  15. package/dist/tsup/chunk-2JSYYH6P.cjs.map +1 -0
  16. package/dist/tsup/{chunk-IJWTHKH6.cjs → chunk-AN7WO7BM.cjs} +3 -3
  17. package/dist/tsup/{chunk-IJWTHKH6.cjs.map → chunk-AN7WO7BM.cjs.map} +1 -1
  18. package/dist/tsup/{chunk-PSXMESQJ.js → chunk-EBXARMGI.js} +2 -2
  19. package/dist/tsup/{chunk-O6H3BUN4.cjs → chunk-FGQABMXZ.cjs} +3 -3
  20. package/dist/tsup/{chunk-O6H3BUN4.cjs.map → chunk-FGQABMXZ.cjs.map} +1 -1
  21. package/dist/tsup/{chunk-WVFTFWJL.js → chunk-GEN4NHXM.js} +2 -2
  22. package/dist/tsup/{chunk-XI4MEUUI.js → chunk-JYTXOMM7.js} +2 -2
  23. package/dist/tsup/{chunk-OX6WVQE6.cjs → chunk-MEF7RVQ7.cjs} +10 -10
  24. package/dist/tsup/{chunk-OX6WVQE6.cjs.map → chunk-MEF7RVQ7.cjs.map} +1 -1
  25. package/dist/tsup/{chunk-OLGQYFUW.js → chunk-MF42PRF2.js} +9 -3
  26. package/dist/tsup/chunk-MF42PRF2.js.map +1 -0
  27. package/dist/tsup/{chunk-IXBD7BXC.js → chunk-OUQUIBVW.js} +1 -1
  28. package/dist/tsup/chunk-OUQUIBVW.js.map +1 -0
  29. package/dist/tsup/{chunk-QLOBB27M.cjs → chunk-PW2VSGUR.cjs} +4 -4
  30. package/dist/tsup/{chunk-QLOBB27M.cjs.map → chunk-PW2VSGUR.cjs.map} +1 -1
  31. package/dist/tsup/{chunk-NHI5L7C7.js → chunk-RBXJWAJB.js} +3 -3
  32. package/dist/tsup/{chunk-UBB32UOG.cjs → chunk-SSPTEGTX.cjs} +7 -7
  33. package/dist/tsup/{chunk-UBB32UOG.cjs.map → chunk-SSPTEGTX.cjs.map} +1 -1
  34. package/dist/tsup/{chunk-4ESGNS3Z.cjs → chunk-TAG6UEL5.cjs} +17 -11
  35. package/dist/tsup/chunk-TAG6UEL5.cjs.map +1 -0
  36. package/dist/tsup/{chunk-EQKSWDIH.cjs → chunk-TXUHVBKH.cjs} +3 -3
  37. package/dist/tsup/{chunk-EQKSWDIH.cjs.map → chunk-TXUHVBKH.cjs.map} +1 -1
  38. package/dist/tsup/{chunk-PZBWSCQR.cjs → chunk-TZPNSOG2.cjs} +203 -195
  39. package/dist/tsup/chunk-TZPNSOG2.cjs.map +1 -0
  40. package/dist/tsup/{chunk-YYIDQBBM.js → chunk-VOYNLY57.js} +41 -33
  41. package/dist/tsup/chunk-VOYNLY57.js.map +1 -0
  42. package/dist/tsup/{chunk-UN5JNDBT.js → chunk-XUODFCRJ.js} +4 -4
  43. package/dist/tsup/{chunk-34Q4YVCX.js → chunk-ZIPXAVKR.js} +2 -2
  44. package/dist/tsup/client/mod.cjs +6 -6
  45. package/dist/tsup/client/mod.d.cts +2 -2
  46. package/dist/tsup/client/mod.d.ts +2 -2
  47. package/dist/tsup/client/mod.js +5 -5
  48. package/dist/tsup/common/log.cjs +3 -3
  49. package/dist/tsup/common/log.js +2 -2
  50. package/dist/tsup/common/websocket.cjs +4 -4
  51. package/dist/tsup/common/websocket.js +3 -3
  52. package/dist/tsup/{config-Bi1r6gI7.d.ts → config-BGTanB_3.d.ts} +18 -2
  53. package/dist/tsup/{config-BXSV17G7.d.cts → config-NHz6oMb9.d.cts} +18 -2
  54. package/dist/tsup/{context-4fJemY0D.d.cts → context-DB8zG_Ib.d.cts} +1 -1
  55. package/dist/tsup/{context-D4sNYboo.d.ts → context-SHomdVV8.d.ts} +1 -1
  56. package/dist/tsup/db/drizzle.cjs +3 -3
  57. package/dist/tsup/db/drizzle.js +2 -2
  58. package/dist/tsup/db/mod.cjs +4 -4
  59. package/dist/tsup/db/mod.js +3 -3
  60. package/dist/tsup/dynamic/mod.d.cts +1 -1
  61. package/dist/tsup/dynamic/mod.d.ts +1 -1
  62. package/dist/tsup/inspector/mod.cjs +6 -6
  63. package/dist/tsup/inspector/mod.js +5 -5
  64. package/dist/tsup/inspector/workflow.cjs +5 -5
  65. package/dist/tsup/inspector/workflow.js +4 -4
  66. package/dist/tsup/inspector-tab/mod.d.cts +2 -2
  67. package/dist/tsup/inspector-tab/mod.d.ts +2 -2
  68. package/dist/tsup/mod.cjs +423 -177
  69. package/dist/tsup/mod.cjs.map +1 -1
  70. package/dist/tsup/mod.d.cts +3 -3
  71. package/dist/tsup/mod.d.ts +3 -3
  72. package/dist/tsup/mod.js +322 -76
  73. package/dist/tsup/mod.js.map +1 -1
  74. package/dist/tsup/test/mod.cjs +7 -7
  75. package/dist/tsup/test/mod.d.cts +1 -1
  76. package/dist/tsup/test/mod.d.ts +1 -1
  77. package/dist/tsup/test/mod.js +5 -5
  78. package/dist/tsup/utils.cjs +3 -3
  79. package/dist/tsup/utils.js +2 -2
  80. package/dist/tsup/workflow/mod.cjs +11 -11
  81. package/dist/tsup/workflow/mod.d.cts +3 -3
  82. package/dist/tsup/workflow/mod.d.ts +3 -3
  83. package/dist/tsup/workflow/mod.js +5 -5
  84. package/package.json +9 -8
  85. package/src/actor/errors.ts +2 -1
  86. package/src/client/actor-handle.ts +10 -3
  87. package/src/client/raw-utils.ts +8 -0
  88. package/src/common/actor-router-consts.ts +2 -0
  89. package/src/engine-client/actor-http-client.ts +6 -22
  90. package/src/engine-client/mod.ts +1 -1
  91. package/src/registry/config/index.ts +27 -1
  92. package/src/registry/index.ts +48 -14
  93. package/src/registry/napi-runtime.ts +32 -5
  94. package/src/registry/native-http.ts +225 -0
  95. package/src/registry/native.ts +130 -64
  96. package/src/registry/runtime.ts +30 -2
  97. package/src/utils/env-vars.ts +4 -0
  98. package/dist/tsup/chunk-4ESGNS3Z.cjs.map +0 -1
  99. package/dist/tsup/chunk-IXBD7BXC.js.map +0 -1
  100. package/dist/tsup/chunk-OLGQYFUW.js.map +0 -1
  101. package/dist/tsup/chunk-PZBWSCQR.cjs.map +0 -1
  102. package/dist/tsup/chunk-SPJORZMM.cjs.map +0 -1
  103. package/dist/tsup/chunk-YYIDQBBM.js.map +0 -1
  104. /package/dist/tsup/{chunk-PSXMESQJ.js.map → chunk-EBXARMGI.js.map} +0 -0
  105. /package/dist/tsup/{chunk-WVFTFWJL.js.map → chunk-GEN4NHXM.js.map} +0 -0
  106. /package/dist/tsup/{chunk-XI4MEUUI.js.map → chunk-JYTXOMM7.js.map} +0 -0
  107. /package/dist/tsup/{chunk-NHI5L7C7.js.map → chunk-RBXJWAJB.js.map} +0 -0
  108. /package/dist/tsup/{chunk-UN5JNDBT.js.map → chunk-XUODFCRJ.js.map} +0 -0
  109. /package/dist/tsup/{chunk-34Q4YVCX.js.map → chunk-ZIPXAVKR.js.map} +0 -0
@@ -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
- ...request,
165
- body: new Uint8Array(request.body),
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: toNapiBuffer(response.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
+ };
@@ -57,7 +57,7 @@ import {
57
57
  } from "@/common/database/mod";
58
58
  import { wrapJsNativeDatabase } from "@/common/database/native-database";
59
59
  import { assertJsonCompatValue, type JsonCompatValue } from "@/common/encoding";
60
- import { isResponseLike, type ResponseLike } from "@/common/fetch-like";
60
+ import { isResponseLike } from "@/common/fetch-like";
61
61
  import {
62
62
  decodeWorkflowHistoryTransport,
63
63
  type WorkflowHistoryBytes,
@@ -85,6 +85,13 @@ import {
85
85
  } from "@/utils/node";
86
86
  import { logger } from "./log";
87
87
  import { loadNapiRuntime } from "./napi-runtime";
88
+ import {
89
+ buildNativeHttpRequest,
90
+ cancelNativeHttpRequestBody,
91
+ convertNativeHttpResponse,
92
+ type NativeHttpRequestBodyStream,
93
+ type NativeHttpResponseBodyStream,
94
+ } from "./native-http";
88
95
  import {
89
96
  type NativeValidationConfig,
90
97
  validateActionArgs,
@@ -105,7 +112,6 @@ import type {
105
112
  RuntimeBytes,
106
113
  RuntimeCronFire,
107
114
  RuntimeCronJobInfo,
108
- RuntimeHttpResponse,
109
115
  RuntimeInspectorTabEntry,
110
116
  RuntimeQueueMessage,
111
117
  RuntimeScheduledEventInfo,
@@ -746,6 +752,12 @@ async function loadEngineCli(): Promise<typeof import("@rivetkit/engine-cli")> {
746
752
  return import(["@rivetkit", "engine-cli"].join("/"));
747
753
  }
748
754
 
755
+ async function loadServices(): Promise<typeof import("@rivet-dev/services")> {
756
+ // Keep the native binary opaque to edge bundlers for the same reason as
757
+ // engine-cli above.
758
+ return import(["@rivet-dev", "services"].join("/"));
759
+ }
760
+
749
761
  function decodeValue<T>(value?: RuntimeBytes | null): T {
750
762
  if (!value || value.length === 0) {
751
763
  return undefined as T;
@@ -1250,38 +1262,6 @@ function decodeArgs(value?: RuntimeBytes | null): unknown[] {
1250
1262
  return normalizeArgs(decoded);
1251
1263
  }
1252
1264
 
1253
- function buildRequest(init: {
1254
- method: string;
1255
- uri: string;
1256
- headers?: Record<string, string>;
1257
- body?: RuntimeBytes;
1258
- }): Request {
1259
- const url = init.uri.startsWith("http")
1260
- ? init.uri
1261
- : new URL(init.uri, "http://127.0.0.1").toString();
1262
- const body =
1263
- init.body && init.body.length > 0
1264
- ? runtimeBytesToArrayBuffer(init.body)
1265
- : undefined;
1266
- return new Request(url, {
1267
- method: init.method,
1268
- headers: init.headers,
1269
- body,
1270
- });
1271
- }
1272
-
1273
- async function toRuntimeHttpResponse(
1274
- response: ResponseLike,
1275
- ): Promise<RuntimeHttpResponse> {
1276
- const headers = Object.fromEntries(response.headers.entries());
1277
- const body = new Uint8Array(await response.arrayBuffer());
1278
- return {
1279
- status: response.status,
1280
- headers,
1281
- body,
1282
- };
1283
- }
1284
-
1285
1265
  function toActorKey(
1286
1266
  segments: Array<{
1287
1267
  kind: string;
@@ -4068,19 +4048,22 @@ export function buildNativeFactory(
4068
4048
  );
4069
4049
  const maybeHandleNativeInspectorRequest = async (
4070
4050
  ctx: ActorContextHandle,
4071
- _rawRequest: {
4051
+ rawRequest: {
4072
4052
  method: string;
4073
4053
  uri: string;
4074
4054
  headers?: Record<string, string>;
4075
4055
  body?: RuntimeBytes;
4076
4056
  },
4077
- jsRequest: Request,
4078
4057
  ): Promise<Response | undefined> => {
4079
- const url = new URL(jsRequest.url);
4058
+ const rawUrl = rawRequest.uri.startsWith("http")
4059
+ ? rawRequest.uri
4060
+ : new URL(rawRequest.uri, "http://127.0.0.1").toString();
4061
+ const url = new URL(rawUrl);
4080
4062
  if (!url.pathname.startsWith("/inspector/")) {
4081
4063
  return undefined;
4082
4064
  }
4083
4065
 
4066
+ const jsRequest = buildNativeHttpRequest(rawRequest);
4084
4067
  const jsonResponse = (body: unknown, init?: ResponseInit) =>
4085
4068
  new Response(JSON.stringify(body), {
4086
4069
  status: init?.status ?? 200,
@@ -4772,7 +4755,9 @@ export function buildNativeFactory(
4772
4755
  );
4773
4756
  const actorCtx = makeActorCtx(
4774
4757
  ctx,
4775
- request ? buildRequest(request) : undefined,
4758
+ request
4759
+ ? buildNativeHttpRequest(request)
4760
+ : undefined,
4776
4761
  );
4777
4762
  try {
4778
4763
  await config.onBeforeConnect(
@@ -4809,7 +4794,9 @@ export function buildNativeFactory(
4809
4794
  unwrapTsfnPayload(error, payload);
4810
4795
  const actorCtx = makeActorCtx(
4811
4796
  ctx,
4812
- request ? buildRequest(request) : undefined,
4797
+ request
4798
+ ? buildNativeHttpRequest(request)
4799
+ : undefined,
4813
4800
  );
4814
4801
  const connAdapter = new NativeConnAdapter(
4815
4802
  runtime,
@@ -4865,7 +4852,9 @@ export function buildNativeFactory(
4865
4852
  );
4866
4853
  const actorCtx = makeActorCtx(
4867
4854
  ctx,
4868
- request ? buildRequest(request) : undefined,
4855
+ request
4856
+ ? buildNativeHttpRequest(request)
4857
+ : undefined,
4869
4858
  );
4870
4859
  const connAdapter = new NativeConnAdapter(
4871
4860
  runtime,
@@ -5017,38 +5006,63 @@ export function buildNativeFactory(
5017
5006
  uri: string;
5018
5007
  headers?: Record<string, string>;
5019
5008
  body?: RuntimeBytes;
5009
+ bodyStream?: NativeHttpRequestBodyStream;
5020
5010
  };
5021
5011
  cancelToken?: CancellationTokenHandle;
5012
+ responseBodyStream?: NativeHttpResponseBodyStream;
5022
5013
  },
5023
5014
  ) => {
5024
5015
  try {
5025
- const { ctx, request, cancelToken } = unwrapTsfnPayload(
5026
- error,
5027
- payload,
5028
- );
5029
- const jsRequest = buildRequest(request);
5016
+ const { ctx, request, cancelToken, responseBodyStream } =
5017
+ unwrapTsfnPayload(error, payload);
5030
5018
  const inspectorResponse =
5031
- await maybeHandleNativeInspectorRequest(
5032
- ctx,
5033
- request,
5034
- jsRequest,
5035
- );
5019
+ await maybeHandleNativeInspectorRequest(ctx, request);
5036
5020
  if (inspectorResponse) {
5037
- return await toRuntimeHttpResponse(inspectorResponse);
5021
+ await cancelNativeHttpRequestBody(request.bodyStream);
5022
+ return (
5023
+ await convertNativeHttpResponse(
5024
+ inspectorResponse,
5025
+ responseBodyStream,
5026
+ )
5027
+ ).response;
5038
5028
  }
5039
5029
 
5040
5030
  if (typeof config.onRequest !== "function") {
5041
- return await toRuntimeHttpResponse(
5042
- new Response(null, { status: 404 }),
5043
- );
5031
+ await cancelNativeHttpRequestBody(request.bodyStream);
5032
+ return (
5033
+ await convertNativeHttpResponse(
5034
+ new Response(null, { status: 404 }),
5035
+ responseBodyStream,
5036
+ )
5037
+ ).response;
5044
5038
  }
5045
5039
 
5040
+ const requestAbortController = new AbortController();
5041
+ const handlerRequest = buildNativeHttpRequest({
5042
+ ...request,
5043
+ abortController: requestAbortController,
5044
+ });
5046
5045
  const rawConnParams =
5047
- jsRequest.headers.get(HEADER_CONN_PARAMS);
5046
+ handlerRequest.headers.get(HEADER_CONN_PARAMS);
5048
5047
  let requestCtx:
5049
5048
  | ReturnType<typeof withConnContext>
5050
5049
  | undefined;
5051
5050
  let conn: ConnHandle | undefined;
5051
+ let removeRequestAbortListener: (() => void) | undefined;
5052
+ let cleanupDeferredToBody = false;
5053
+ let cleanedUp = false;
5054
+ const cleanupRequest = async () => {
5055
+ if (cleanedUp) return;
5056
+ cleanedUp = true;
5057
+ removeRequestAbortListener?.();
5058
+ try {
5059
+ await requestCtx?.dispose();
5060
+ } finally {
5061
+ if (conn) {
5062
+ await runtime.connDisconnect(conn);
5063
+ }
5064
+ }
5065
+ };
5052
5066
  try {
5053
5067
  const connParams = validateConnParams(
5054
5068
  schemaConfig.connParamsSchema,
@@ -5066,23 +5080,62 @@ export function buildNativeFactory(
5066
5080
  requestCtx = makeConnCtx(
5067
5081
  ctx,
5068
5082
  conn,
5069
- jsRequest,
5083
+ handlerRequest,
5070
5084
  cancelToken,
5071
5085
  );
5086
+ const ctxAbortSignal = requestCtx.abortSignal;
5087
+ const abortRequest = () =>
5088
+ requestAbortController.abort(ctxAbortSignal.reason);
5089
+ if (ctxAbortSignal.aborted) {
5090
+ abortRequest();
5091
+ } else {
5092
+ ctxAbortSignal.addEventListener(
5093
+ "abort",
5094
+ abortRequest,
5095
+ { once: true },
5096
+ );
5097
+ removeRequestAbortListener = () =>
5098
+ ctxAbortSignal.removeEventListener(
5099
+ "abort",
5100
+ abortRequest,
5101
+ );
5102
+ }
5072
5103
  const response = await config.onRequest(
5073
5104
  requestCtx,
5074
- jsRequest,
5105
+ handlerRequest,
5075
5106
  );
5076
5107
  if (!isResponseLike(response)) {
5077
5108
  throw new Error(
5078
5109
  "onRequest handler must return a Response",
5079
5110
  );
5080
5111
  }
5081
- return await toRuntimeHttpResponse(response);
5112
+ const conversion = await convertNativeHttpResponse(
5113
+ response,
5114
+ responseBodyStream,
5115
+ );
5116
+ if (conversion.bodyCompletion) {
5117
+ cleanupDeferredToBody = true;
5118
+ void conversion.bodyCompletion
5119
+ .then(cleanupRequest)
5120
+ .catch((cleanupError) => {
5121
+ logger().error({
5122
+ msg: "failed to clean up native streaming http request",
5123
+ error: cleanupError,
5124
+ });
5125
+ });
5126
+ }
5127
+ return conversion.response;
5082
5128
  } finally {
5083
- await requestCtx?.dispose();
5084
- if (conn) {
5085
- await runtime.connDisconnect(conn);
5129
+ try {
5130
+ // Handler completion ends upload ownership even when
5131
+ // the Web Request body is locked or partly consumed.
5132
+ await cancelNativeHttpRequestBody(
5133
+ request.bodyStream,
5134
+ );
5135
+ } finally {
5136
+ if (!cleanupDeferredToBody) {
5137
+ await cleanupRequest();
5138
+ }
5086
5139
  }
5087
5140
  }
5088
5141
  } catch (error) {
@@ -5114,7 +5167,7 @@ export function buildNativeFactory(
5114
5167
  const { ctx, conn, ws, request } =
5115
5168
  unwrapTsfnPayload(error, payload);
5116
5169
  const jsRequest = request
5117
- ? buildRequest(request)
5170
+ ? buildNativeHttpRequest(request)
5118
5171
  : undefined;
5119
5172
  const actorCtx = makeConnCtx(ctx, conn, jsRequest);
5120
5173
  try {
@@ -5281,7 +5334,7 @@ export function buildNativeFactory(
5281
5334
  timeoutMs,
5282
5335
  cancelToken,
5283
5336
  } = unwrapTsfnPayload(error, payload);
5284
- const jsRequest = buildRequest(request);
5337
+ const jsRequest = buildNativeHttpRequest(request);
5285
5338
  const actorCtx = withConnContext(
5286
5339
  runtime,
5287
5340
  ctx,
@@ -5380,6 +5433,7 @@ export function buildNativeFactory(
5380
5433
 
5381
5434
  export async function buildServeConfig(
5382
5435
  config: RegistryConfig,
5436
+ resolveServicesBinary = true,
5383
5437
  ): Promise<RuntimeServeConfig> {
5384
5438
  if (!config.endpoint) {
5385
5439
  throw nativeEndpointNotConfiguredError();
@@ -5391,6 +5445,7 @@ export async function buildServeConfig(
5391
5445
  token: config.token,
5392
5446
  namespace: config.namespace,
5393
5447
  poolName: config.envoy.poolName,
5448
+ startServices: resolveServicesBinary && config.startServices,
5394
5449
  handleInspectorHttpInRuntime: true,
5395
5450
  serverlessBasePath: config.serverless.basePath,
5396
5451
  serverlessPackageVersion: VERSION,
@@ -5422,6 +5477,17 @@ export async function buildServeConfig(
5422
5477
  error: stringifyError(error),
5423
5478
  });
5424
5479
  }
5480
+ if (resolveServicesBinary && config.startServices) {
5481
+ try {
5482
+ const { getServicesPath } = await loadServices();
5483
+ serveConfig.servicesBinaryPath = getServicesPath();
5484
+ } catch (error) {
5485
+ logger().warn({
5486
+ msg: "could not resolve the Services binary; a local engine will fail with services.binary_unavailable — set RIVET_RUN_SERVICES=0 to disable Services or RIVET_SERVICES_BINARY to a local rivet-services binary",
5487
+ error: stringifyError(error),
5488
+ });
5489
+ }
5490
+ }
5425
5491
  serveConfig.engineHost = config.engineHost;
5426
5492
  serveConfig.enginePort = config.enginePort;
5427
5493
  if (config.test?.enabled) {
@@ -5467,7 +5533,7 @@ export async function buildRegistryWithRuntime(
5467
5533
  return {
5468
5534
  runtime,
5469
5535
  registry,
5470
- serveConfig: await buildServeConfig(config),
5536
+ serveConfig: await buildServeConfig(config, runtime.kind === "napi"),
5471
5537
  };
5472
5538
  }
5473
5539