UNPKG

@izzyjs/route

Version:

Use your AdonisJs routes in your Inertia.js application

280 lines (279 loc) 9.35 kB
import { isBrowser } from './utils/is_browser.js'; export class Route extends String { /** * The complete URL with protocol and domain when baseUrl is configured * @example * ```ts * const route = Route.new('user.show', { id: '1' }) * console.log(route.url) // https://example.com/users/1 * ``` */ url; /** * The HTTP method for the route */ method; /** * The route parameters as an array * @example * ```ts * const route = Route.new('user.show', { id: '1' }) * console.log(route.params) // ['id'] * ``` */ params; /** * The route name as a string * @example * ```ts * const route = Route.new('user.show', { id: '1' }) * console.log(route.name) // user.show * ``` */ name; /** * The route pattern * @example * ```ts * const route = Route.new('user.show', { id: '1' }) * console.log(route.pattern) // /users/:id * ``` */ pattern; /** * The route path with parameters replaced * @example * ```ts * const route = Route.new('user.show', { id: '1' }) * console.log(route.path) // /users/1 * ``` */ path; /** * The query string * @example * ```ts * const route = Route.new('user.show', { id: '1' }, { page: 1 }) * console.log(route.qs.toString()) // page=1 * ``` */ qs; /** * The hash fragment * @example * ```ts * const route = Route.new('user.show', { id: '1' }, {}, '', 'contato') * console.log(route.hash) // contato * ``` */ hash; constructor(routeName, params, qs = {}, prefix = '', hash = '') { const { routes } = Route.izzy(); const exist = routes.find((r) => 'name' in r && r.name === routeName); if (!exist) { throw new Error(`Route with name "${routeName}" not found`); } const searchParams = new URLSearchParams(qs); let pattern; if (exist.params) { const requiredParams = exist.params.required || []; const optionalParams = exist.params.optional || []; // Check if required parameters are provided if (requiredParams.length > 0) { if (!params) { throw new Error(`Route "${routeName}" requires parameters: ${requiredParams.map((p) => `"${p}"`).join(', ')}`); } // Check if all required parameters are provided const missingParams = requiredParams.filter((param) => !(param in params)); if (missingParams.length > 0) { throw new Error(`Missing required parameters for route "${routeName}": ${missingParams.map((p) => `"${p}"`).join(', ')}`); } } // Replace parameters in the path if (params && Object.keys(params).length > 0) { pattern = Route.replaceRouteParams(exist.path, params); } else if (optionalParams.length > 0) { // Remove optional parameters from the path if no params provided pattern = exist.path.replace(/:\w+\?/g, ''); // Clean up double slashes and trailing slashes pattern = pattern.replace(/\/+/g, '/').replace(/\/$/, '') || '/'; } else { pattern = exist.path; } } else { // Route has no parameters pattern = exist.path; } if (searchParams.toString()) { pattern += `?${searchParams.toString()}`; } if (hash) { pattern += `#${hash}`; } if (prefix) { pattern = prefix + pattern; } super(pattern); this.path = pattern; this.name = exist.name; this.method = exist.method; this.params = exist.params; this.pattern = exist.path; this.qs = searchParams; this.hash = hash; // Generate complete URL with protocol and domain if baseUrl is configured this.url = this.generateCompleteUrl(pattern, exist.domain); } /** * Generate complete URL with protocol and domain when baseUrl is configured */ generateCompleteUrl(path, routeDomain) { try { const config = Route.getConfig(); if (config?.baseUrl) { const baseUrl = new URL(config.baseUrl); // If route has a specific domain (not 'root'), use that domain // Otherwise use the baseUrl.host const host = routeDomain && routeDomain !== 'root' ? routeDomain : baseUrl.host; // Parse the path to separate pathname, search, and hash const url = new URL(path, 'http://example.com'); baseUrl.pathname = url.pathname; baseUrl.search = url.search; baseUrl.hash = url.hash; baseUrl.host = host; return baseUrl.toString(); } } catch (error) { // If baseUrl is invalid, fallback to path only console.warn('Invalid baseUrl configuration, falling back to path only'); } return path; } /** * Get the configuration from the global Izzy object */ static getConfig() { try { if (isBrowser()) { return window.__izzy_route__?.config || null; } else { return globalThis.__izzy_route__?.config || null; } } catch { return null; } } static replaceRouteParams(routePath, params) { let result = routePath; // First, replace all provided parameters for (const [key, value] of Object.entries(params)) { result = result.replace(new RegExp(`:${key}\\??`, 'g'), value); } // Then, remove any remaining optional parameters that weren't provided result = result.replace(/:\w+\?/g, ''); // Clean up double slashes and trailing slashes result = result.replace(/\/+/g, '/').replace(/\/$/, '') || '/'; return result; } static new(routeName, params, qs, prefix, hash) { if (!routeName) { return new Routes(); } return new Route(routeName, params, qs, prefix, hash); } /** * Get the `Route` instance from the global Izzy object * @returns { GlobalIzzyJs } */ static izzy() { let routes; let currentRoute; if (isBrowser()) { routes = window.__izzy_route__.routes; currentRoute = window.location.pathname; } else { routes = globalThis.__izzy_route__.routes; currentRoute = globalThis.__izzy_route__.current; } return { routes, current: currentRoute }; } toString() { return this.path; } } export class Routes { currentRoute; routes; constructor() { const { routes, current: currentRoute } = Route.izzy(); this.routes = routes; this.currentRoute = currentRoute; } current(routeName, params) { return Routes.current(routeName, params); } static current(routeName, params) { const routes = new Routes(); if (!routeName) { return routes.currentRoute; } if (routeName.includes('*')) { const regex = new RegExp(routeName.replace(/\*/g, '.*')); return regex.test(routes.currentRoute); } const route = Route.new(routeName, params); return routes.currentRoute === route.toString(); } /** * Check if the current route has the given route name * @summary unstable * @param routeName route name * @example * ```ts * route().has('users.index') // => true or false * route().has('user.*') // => true or false * ``` */ has(routeName) { if (routeName.includes('*')) { const regex = new RegExp(routeName.replace(/\*/g, '.*')); return this.routes.some((r) => 'name' in r && regex.test(r.name)); } return this.routes.some((r) => 'name' in r && r.name === routeName); } get params() { const route = this.routes.find(({ path }) => { const regex = new RegExp(path .split('/') .map((p) => (p.startsWith(':') ? '([^/]+)' : p)) .join('/')); return regex.test(this.currentRoute); }); if (route && route.params) { const allParams = [...(route.params.required || []), ...(route.params.optional || [])]; if (allParams.length === 0) { return {}; } const regex = new RegExp(route.path .split('/') .map((p) => (p.startsWith(':') ? '([^/]+)' : p)) .join('/')); const values = this.currentRoute.match(regex); if (!values) { return {}; } return allParams.reduce((acc, param, index) => ({ ...acc, [param]: values[index + 1], }), {}); } return {}; } }