Skip to content

Dynamic client base URL and custom WebSocket topic field #148

Closed

Description

@present-g

Hi!

I have two questions regarding client configuration and WebSocket integration.

  1. Dynamic client base URL

According to the Client Setup documentation the URL is configured when creating the client:

const client = createClient({
  url: "https://api.example.com",
});

const getUsers = client.createRequest()({
  method: "GET",
  endpoint: "/users",
});

However, in my application, the API base URL can change at runtime (for example, when switching between servers or environments).

Ideally, I'd like to keep the same client and request instances, including their integrations with React hooks, while dynamically updating the base URL.

Questions:

  1. Is there a recommended way to update the client's base URL without recreating the client?
  2. Can the URL be provided dynamically, for example through a getter or callback evaluated when sending a request?
  3. If the URL changes, will previously created requests automatically use the new URL?
  4. How should caching and request deduplication behave when switching between different base URLs?

Expected behavior:

const client = createClient({
  url: () => config.apiUrl,
});

// Or alternatively:
client.setUrl("https://another-api.example.com");
  1. WebSocket: Custom topic field ("type" instead of "topic")

Our WebSocket backend uses the "type" field instead of "topic" for message routing.

For example:

{
  "type": "prices",
  "data": {
    "symbol": "BTCUSDT",
    "price": 67000
  }
}

Questions:

  1. Is it possible to configure HyperFetch Sockets to use "type" instead of "topic" for both outgoing and incoming messages?
  2. Can this mapping be customized without implementing an entirely new WebSocket adapter?
  3. Is there an existing recommended approach for integrating HyperFetch Sockets with backends that use different message envelope structures?

Ideally, I'd like to configure the field mapping once at the socket level, while keeping the standard listener API and React hooks fully functional.

Thanks in advance!

Activity

  1. present-g commented on Oct 20, 2026

    @present-g
    Author

    It is definitely not the best way to solve the dynamic URL issue, but it works. It would be great to find a more native solution.

    HTTP wrapper:

    import type { ClientInstance } from "@hyper-fetch/core";
    
    export type DynamicUrl = {
      getUrl: () => string;
      setUrl: (url: string) => void;
    };
    
    export const withDynamicUrl = <TClient extends ClientInstance>(client: TClient): TClient & DynamicUrl => {
      let url = client.url;
    
      Object.defineProperty(client, "url", { get: () => url, configurable: true });
    
      client.onRequest((request) => {
        if (!url) throw new Error("Base URL is not set: call setUrl() first");
        return request;
      });
    
      const setUrl = (next: string) => {
        if (next === url) return;
    
        const running = client.fetchDispatcher.getAllRunningRequests().map(({ request }) => request.cacheKey);
        client.requestManager.abortAll();
    
        url = next;
    
        client.cache.invalidate([/.*/, ...running]);
      };
    
      return Object.assign(client, { getUrl: () => url, setUrl });
    };
    export const apiClient = withDynamicUrl(createClient({ url: "", mode: "client" }));

    WS wrapper:

    import type { SocketInstance } from "@hyper-fetch/sockets";
    
    export type DynamicSocketUrl = {
      getUrl: () => string;
      /** Closes the current connection and opens a new one at `url`. Listeners stay subscribed. */
      setUrl: (url: string) => Promise<void>;
    };
    
    export const withDynamicSocketUrl = <TSocket extends SocketInstance>(socket: TSocket): TSocket & DynamicSocketUrl => {
      socket.onConnect(({ connection }) => {
        if (!connection.url) throw new Error("Socket URL is not set: call setUrl() first");
        return connection;
      });
    
      const setUrl = async (next: string) => {
        if (next === socket.url) return;
    
        socket.url = next;
    
        await socket.disconnect();
        if (next) await socket.connect();
      };
    
      return Object.assign(socket, { getUrl: () => socket.url, setUrl });
    };
    xport const apiSocket = withDynamicSocketUrl(new Socket({ url: "", adapter: TypeWebsocketAdapter }));

    Regarding the topic/type issue in WS.

    Proposal, add an optional codec to WebsocketAdapter

    type WebsocketCodec = {
      /** Incoming frame → listener event. Return `null` to ignore the frame (pings, unknown messages). */
      decode: (event: MessageEvent) => { topic: string; data: unknown } | null;
      /** Emitter message → frame passed to `websocket.send`. */
      encode: (message: { topic: string; payload: unknown }) => string | ArrayBufferLike | Blob | ArrayBufferView;
    };
    
    type WebsocketAdapterOptionsType = {
      // ...existing options
      codec?: WebsocketCodec;
    };
    
    const socket = new Socket({
      url,
      adapter: WebsocketAdapter,
      adapterOptions: {
        codec: {
          decode: (event) => {
            const msg = parseResponse(event.data);
            return { topic: msg.type, data: msg.payload };
          },
          encode: ({ topic, payload }) => JSON.stringify({ type: topic, payload }),
        },
      },
    });

    Of course, we can add a custom WS adapter, but it requires rewriting the entire setConnector. It would be great to be able to overwrite only a small part of the connector, rather than the entire logic.

  2. present-g commented on Oct 21, 2026

    @present-g
    Author

    @prc5, hi, could you take a look at this when you have a free moment?

  3. prc5 commented on Oct 21, 2026

    @prc5
    Contributor

    Amazing feedback, thank you! It is now being deployed 🫡
    Read about it here: #149

  4. github-actions commented on Oct 21, 2026

    @github-actions

    🎉 This issue has been resolved in version 9.2.0 🎉

    The release is available on:

    Your semantic-release bot 📦🚀

  5. present-g commented on Oct 21, 2026

    @present-g
    Author

    @prc5, awesome, thank you so much! You are the best, as usual!

  6. prc5 commented on Oct 22, 2026

    @prc5
    Contributor

    @present-g how is it looking from your side? Everything working as expected? 👀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions