static-path
Version:
Static-path uses TypeScript to prevent 404s and other path generation mistakes at compile time
77 lines • 3.12 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.path = void 0;
/* Build a new path.
*
* This function also does some runtime validity checks on the paths. Path
* objects should be built at app boot time, so the validity checks here are
* safe: the app won't even boot if the checks fail. */
function path(pattern) {
const parts = patternParts(pattern);
const paramsToPath = (params) => {
return ('/' +
parts
.map((part) => {
if (part.kind === 'param') {
return paramValue(params, part.paramName);
}
else {
return part.value;
}
})
.filter((value) => value !== '')
.join('/'));
};
/* Ideally, we'd normalize the pattern, then extract the path parts from that
* normalized form. Unfortunately, that doesn't work because so much of this
* code needs to know the exact literal type of `Pattern`, but normalizing
* the pattern turns it into some other string type. So there's some subtle
* duplication here, where multiple parts of the code know how to handle
* slashes. */
paramsToPath.pattern = normalizePattern(pattern);
paramsToPath.parts = parts;
paramsToPath.path = (subpattern) => {
return path(`${pattern}/${subpattern}`);
};
if (!paramsToPath.pattern.startsWith('/')) {
throw new Error(`Paths must begin with slashes, but ${JSON.stringify(paramsToPath.pattern)} doesn't.`);
}
return paramsToPath;
}
exports.path = path;
/* Turn a Pattern literal string type into an array of the path's parts. */
function patternParts(pattern) {
return pattern.split('/').map((part) => {
if (part.startsWith(':')) {
return { kind: 'param', paramName: part.replace(/^:/, '') };
}
else {
return { kind: 'constant', value: part };
}
});
}
/* Normalize patterns. E.g., '/courses//:courseId/' becomes
* '/courses/:courseId'
*
* This function needs to exactly match what the `NormalizePattern` type does.
* The type system can't see what we're doing here, so we have to verify the
* match manually. */
function normalizePattern(pattern) {
// Case 1: we're looking at the root path, '/'. We don't want to change it.
if (pattern === '/') {
return pattern;
}
return pattern
// Case 2: Replace repeated slashes with one slash.
.replace(/\/+/g, '/')
// Case 3: Remove trailing slashes.
.replace(/^(.+)\/$/, (match, patternWithoutTrailingSlash) => patternWithoutTrailingSlash);
}
function paramValue(params, paramName) {
const value = params[paramName];
if (typeof value !== 'string') {
throw new Error(`When generating a path, the path param ${JSON.stringify(paramName)} didn't exist on params object ${JSON.stringify(params)}. The types should have prevented this, so either you defeated them (e.g., with \`as\`) or this is a bug!`);
}
return value;
}
//# sourceMappingURL=index.js.map
;