@rzl-zone/utils-js
Version:
A modern, lightweight set of JavaScript utility functions with TypeScript support for everyday development, crafted to enhance code readability and maintainability.
232 lines (226 loc) • 12.1 kB
TypeScript
/*!
* ====================================================
* Rzl Utils-JS.
* ----------------------------------------------------
* Version: 3.11.0.
* Author: Rizalvin Dwiky.
* Repository: https://github.com/rzl-zone/utils-js.
* ====================================================
*/
import { IsAny } from '@rzl-zone/ts-types-plus';
/** ---------------------------------------------------------
* * ***Extracts dynamic route parameters from a given route string.***
* ---------------------------------------------------------
* **This utility type recursively searches for dynamic segments within a route,
* extracting each parameter and constructing an object where each key represents
* a dynamic segment and its value is of type `string`.**
* - ***⚠️ Warning:***
* - ***This types only support when using ***[`NextJS`](https://nextjs.org/)***.***
* @template T - The route string containing potential dynamic segments.
* @example
* ```ts
* type Params1 = ExtractRouteParams<"/user/[id]">;
* // ➔ { id: string }
* type Params2 = ExtractRouteParams<"/post/[slug]/comment/[commentId]">;
* // ➔ { slug: string; commentId: string }
* type Params3 = ExtractRouteParams<"/dashboard">;
* // ➔ {} (no dynamic parameters)
* ```
*/
type ExtractRouteParams<T> = T extends string ? HasDynamicSegments<T> extends true ? T extends `${infer _Start}[${infer Param}]${infer Rest}` ? {
[K in Param | keyof ExtractRouteParams<Rest>]: string;
} : unknown : unknown : unknown;
/** ---------------------------------------------------------
* * ***Determines whether a given route contains dynamic segments.***
* ---------------------------------------------------------
* **This type checks if the route includes at least one `[param]` pattern.
* If it does, the result is `true`, otherwise `false`.**
* - ***⚠️ Warning:***
* - ***This types only support when using ***[`NextJS`](https://nextjs.org/)***.***
* @template T - The route string to be evaluated.
* @example
* ```ts
* type HasParams1 = HasDynamicSegments<"/user/[id]">;
* // ➔ true
* type HasParams2 = HasDynamicSegments<"/settings/profile">;
* // ➔ false
* type HasParams3 = HasDynamicSegments<"/blog/[category]/[slug]">;
* // ➔ true
* ```
*/
type HasDynamicSegments<T> = T extends `${string}[${string}]${string}` ? true : false;
type GenerateRouteResult<T> = true extends IsAny<T> ? unknown : T extends string ? string : unknown;
/** ---------------------------------
* * ***Utility for NextJS: `generateRoute`.***
* ---------------------------------
* **Generates a URL by replacing dynamic route parameters with provided values.**
* - ***⚠️ Warning:***
* - ***This function only support when using ***[`NextJS`](https://nextjs.org/)***.***
* @template T - The route string containing dynamic segments in the format `[param]`.
* @param {T} route - The route string containing dynamic segments.
* @param {ExtractRouteParams<T>} [params] - An object containing key-value pairs that match the dynamic segments in the route.
* @returns {string} The formatted URL with all dynamic segments replaced.
* @throws **{@link Error | `Error`}** if the route contains dynamic segments but no parameters object is provided.
* @throws **{@link Error | `Error`}** if a required parameter is missing from the `params` object.
* @throws **{@link Error | `Error`}** if a parameter value is an empty string.
* @throws **{@link Error | `Error`}** if any parameter contains invalid characters like `?`, `&`, `=`, `#`, `/`, spaces, `'`, `"`, `(`, `)`, `+`, `;`, `%`, `@`, or `:`, which can cause URL issues.
* @example
* // Basic usage
* generateRoute("/user/[id]", { id: "123" });
* // ➔ "/user/123"
*
* // No dynamic segments, returns as-is
* generateRoute("/dashboard");
* // ➔ "/dashboard"
*
* // Throws an error due to missing parameters object
* generateRoute("/profile/[username]");
* // ➔ ❌ Error: ❌ Missing parameters object for route: "/profile/[username]"
*
* // Throws an error due to an empty parameter value
* generateRoute("/post/[category]/[slug]", { category: "tech", slug: "" });
* // ➔ ❌ Error: ❌ Parameter "slug" cannot be empty in route: "/post/[category]/[slug]"
*
* // Throws an error due to parameter containing invalid characters
* generateRoute("/search/[query]", { query: "how to?learn" });
* // ➔ ❌ Error: ❌ Parameter "query" contains invalid character "?" in route: "/search/[query]"
*
* // Handles leading/trailing slashes correctly
* generateRoute("/blog/[category]/[slug]", { category: "/news/", slug: "/latest-update/" });
* // ➔ ❌ Error: ❌ Parameter "category" and "slug" contains slashes "/" which is not allowed.
*/
declare function generateRoute<T extends string>(route: T extends string ? (HasDynamicSegments<T> extends true ? T : never) : never, params: T extends string ? ExtractRouteParams<T> : undefined): GenerateRouteResult<T>;
declare function generateRoute<T extends string>(route: T extends string ? T : never, params?: Extract<ExtractRouteParams<T>, Record<string, unknown>>): GenerateRouteResult<T>;
declare function generateRoute<T = unknown>(route: T extends string ? (HasDynamicSegments<T> extends true ? T : unknown) : unknown, params?: T extends string ? ExtractRouteParams<T> : undefined): unknown;
type OptionsCreateBeApiUrl = {
/** * The prefix pathname api url, e.g:`"http://localhost.com/your-target-prefix-entri-point-api-is-here"`, default: `"/api"`.
*
* @default "/api" */
prefix?: string;
/** * Option to getting `prefix` and `pathname` of api url only `(removing origin base api url)`, default: `true`.
*
* @default true */
withOrigin?: boolean;
};
/** ---------------------------------
* * ***Utility for NextJS: `createBeApiUrl`.***
* ---------------------------------
* **Constructs a backend API URL by appending a given pathname to the base API URL.**
* - **ℹ️ Note:**
* - This function builds on top of `getBeApiUrl()`.
* - **Determines the base API URL from:**
* - `NEXT_PUBLIC_BACKEND_API_URL` environment variable (or defaults to `"http://localhost:8000"`).
* - Automatically appends `NEXT_PUBLIC_PORT_BE` if the base URL does not already include a port.
* - **Features of this function:**
* - Allows customizing the API path with an optional `prefix` (defaults to `"/api"`).
* - Can include or exclude the origin (protocol + host) via `withOrigin`.
* - Normalizes paths to avoid duplicate slashes.
* - ***⚠️ Warning:***
* - ***This function only support when using ***[`NextJS`](https://nextjs.org/)***.***
* @param {string|null|undefined} pathname - The API endpoint path (e.g., `/users` or `/v1/posts`), defaultValue: `""`.
* @param {OptionsCreateBeApiUrl} [options] - Configuration options.
* @param {OptionsCreateBeApiUrl["prefix"]} [options.prefix="/api"] - The prefix for the API path (default is `"/api"`).
* @param {OptionsCreateBeApiUrl["withOrigin"]} [options.withOrigin=true] - Whether to include the full base URL or return only the API path.
* @returns {string} The formatted API URL.
* @throws **{@link TypeError | `TypeError`}** if `withOrigin` is not a boolean.
* @throws **{@link TypeError | `TypeError`}** if `prefix` and `pathname` is not a string.
* @throws **{@link Error | `Error`}** if constructing the API URL fails due to an invalid base URL.
* @example
* createBeApiUrl("/users")
* // ➔ "http://localhost:8000/api/users"
* createBeApiUrl("/api/users")
* // ➔ "http://localhost:8000/api/users"
* createBeApiUrl("/v1", { prefix: "/v1" })
* // ➔ "http://localhost:8000/v1"
* createBeApiUrl("/v1/users")
* // ➔ "http://localhost:8000/api/v1/users"
* createBeApiUrl("/v1/users", { prefix: "/v1" })
* // ➔ "http://localhost:8000/v1/users"
* createBeApiUrl("/users", { withOrigin: false })
* // ➔ "/api/users"
* createBeApiUrl(null, { withOrigin: false })
* // ➔ "/api"
* createBeApiUrl(undefined, { withOrigin: false })
* // ➔ "/api"
*/
declare const createBeApiUrl: (
/** * The pathname api url, e.g:`"http://localhost.com/your-target-prefix-entri-point-api-is-here/your-target-pathname-is-here"`.
*
* @default ""
*/
pathname: string | null | undefined, options?: OptionsCreateBeApiUrl) => string;
type OptionsGetBeApiUrl = {
/** * ***The Suffix origin base api url, e.g:`http://localhost.com/api`, default: `"/"`.***
*
* @default "/" */
suffix?: string;
};
/** ---------------------------------------------------
* * ***Utility for NextJS: `getBeApiUrl`.***
* ---------------------------------------------------
* **This function determines the backend API base URL from the `NEXT_PUBLIC_BACKEND_API_URL` environment variable (retrieves the base API URL of the backend).**
* - **Behavior:**
* - If the variable is not set, it defaults to `"http://localhost:8000"`.
* - It also allows adding an optional suffix to the returned URL.
* - ***⚠️ Warning:***
* - ***This function only support when using ***[`NextJS`](https://nextjs.org/)***.***
* @description
* This function determines the backend API base URL from the `NEXT_PUBLIC_BACKEND_API_URL` environment variable.
* - If `NEXT_PUBLIC_BACKEND_API_URL` is not set, it defaults to `"http://localhost:8000"`.
* - If `NEXT_PUBLIC_BACKEND_API_URL` does **not** contain a port, it appends one from `NEXT_PUBLIC_PORT_BE` if available.
* - Supports appending optional suffix (like `"/api"`).
* @param {OptionsGetBeApiUrl|undefined} options - Configuration options.
* @param {OptionsGetBeApiUrl["suffix"]} [options.suffix="/"] - The suffix to append to the base API URL.
* @returns {string} The formatted backend API base URL.
* @throws **{@link TypeError | `TypeError`}** if `suffix` is not a `string`.
* @throws **{@link Error | `Error`}** if `NEXT_PUBLIC_BACKEND_API_URL` is invalid.
* @example
* // With NEXT_PUBLIC_BACKEND_API_URL set at `*.env` file
* NEXT_PUBLIC_BACKEND_API_URL = "https://api.example.com";
* getBeApiUrl();
* // ➔ "https://api.example.com/"
*
* // With NEXT_PUBLIC_BACKEND_API_URL but no port, using NEXT_PUBLIC_PORT_BE at `*.env` file
* NEXT_PUBLIC_BACKEND_API_URL = "http://localhost";
* NEXT_PUBLIC_PORT_BE = "5000";
* getBeApiUrl({ suffix: "/api" });
* // ➔ "http://localhost:5000/api"
*
* // Without NEXT_PUBLIC_BACKEND_API_URL at `*.env` file (defaults to localhost:8000)
* delete NEXT_PUBLIC_BACKEND_API_URL;
* getBeApiUrl({ suffix: "/v1" });
* // ➔ "http://localhost:8000/v1"
*/
declare const getBeApiUrl: (options?: OptionsGetBeApiUrl) => string;
/** ---------------------------------------------------
* * ***Utility for NextJS: `getBaseUrl`.***
* ---------------------------------------------------
* **Retrieves the base URL of the application.**
* - **Behavior:**
* - It determines the base URL from the `NEXT_PUBLIC_BASE_URL` environment variable.
* - If `NEXT_PUBLIC_BASE_URL` is not set, it defaults to `"http://localhost:3000"`.
* - If `NEXT_PUBLIC_BASE_URL` does **not** contain a port, it appends one from `NEXT_PUBLIC_PORT_FE` if available.
* - Ensures the final URL is valid and normalized (no trailing slashes).
* - ***⚠️ Warning:***
* - ***This function only support when using ***[`NextJS`](https://nextjs.org/)***.***
* @returns {string} The resolved base URL of the application.
* @throws **{@link Error | `Error`}** if the constructed URL is invalid or malformed.
* @example
* // With environment variable set at `*.env` file
* NEXT_PUBLIC_BASE_URL = "https://example.com";
* getBaseUrl();
* // ➔ "https://example.com"
*
* // With custom port via NEXT_PUBLIC_PORT_FE at `*.env` file
* NEXT_PUBLIC_BASE_URL = "http://localhost";
* NEXT_PUBLIC_PORT_FE = "4000";
* getBaseUrl();
* // ➔ "http://localhost:4000"
*
* // Without environment variable at `*.env` file
* delete NEXT_PUBLIC_BASE_URL;
* getBaseUrl();
* // ➔ "http://localhost:3000"
*/
declare const getBaseUrl: () => string;
export { type ExtractRouteParams, type HasDynamicSegments, createBeApiUrl, generateRoute, getBaseUrl, getBeApiUrl };