rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
97 lines (96 loc) • 3.72 kB
JavaScript
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("/");
}