fastify-file-router
Version:
A fastify plugin that automatically registers routes from files in a directory.
144 lines • 5.33 kB
JavaScript
import { methodRegex, nextParamRegex, nextSegment, remixParamRegex, remixSegment } from './constants.js';
/**
* Validates and converts a method string to an HTTPMethods type.
* @param method - The method string to validate
* @param fullPath - The full path to the file (for error messages)
* @returns The validated HTTP method
* @throws Error if the method is invalid
*/
export function toHttpMethod(method, fullPath) {
// Validate method
if (!method || !methodRegex.test(method)) {
throw new Error(`Invalid method "${method}" in file ${fullPath}`);
}
return method;
}
/**
* Pre-processes segments to merge [.] with adjacent segments.
* @param segments - Array of route segments
* @param fullPath - The full path to the file (for error messages)
* @returns Processed segments with [.] merged
*/
function mergeLiteralDots(segments, fullPath) {
const merged = [];
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
if (!segment)
continue;
if (segment === '[.]') {
if (merged.length === 0) {
throw new Error(`Invalid segment "[.]" at start of route in file ${fullPath}`);
}
// Don't merge [.] if the previous segment is a parameter (starts with $)
// Parameters should end at [.], and [.] + next segment should be merged separately
const prevSegment = merged[merged.length - 1];
if (prevSegment?.startsWith('$')) {
// Keep the parameter segment as-is, merge [.] with next segment
const next = segments[i + 1];
if (next) {
merged.push(`.${next}`);
i++; // Skip next segment
}
else {
merged.push('.');
}
}
else {
// Normal merge: merge [.] with previous segment
const next = segments[i + 1];
if (next) {
merged[merged.length - 1] += `.${next}`;
i++; // Skip next segment
}
else {
merged[merged.length - 1] += '.';
}
}
}
else {
merged.push(segment);
}
}
return merged;
}
/**
* Converts route segments to a Remix-style route path.
* @param segments - Array of route segments
* @param fullPath - The full path to the file (for error messages)
* @returns The converted route path
* @throws Error if any segment is invalid
*/
export function toRouteRemixStyle(segments, fullPath) {
const merged = mergeLiteralDots(segments, fullPath);
const pathParts = [];
for (let i = 0; i < merged.length; i++) {
const segment = merged[i];
if (!segment)
continue;
if (!remixSegment.test(segment)) {
throw new Error(`Invalid segment "${segment}" in file ${fullPath}`);
}
// If segment starts with '.', append it to the previous segment (no / separator)
if (segment.startsWith('.')) {
if (pathParts.length === 0) {
throw new Error(`Invalid segment "${segment}" at start of route in file ${fullPath}`);
}
pathParts[pathParts.length - 1] += segment;
continue;
}
const match = segment.match(remixParamRegex);
if (match?.groups) {
const param = match.groups.param;
pathParts.push(param && param.length > 0 ? `:${param}` : '*');
}
else {
pathParts.push(segment);
}
}
return pathParts.join('/');
}
/**
* Converts route segments to a Next.js-style route path.
* @param segments - Array of route segments
* @param fullPath - The full path to the file (for error messages)
* @returns The converted route path
* @throws Error if any segment is invalid
*/
export function toRouteNextStyle(segments, fullPath) {
const merged = mergeLiteralDots(segments, fullPath);
const pathParts = [];
for (let i = 0; i < merged.length; i++) {
const segment = merged[i];
if (!segment)
continue;
if (!nextSegment.test(segment)) {
throw new Error(`Invalid segment "${segment}" in file ${fullPath}`);
}
// If segment starts with '.', append it to the previous segment (no / separator)
if (segment.startsWith('.')) {
if (pathParts.length === 0) {
throw new Error(`Invalid segment "${segment}" at start of route in file ${fullPath}`);
}
pathParts[pathParts.length - 1] += segment;
continue;
}
const match = segment.match(nextParamRegex);
if (match?.groups) {
const param = match.groups.param;
if (param?.startsWith('...')) {
pathParts.push('*');
}
else if (param && param.length > 0) {
pathParts.push(`:${param}`);
}
else {
throw new Error(`Invalid segment "${segment}" in convention "next"`);
}
}
else {
pathParts.push(segment);
}
}
return pathParts.join('/');
}
//# sourceMappingURL=routeConverter.js.map