diff --git a/packages/libsql-client/src/__tests__/uri.test.ts b/packages/libsql-client/src/__tests__/uri.test.ts index 6ea18b6..71dc2f9 100644 --- a/packages/libsql-client/src/__tests__/uri.test.ts +++ b/packages/libsql-client/src/__tests__/uri.test.ts @@ -216,13 +216,13 @@ test("encodeBaseUrl()", () => { scheme: "http", host: "localhost", path: "/foo/bar", - url: "http://localhost/foo/bar", + url: "http://localhost/foo/bar/", }, { scheme: "http", host: "localhost", path: "foo/bar", - url: "http://localhost/foo/bar", + url: "http://localhost/foo/bar/", }, { scheme: "http", @@ -262,7 +262,7 @@ test("encodeBaseUrl()", () => { userinfo: { username: "alice", password: "secret" }, port: 8080, path: "/some/path", - url: "https://alice:secret@localhost:8080/some/path", + url: "https://alice:secret@localhost:8080/some/path/", }, ]; @@ -272,3 +272,27 @@ test("encodeBaseUrl()", () => { ).toStrictEqual(new URL(url)); } }); + +test("encodeBaseUrl() keeps the full path when the Hrana client resolves an endpoint against it", () => { + // Regression for #296: a deployment proxied under a sub-path lost its last + // path segment because `new URL("v2/pipeline", base)` drops it unless the + // base path ends with "/". + const authority = { + host: "example.com", + port: undefined, + userinfo: undefined, + }; + + const base = encodeBaseUrl("https", authority, "/some/sub/path"); + expect(new URL("v2/pipeline", base).href).toBe( + "https://example.com/some/sub/path/v2/pipeline", + ); + expect(new URL("v3-protobuf/pipeline", base).href).toBe( + "https://example.com/some/sub/path/v3-protobuf/pipeline", + ); + + const root = encodeBaseUrl("https", authority, ""); + expect(new URL("v2/pipeline", root).href).toBe( + "https://example.com/v2/pipeline", + ); +}); diff --git a/packages/libsql-core/src/uri.ts b/packages/libsql-core/src/uri.ts index 470df4e..203ee5d 100644 --- a/packages/libsql-core/src/uri.ts +++ b/packages/libsql-core/src/uri.ts @@ -176,6 +176,13 @@ export function encodeBaseUrl( if (pathText !== "" && !pathText.startsWith("/")) { pathText = "/" + pathText; } + // The Hrana client resolves endpoint paths ("v2/pipeline", ...) against this + // URL with `new URL(endpointPath, baseUrl)`, which replaces the last path + // segment unless the base path ends with "/". Keep the trailing slash so a + // deployment served under a proxied sub-path does not lose that segment. + if (pathText !== "" && !pathText.endsWith("/")) { + pathText += "/"; + } return new URL(`${schemeText}${authorityText}${pathText}`); }