Skip to content
This repository was archived by the owner on Oct 22, 2025. It is now read-only.

Commit eedbac6

Browse files
committed
chore(core): simplify appending paths (#1254)
1 parent 7c74870 commit eedbac6

File tree

5 files changed

+63
-4
lines changed

5 files changed

+63
-4
lines changed

packages/rivetkit/src/remote-manager-driver/actor-http-client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ClientConfig } from "@/client/config";
2+
import { combineUrlPath } from "@/utils";
23
import { getEndpoint } from "./api-utils";
34

45
export async function sendHttpRequestToActor(
@@ -9,7 +10,7 @@ export async function sendHttpRequestToActor(
910
// Route through guard port
1011
const url = new URL(actorRequest.url);
1112
const endpoint = getEndpoint(runConfig);
12-
const guardUrl = `${endpoint}${url.pathname}${url.search}`;
13+
const guardUrl = combineUrlPath(endpoint, url.pathname + url.search);
1314

1415
// Handle body properly based on method and presence
1516
let bodyToSend: ArrayBuffer | null = null;

packages/rivetkit/src/remote-manager-driver/actor-websocket-client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
import type { ClientConfig } from "@/client/config";
77
import { importWebSocket } from "@/common/websocket";
88
import type { Encoding, UniversalWebSocket } from "@/mod";
9+
import { combineUrlPath } from "@/utils";
910
import { getEndpoint } from "./api-utils";
1011
import { logger } from "./log";
1112

@@ -20,7 +21,7 @@ export async function openWebSocketToActor(
2021

2122
// WebSocket connections go through guard
2223
const endpoint = getEndpoint(runConfig);
23-
const guardUrl = `${endpoint}${path}`;
24+
const guardUrl = combineUrlPath(endpoint, path);
2425

2526
logger().debug({
2627
msg: "opening websocket to actor via guard",

packages/rivetkit/src/remote-manager-driver/api-utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ClientConfig } from "@/client/config";
22
import { sendHttpRequest } from "@/client/utils";
3+
import { combineUrlPath } from "@/utils";
34
import { logger } from "./log";
45

56
// Error class for Engine API errors
@@ -26,7 +27,9 @@ export async function apiCall<TInput = unknown, TOutput = unknown>(
2627
body?: TInput,
2728
): Promise<TOutput> {
2829
const endpoint = getEndpoint(config);
29-
const url = `${endpoint}${path}${path.includes("?") ? "&" : "?"}namespace=${encodeURIComponent(config.namespace)}`;
30+
const url = combineUrlPath(endpoint, path, {
31+
namespace: config.namespace,
32+
});
3033

3134
logger().debug({ msg: "making api call", method, url });
3235

packages/rivetkit/src/remote-manager-driver/mod.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
import type { ManagerInspector } from "@/inspector/manager";
1818
import type { Encoding, RegistryConfig, UniversalWebSocket } from "@/mod";
1919
import type { RunConfig } from "@/registry/run-config";
20+
import { combineUrlPath } from "@/utils";
2021
import { sendHttpRequestToActor } from "./actor-http-client";
2122
import {
2223
buildGuardHeadersForWebSocket,
@@ -246,7 +247,7 @@ export class RemoteManagerDriver implements ManagerDriver {
246247
invariant(upgradeWebSocket, "missing getUpgradeWebSocket");
247248

248249
const endpoint = getEndpoint(this.#config);
249-
const guardUrl = `${endpoint}${path}`;
250+
const guardUrl = combineUrlPath(endpoint, path);
250251
const wsGuardUrl = guardUrl.replace("http://", "ws://");
251252

252253
logger().debug({

packages/rivetkit/src/utils.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,56 @@ export function bufferToArrayBuffer(buf: Buffer | Uint8Array): ArrayBuffer {
170170
buf.byteOffset + buf.byteLength,
171171
) as ArrayBuffer;
172172
}
173+
174+
/**
175+
* Properly combines a base URL endpoint with a path, preserving any base path in the endpoint.
176+
*
177+
* @example
178+
* combineUrlPath("http://localhost:8787/rivet", "/actors/action")
179+
* // Returns: "http://localhost:8787/rivet/actors/action"
180+
*
181+
* @example
182+
* combineUrlPath("http://localhost:8787/rivet", "/actors?type=foo", { namespace: "test" })
183+
* // Returns: "http://localhost:8787/rivet/actors?type=foo&namespace=test"
184+
*
185+
* @param endpoint The base URL endpoint that may contain a path component
186+
* @param path The path to append to the endpoint (may include query parameters)
187+
* @param queryParams Optional additional query parameters to append
188+
* @returns The properly combined URL string
189+
*/
190+
export function combineUrlPath(
191+
endpoint: string,
192+
path: string,
193+
queryParams?: Record<string, string | undefined>,
194+
): string {
195+
const baseUrl = new URL(endpoint);
196+
197+
// Extract path and query from the provided path
198+
const pathParts = path.split("?");
199+
const pathOnly = pathParts[0];
200+
const existingQuery = pathParts[1] || "";
201+
202+
// Remove trailing slash from base path and ensure path starts with /
203+
const basePath = baseUrl.pathname.replace(/\/$/, "");
204+
const cleanPath = pathOnly.startsWith("/") ? pathOnly : `/${pathOnly}`;
205+
// Combine paths and remove any double slashes
206+
const fullPath = (basePath + cleanPath).replace(/\/\//g, "/");
207+
208+
// Build query string
209+
const queryParts: string[] = [];
210+
if (existingQuery) {
211+
queryParts.push(existingQuery);
212+
}
213+
if (queryParams) {
214+
for (const [key, value] of Object.entries(queryParams)) {
215+
if (value !== undefined) {
216+
queryParts.push(
217+
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
218+
);
219+
}
220+
}
221+
}
222+
223+
const fullQuery = queryParts.length > 0 ? `?${queryParts.join("&")}` : "";
224+
return `${baseUrl.protocol}//${baseUrl.host}${fullPath}${fullQuery}`;
225+
}

0 commit comments

Comments
 (0)