UNPKG

rwsdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

97 lines (96 loc) 3.72 kB
export function linkFor() { return createLinkFunction(); } export function createLinks(_app) { return linkFor(); } // Implementation export function defineLinks(routes) { // If no routes provided, this is the app type overload // At runtime, we can't distinguish, but the type system ensures // this only happens when called as defineLinks<App>() // We delegate to linkFor which handles app types correctly if (routes === undefined) { // This branch is only reachable when called as defineLinks<App>() // The return type is AppLink<App> due to the overload // We use linkFor internally which doesn't need runtime route validation return linkFor(); } // Original implementation for route arrays routes.forEach((route) => { if (typeof route !== "string") { throw new Error(`RedwoodSDK: Invalid route: ${route}. Routes must be string literals. Ensure you're passing an array of route paths.`); } }); const link = createLinkFunction(); return ((path, params) => { if (!routes.includes(path)) { throw new Error(`RedwoodSDK: Invalid route: ${path}. This route is not included in the routes array passed to defineLinks(). Check for typos or ensure the route is defined in your router.`); } return link(path, params); }); } const TOKEN_REGEX = /:([a-zA-Z0-9_]+)|\*/g; function createLinkFunction() { return ((path, params) => { const expectsParams = hasRouteParameters(path); if (!params || Object.keys(params).length === 0) { if (expectsParams) { throw new Error(`RedwoodSDK: Route ${path} requires an object of parameters (e.g., link("${path}", { id: "123" })).`); } return path; } return interpolate(path, params); }); } function hasRouteParameters(path) { TOKEN_REGEX.lastIndex = 0; const result = TOKEN_REGEX.test(path); TOKEN_REGEX.lastIndex = 0; return result; } function interpolate(template, params) { let result = ""; let lastIndex = 0; let wildcardIndex = 0; const consumed = new Set(); TOKEN_REGEX.lastIndex = 0; let match; while ((match = TOKEN_REGEX.exec(template)) !== null) { result += template.slice(lastIndex, match.index); if (match[1]) { const name = match[1]; const value = params[name]; if (value === undefined) { throw new Error(`RedwoodSDK: Missing parameter "${name}" for route ${template}. Ensure you're providing all required parameters in the params object.`); } result += encodeURIComponent(value); consumed.add(name); } else { const key = `$${wildcardIndex}`; const value = params[key]; if (value === undefined) { throw new Error(`RedwoodSDK: Missing parameter "${key}" for route ${template}. Wildcard routes use $0, $1, etc. as parameter keys.`); } result += encodeWildcardValue(value); consumed.add(key); wildcardIndex += 1; } lastIndex = TOKEN_REGEX.lastIndex; } result += template.slice(lastIndex); for (const key of Object.keys(params)) { if (!consumed.has(key)) { throw new Error(`RedwoodSDK: Parameter "${key}" is not used by route ${template}. Check your params object for typos or remove unused parameters.`); } } TOKEN_REGEX.lastIndex = 0; return result; } function encodeWildcardValue(value) { return value .split("/") .map((segment) => encodeURIComponent(segment)) .join("/"); }