Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

path-to-regexp

Package Overview
Dependencies
Maintainers
5
Versions
67
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

path-to-regexp - npm Package Compare versions

Comparing version 7.1.0 to 8.0.0

127

dist/index.d.ts

@@ -11,6 +11,2 @@ /**

/**
* The default delimiter for segments. (default: `'/'`)
*/
delimiter?: string;
/**
* A function for encoding input strings.

@@ -20,63 +16,72 @@ */

}
export interface PathToRegexpOptions extends ParseOptions {
export interface MatchOptions {
/**
* Regexp will be case sensitive. (default: `false`)
* Function for decoding strings for params, or `false` to disable entirely. (default: `decodeURIComponent`)
*/
sensitive?: boolean;
decode?: Decode | false;
/**
* Allow the delimiter to be arbitrarily repeated. (default: `true`)
* Matches the path completely without trailing characters. (default: `true`)
*/
loose?: boolean;
/**
* Verify patterns are valid and safe to use. (default: `false`)
*/
strict?: boolean;
/**
* Match from the beginning of the string. (default: `true`)
*/
start?: boolean;
/**
* Match to the end of the string. (default: `true`)
*/
end?: boolean;
/**
* Allow optional trailing delimiter to match. (default: `true`)
* Allows optional trailing delimiter to match. (default: `true`)
*/
trailing?: boolean;
}
export interface MatchOptions extends PathToRegexpOptions {
/**
* Function for decoding strings for params, or `false` to disable entirely. (default: `decodeURIComponent`)
* Match will be case sensitive. (default: `false`)
*/
decode?: Decode | false;
}
export interface CompileOptions extends ParseOptions {
/**
* Regexp will be case sensitive. (default: `false`)
*/
sensitive?: boolean;
/**
* Allow the delimiter to be arbitrarily repeated. (default: `true`)
* The default delimiter for segments. (default: `'/'`)
*/
loose?: boolean;
delimiter?: string;
}
export interface CompileOptions {
/**
* Verify patterns are valid and safe to use. (default: `false`)
* Function for encoding input strings for output into the path, or `false` to disable entirely. (default: `encodeURIComponent`)
*/
strict?: boolean;
encode?: Encode | false;
/**
* Verifies the function is producing a valid path. (default: `true`)
* The default delimiter for segments. (default: `'/'`)
*/
validate?: boolean;
/**
* Function for encoding input strings for output into the path, or `false` to disable entirely. (default: `encodeURIComponent`)
*/
encode?: Encode | false;
delimiter?: string;
}
/**
* Tokenized path instance. Can we passed around instead of string.
* Plain text.
*/
export interface Text {
type: "text";
value: string;
}
/**
* A parameter designed to match arbitrary text within a segment.
*/
export interface Parameter {
type: "param";
name: string;
}
/**
* A wildcard parameter designed to match multiple segments.
*/
export interface Wildcard {
type: "wildcard";
name: string;
}
/**
* A set of possible tokens to expand when matching.
*/
export interface Group {
type: "group";
tokens: Token[];
}
/**
* A sequence of path match characters.
*/
export type Token = Text | Parameter | Wildcard | Group;
/**
* Tokenized path instance.
*/
export declare class TokenData {
readonly tokens: Token[];
readonly delimiter: string;
constructor(tokens: Token[], delimiter: string);
constructor(tokens: Token[]);
}

@@ -90,3 +95,3 @@ /**

*/
export declare function compile<P extends ParamData = ParamData>(path: Path, options?: CompileOptions): PathFunction<P>;
export declare function compile<P extends ParamData = ParamData>(path: Path, options?: CompileOptions & ParseOptions): PathFunction<P>;
export type ParamData = Partial<Record<string, string | string[]>>;

@@ -99,3 +104,2 @@ export type PathFunction<P extends ParamData> = (data?: P) => string;

path: string;
index: number;
params: P;

@@ -111,34 +115,3 @@ }

export type MatchFunction<P extends ParamData> = (path: string) => Match<P>;
/**
* Create path match function from `path-to-regexp` spec.
*/
export declare function match<P extends ParamData>(path: Path, options?: MatchOptions): MatchFunction<P>;
/**
* A key is a capture group in the regex.
*/
export interface Key {
name: string;
prefix?: string;
suffix?: string;
pattern?: string;
modifier?: string;
separator?: string;
}
/**
* A token is a string (nothing special) or key metadata (capture group).
*/
export type Token = string | Key;
/**
* Repeated and simple input types.
*/
export type Path = string | TokenData;
/**
* Normalize the given path string, returning a regular expression.
*
* An empty array can be passed in for the keys, which will hold the
* placeholder key descriptions. For example, using `/user/:id`, `keys` will
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
*/
export declare function pathToRegexp(path: Path, options?: PathToRegexpOptions): RegExp & {
keys: Key[];
};
export declare function match<P extends ParamData>(path: Path | Path[], options?: MatchOptions & ParseOptions): MatchFunction<P>;
"use strict";
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _Iter_peek;
Object.defineProperty(exports, "__esModule", { value: true });

@@ -7,85 +19,92 @@ exports.TokenData = void 0;

exports.match = match;
exports.pathToRegexp = pathToRegexp;
const DEFAULT_DELIMITER = "/";
const NOOP_VALUE = (value) => value;
const ID_CHAR = /^\p{XID_Continue}$/u;
const ID_START = /^[$_\p{ID_Start}]$/u;
const ID_CONTINUE = /^[$\u200c\u200d\p{ID_Continue}]$/u;
const DEBUG_URL = "https://git.new/pathToRegexpError";
const SIMPLE_TOKENS = {
"!": "!",
"@": "@",
";": ";",
",": ",",
"*": "*",
// Groups.
"{": "{",
"}": "}",
// Reserved.
"(": "(",
")": ")",
"[": "[",
"]": "]",
"+": "+",
"?": "?",
"{": "{",
"}": "}",
"!": "!",
};
/**
* Escape a regular expression string.
*/
function escape(str) {
return str.replace(/[.+*?^${}()[\]|/\\]/g, "\\$&");
}
/**
* Get the flags for a regexp from the options.
*/
function toFlags(options) {
return options.sensitive ? "s" : "is";
}
/**
* Tokenize input string.
*/
function lexer(str) {
function* lexer(str) {
const chars = [...str];
const tokens = [];
let i = 0;
while (i < chars.length) {
const value = chars[i];
const type = SIMPLE_TOKENS[value];
if (type) {
tokens.push({ type, index: i++, value });
continue;
}
if (value === "\\") {
tokens.push({ type: "ESCAPED", index: i++, value: chars[i++] });
continue;
}
if (value === ":") {
let name = "";
while (ID_CHAR.test(chars[++i])) {
name += chars[i];
function name() {
let value = "";
if (ID_START.test(chars[++i])) {
value += chars[i];
while (ID_CONTINUE.test(chars[++i])) {
value += chars[i];
}
if (!name) {
throw new TypeError(`Missing parameter name at ${i}`);
}
tokens.push({ type: "NAME", index: i, value: name });
continue;
}
if (value === "(") {
const pos = i++;
let count = 1;
let pattern = "";
if (chars[i] === "?") {
throw new TypeError(`Pattern cannot start with "?" at ${i}`);
}
else if (chars[i] === '"') {
let pos = i;
while (i < chars.length) {
if (chars[++i] === '"') {
i++;
pos = 0;
break;
}
if (chars[i] === "\\") {
pattern += chars[i++] + chars[i++];
continue;
value += chars[++i];
}
if (chars[i] === ")") {
count--;
if (count === 0) {
i++;
break;
}
else {
value += chars[i];
}
else if (chars[i] === "(") {
count++;
if (chars[i + 1] !== "?") {
throw new TypeError(`Capturing groups are not allowed at ${i}`);
}
}
pattern += chars[i++];
}
if (count)
throw new TypeError(`Unbalanced pattern at ${pos}`);
if (!pattern)
throw new TypeError(`Missing pattern at ${pos}`);
tokens.push({ type: "PATTERN", index: i, value: pattern });
continue;
if (pos) {
throw new TypeError(`Unterminated quote at ${pos}: ${DEBUG_URL}`);
}
}
tokens.push({ type: "CHAR", index: i, value: chars[i++] });
if (!value) {
throw new TypeError(`Missing parameter name at ${i}: ${DEBUG_URL}`);
}
return value;
}
tokens.push({ type: "END", index: i, value: "" });
return new Iter(tokens);
while (i < chars.length) {
const value = chars[i];
const type = SIMPLE_TOKENS[value];
if (type) {
yield { type, index: i++, value };
}
else if (value === "\\") {
yield { type: "ESCAPED", index: i++, value: chars[i++] };
}
else if (value === ":") {
const value = name();
yield { type: "PARAM", index: i, value };
}
else if (value === "*") {
const value = name();
yield { type: "WILDCARD", index: i, value };
}
else {
yield { type: "CHAR", index: i, value: chars[i++] };
}
}
return { type: "END", index: i, value: "" };
}

@@ -95,6 +114,10 @@ class Iter {

this.tokens = tokens;
this.index = 0;
_Iter_peek.set(this, void 0);
}
peek() {
return this.tokens[this.index];
if (!__classPrivateFieldGet(this, _Iter_peek, "f")) {
const next = this.tokens.next();
__classPrivateFieldSet(this, _Iter_peek, next.value, "f");
}
return __classPrivateFieldGet(this, _Iter_peek, "f");
}

@@ -105,3 +128,3 @@ tryConsume(type) {

return;
this.index++;
__classPrivateFieldSet(this, _Iter_peek, undefined, "f"); // Reset after consumed.
return token.value;

@@ -124,13 +147,10 @@ }

}
modifier() {
return this.tryConsume("?") || this.tryConsume("*") || this.tryConsume("+");
}
}
_Iter_peek = new WeakMap();
/**
* Tokenized path instance. Can we passed around instead of string.
* Tokenized path instance.
*/
class TokenData {
constructor(tokens, delimiter) {
constructor(tokens) {
this.tokens = tokens;
this.delimiter = delimiter;
}

@@ -143,56 +163,54 @@ }

function parse(str, options = {}) {
const { encodePath = NOOP_VALUE, delimiter = encodePath(DEFAULT_DELIMITER) } = options;
const tokens = [];
const it = lexer(str);
let key = 0;
do {
const path = it.text();
if (path)
tokens.push(encodePath(path));
const name = it.tryConsume("NAME");
const pattern = it.tryConsume("PATTERN");
if (name || pattern) {
tokens.push({
name: name || String(key++),
pattern,
});
const next = it.peek();
if (next.type === "*") {
throw new TypeError(`Unexpected * at ${next.index}, you probably want \`/*\` or \`{/:foo}*\`: ${DEBUG_URL}`);
const { encodePath = NOOP_VALUE } = options;
const it = new Iter(lexer(str));
function consume(endType) {
const tokens = [];
while (true) {
const path = it.text();
if (path)
tokens.push({ type: "text", value: encodePath(path) });
const param = it.tryConsume("PARAM");
if (param) {
tokens.push({
type: "param",
name: param,
});
continue;
}
continue;
const wildcard = it.tryConsume("WILDCARD");
if (wildcard) {
tokens.push({
type: "wildcard",
name: wildcard,
});
continue;
}
const open = it.tryConsume("{");
if (open) {
tokens.push({
type: "group",
tokens: consume("}"),
});
continue;
}
it.consume(endType);
return tokens;
}
const asterisk = it.tryConsume("*");
if (asterisk) {
tokens.push({
name: String(key++),
pattern: `(?:(?!${escape(delimiter)}).)*`,
modifier: "*",
separator: delimiter,
});
continue;
}
const tokens = consume("END");
return new TokenData(tokens);
}
/**
* Transform tokens into a path building function.
*/
function $compile(data, options) {
const { encode = encodeURIComponent, delimiter = DEFAULT_DELIMITER } = options;
const fn = tokensToFunction(data.tokens, delimiter, encode);
return function path(data = {}) {
const [path, ...missing] = fn(data);
if (missing.length) {
throw new TypeError(`Missing parameters: ${missing.join(", ")}`);
}
const open = it.tryConsume("{");
if (open) {
const prefix = it.text();
const name = it.tryConsume("NAME");
const pattern = it.tryConsume("PATTERN");
const suffix = it.text();
const separator = it.tryConsume(";") && it.text();
it.consume("}");
const modifier = it.modifier();
tokens.push({
name: name || (pattern ? String(key++) : ""),
prefix: encodePath(prefix),
suffix: encodePath(suffix),
pattern,
modifier,
separator,
});
continue;
}
it.consume("END");
break;
} while (true);
return new TokenData(tokens, delimiter);
return path;
};
}

@@ -203,56 +221,50 @@ /**

function compile(path, options = {}) {
const data = path instanceof TokenData ? path : parse(path, options);
return compileTokens(data, options);
return $compile(path instanceof TokenData ? path : parse(path, options), options);
}
function tokensToFunction(tokens, delimiter, encode) {
const encoders = tokens.map((token) => tokenToFunction(token, delimiter, encode));
return (data) => {
const result = [""];
for (const encoder of encoders) {
const [value, ...extras] = encoder(data);
result[0] += value;
result.push(...extras);
}
return result;
};
}
/**
* Convert a single token into a path building function.
*/
function tokenToFunction(token, encode) {
if (typeof token === "string") {
return () => token;
}
const encodeValue = encode || NOOP_VALUE;
const repeated = token.modifier === "+" || token.modifier === "*";
const optional = token.modifier === "?" || token.modifier === "*";
const { prefix = "", suffix = "", separator = suffix + prefix } = token;
if (encode && repeated) {
const stringify = (value, index) => {
if (typeof value !== "string") {
throw new TypeError(`Expected "${token.name}/${index}" to be a string`);
}
return encodeValue(value);
};
const compile = (value) => {
if (!Array.isArray(value)) {
throw new TypeError(`Expected "${token.name}" to be an array`);
}
if (value.length === 0)
return "";
return prefix + value.map(stringify).join(separator) + suffix;
};
if (optional) {
return (data) => {
const value = data[token.name];
if (value == null)
return "";
return value.length ? compile(value) : "";
};
}
function tokenToFunction(token, delimiter, encode) {
if (token.type === "text")
return () => [token.value];
if (token.type === "group") {
const fn = tokensToFunction(token.tokens, delimiter, encode);
return (data) => {
const value = data[token.name];
return compile(value);
const [value, ...missing] = fn(data);
if (!missing.length)
return [value];
return [""];
};
}
const stringify = (value) => {
if (typeof value !== "string") {
throw new TypeError(`Expected "${token.name}" to be a string`);
}
return prefix + encodeValue(value) + suffix;
};
if (optional) {
const encodeValue = encode || NOOP_VALUE;
if (token.type === "wildcard" && encode !== false) {
return (data) => {
const value = data[token.name];
if (value == null)
return "";
return stringify(value);
return ["", token.name];
if (!Array.isArray(value) || value.length === 0) {
throw new TypeError(`Expected "${token.name}" to be a non-empty array`);
}
return [
value
.map((value, index) => {
if (typeof value !== "string") {
throw new TypeError(`Expected "${token.name}/${index}" to be a string`);
}
return encodeValue(value);
})
.join(delimiter),
];
};

@@ -262,56 +274,41 @@ }

const value = data[token.name];
return stringify(value);
if (value == null)
return ["", token.name];
if (typeof value !== "string") {
throw new TypeError(`Expected "${token.name}" to be a string`);
}
return [encodeValue(value)];
};
}
/**
* Transform tokens into a path building function.
* Create path match function from `path-to-regexp` spec.
*/
function compileTokens(data, options) {
const { encode = encodeURIComponent, loose = true, validate = true, strict = false, } = options;
function $match(data, options = {}) {
const { decode = decodeURIComponent, delimiter = DEFAULT_DELIMITER, end = true, trailing = true, } = options;
const flags = toFlags(options);
const stringify = toStringify(loose, data.delimiter);
const sources = toRegExpSource(data, stringify, [], flags, strict);
// Compile all the tokens into regexps.
const encoders = data.tokens.map((token, index) => {
const fn = tokenToFunction(token, encode);
if (!validate || typeof token === "string")
return fn;
const validRe = new RegExp(`^${sources[index]}$`, flags);
return (data) => {
const value = fn(data);
if (!validRe.test(value)) {
throw new TypeError(`Invalid value for "${token.name}": ${JSON.stringify(value)}`);
}
return value;
};
});
return function path(data = {}) {
let path = "";
for (const encoder of encoders)
path += encoder(data);
return path;
};
}
/**
* Create path match function from `path-to-regexp` spec.
*/
function match(path, options = {}) {
const { decode = decodeURIComponent, loose = true } = options;
const data = path instanceof TokenData ? path : parse(path, options);
const stringify = toStringify(loose, data.delimiter);
const sources = [];
const keys = [];
const re = tokensToRegexp(data, keys, options);
for (const { tokens } of data) {
for (const seq of flatten(tokens, 0, [])) {
const regexp = sequenceToRegExp(seq, delimiter, keys);
sources.push(regexp);
}
}
let pattern = `^(?:${sources.join("|")})`;
if (trailing)
pattern += `(?:${escape(delimiter)}$)?`;
pattern += end ? "$" : `(?=${escape(delimiter)}|$)`;
const re = new RegExp(pattern, flags);
const decoders = keys.map((key) => {
if (decode && (key.modifier === "+" || key.modifier === "*")) {
const { prefix = "", suffix = "", separator = suffix + prefix } = key;
const re = new RegExp(stringify(separator), "g");
return (value) => value.split(re).map(decode);
}
return decode || NOOP_VALUE;
if (decode === false)
return NOOP_VALUE;
if (key.type === "param")
return decode;
return (value) => value.split(delimiter).map(decode);
});
return function match(input) {
return Object.assign(function match(input) {
const m = re.exec(input);
if (!m)
return false;
const { 0: path, index } = m;
const { 0: path } = m;
const params = Object.create(null);

@@ -325,115 +322,69 @@ for (let i = 1; i < m.length; i++) {

}
return { path, index, params };
};
return { path, params };
}, { re });
}
/**
* Escape a regular expression string.
*/
function escape(str) {
return str.replace(/([.+*?^${}()[\]|/\\])/g, "\\$1");
function match(path, options = {}) {
const paths = Array.isArray(path) ? path : [path];
const items = paths.map((path) => path instanceof TokenData ? path : parse(path, options));
return $match(items, options);
}
/**
* Escape and repeat loose characters for regular expressions.
* Generate a flat list of sequence tokens from the given tokens.
*/
function looseReplacer(value, loose) {
const escaped = escape(value);
return loose ? `(?:${escaped})+(?!${escaped})` : escaped;
function* flatten(tokens, index, init) {
if (index === tokens.length) {
return yield init;
}
const token = tokens[index];
if (token.type === "group") {
const fork = init.slice();
for (const seq of flatten(token.tokens, 0, fork)) {
yield* flatten(tokens, index + 1, seq);
}
}
else {
init.push(token);
}
yield* flatten(tokens, index + 1, init);
}
/**
* Encode all non-delimiter characters using the encode function.
* Transform a flat sequence of tokens into a regular expression.
*/
function toStringify(loose, delimiter) {
if (!loose)
return escape;
const re = new RegExp(`(?:(?!${escape(delimiter)}).)+|(.)`, "g");
return (value) => value.replace(re, looseReplacer);
}
/**
* Get the flags for a regexp from the options.
*/
function toFlags(options) {
return options.sensitive ? "" : "i";
}
/**
* Expose a function for taking tokens and returning a RegExp.
*/
function tokensToRegexp(data, keys, options) {
const { trailing = true, loose = true, start = true, end = true, strict = false, } = options;
const flags = toFlags(options);
const stringify = toStringify(loose, data.delimiter);
const sources = toRegExpSource(data, stringify, keys, flags, strict);
let pattern = start ? "^" : "";
pattern += sources.join("");
if (trailing)
pattern += `(?:${stringify(data.delimiter)})?`;
pattern += end ? "$" : `(?=${escape(data.delimiter)}|$)`;
return new RegExp(pattern, flags);
}
/**
* Convert a token into a regexp string (re-used for path validation).
*/
function toRegExpSource(data, stringify, keys, flags, strict) {
const defaultPattern = `(?:(?!${escape(data.delimiter)}).)+?`;
function sequenceToRegExp(tokens, delimiter, keys) {
let result = "";
let backtrack = "";
let safe = true;
return data.tokens.map((token, index) => {
if (typeof token === "string") {
backtrack = token;
return stringify(token);
let isSafeSegmentParam = true;
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (token.type === "text") {
result += escape(token.value);
backtrack = token.value;
isSafeSegmentParam || (isSafeSegmentParam = token.value.includes(delimiter));
continue;
}
const { prefix = "", suffix = "", separator = suffix + prefix, modifier = "", } = token;
const pre = stringify(prefix);
const post = stringify(suffix);
if (token.name) {
const pattern = token.pattern ? `(?:${token.pattern})` : defaultPattern;
const re = checkPattern(pattern, token.name, flags);
safe || (safe = safePattern(re, prefix || backtrack));
if (!safe) {
throw new TypeError(`Ambiguous pattern for "${token.name}": ${DEBUG_URL}`);
if (token.type === "param" || token.type === "wildcard") {
if (!isSafeSegmentParam && !backtrack) {
throw new TypeError(`Missing text after "${token.name}": ${DEBUG_URL}`);
}
safe = !strict || safePattern(re, suffix);
if (token.type === "param") {
result += `(${negate(delimiter, isSafeSegmentParam ? "" : backtrack)}+)`;
}
else {
result += `(.+)`;
}
keys.push(token);
backtrack = "";
keys.push(token);
if (modifier === "+" || modifier === "*") {
const mod = modifier === "*" ? "?" : "";
const sep = stringify(separator);
if (!sep) {
throw new TypeError(`Missing separator for "${token.name}": ${DEBUG_URL}`);
}
safe || (safe = !strict || safePattern(re, separator));
if (!safe) {
throw new TypeError(`Ambiguous pattern for "${token.name}" separator: ${DEBUG_URL}`);
}
safe = !strict;
return `(?:${pre}(${pattern}(?:${sep}${pattern})*)${post})${mod}`;
}
return `(?:${pre}(${pattern})${post})${modifier}`;
isSafeSegmentParam = false;
continue;
}
return `(?:${pre}${post})${modifier}`;
});
}
function checkPattern(pattern, name, flags) {
try {
return new RegExp(`^${pattern}$`, flags);
}
catch (err) {
throw new TypeError(`Invalid pattern for "${name}": ${err.message}`);
}
return result;
}
function safePattern(re, value) {
return value ? !re.test(value) : false;
function negate(delimiter, backtrack) {
const values = [delimiter, backtrack].filter(Boolean);
const isSimple = values.every((value) => value.length === 1);
if (isSimple)
return `[^${escape(values.join(""))}]`;
return `(?:(?!${values.map(escape).join("|")}).)`;
}
/**
* Normalize the given path string, returning a regular expression.
*
* An empty array can be passed in for the keys, which will hold the
* placeholder key descriptions. For example, using `/user/:id`, `keys` will
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
*/
function pathToRegexp(path, options = {}) {
const data = path instanceof TokenData ? path : parse(path, options);
const keys = [];
const regexp = tokensToRegexp(data, keys, options);
return Object.assign(regexp, { keys });
}
//# sourceMappingURL=index.js.map
{
"name": "path-to-regexp",
"version": "7.1.0",
"version": "8.0.0",
"description": "Express style path to RegExp utility",

@@ -23,2 +23,3 @@ "keywords": [

"scripts": {
"bench": "vitest bench",
"build": "ts-scripts build",

@@ -25,0 +26,0 @@ "format": "ts-scripts format",

@@ -20,204 +20,68 @@ # Path-to-RegExp

```js
const { pathToRegexp, match, parse, compile } = require("path-to-regexp");
const { match, compile, parse } = require("path-to-regexp");
// pathToRegexp(path, options?)
// match(path, options?)
// compile(path, options?)
// parse(path, options?)
// compile(path, options?)
```
### Path to regexp
The `pathToRegexp` function returns a regular expression with `keys` as a property. It accepts the following arguments:
- **path** A string.
- **options** _(optional)_
- **sensitive** Regexp will be case sensitive. (default: `false`)
- **trailing** Allows optional trailing delimiter to match. (default: `true`)
- **strict** Verify patterns are valid and safe to use. (default: `false`, recommended: `true`)
- **end** Match to the end of the string. (default: `true`)
- **start** Match from the beginning of the string. (default: `true`)
- **loose** Allow the delimiter to be arbitrarily repeated, e.g. `/` or `///`. (default: `true`)
- **delimiter** The default delimiter for segments, e.g. `[^/]` for `:named` parameters. (default: `'/'`)
- **encodePath** A function for encoding input strings. (default: `x => x`, recommended: [`encodeurl`](https://github.com/pillarjs/encodeurl) for unicode encoding)
```js
const regexp = pathToRegexp("/foo/:bar");
// regexp = /^\/+foo(?:\/+([^\/]+?))(?:\/+)?$/i
// keys = [{ name: 'bar', prefix: '', suffix: '', pattern: '', modifier: '' }]
```
**Please note:** The `RegExp` returned by `path-to-regexp` is intended for ordered data (e.g. pathnames, hostnames). It can not handle arbitrarily ordered data (e.g. query strings, URL fragments, JSON, etc).
### Parameters
The path argument is used to define parameters and populate keys.
Parameters match arbitrary strings in a path by matching up to the end of the segment, or up to any proceeding tokens. They are defined by prefixing a colon to the parameter name (`:foo`). Parameter names can use any valid JavaScript identifier, or be double quoted to use other characters (`:"param-name"`).
#### Named parameters
Named parameters are defined by prefixing a colon to the parameter name (`:foo`). Parameter names can use any valid unicode identifier characters (similar to JavaScript).
```js
const regexp = pathToRegexp("/:foo/:bar");
// keys = [{ name: 'foo', ... }, { name: 'bar', ... }]
const fn = match("/:foo/:bar");
regexp.exec("/test/route");
//=> [ '/test/route', 'test', 'route', index: 0 ]
fn("/test/route");
//=> { path: '/test/route', params: { foo: 'test', bar: 'route' } }
```
##### Custom matching parameters
### Wildcard
Parameters can have a custom regexp, which overrides the default match (`[^/]+`). For example, you can match digits or names in a path:
Wildcard parameters match one or more characters across multiple segments. They are defined the same way as regular parameters, but are prefixed with an asterisk (`*foo`).
```js
const regexpNumbers = pathToRegexp("/icon-:foo(\\d+).png");
// keys = [{ name: 'foo', ... }]
const fn = match("/*splat");
regexpNumbers.exec("/icon-123.png");
//=> ['/icon-123.png', '123']
regexpNumbers.exec("/icon-abc.png");
//=> null
const regexpWord = pathToRegexp("/(user|u)");
// keys = [{ name: 0, ... }]
regexpWord.exec("/u");
//=> ['/u', 'u']
regexpWord.exec("/users");
//=> null
fn("/bar/baz");
//=> { path: '/bar/baz', params: { splat: [ 'bar', 'baz' ] } }
```
**Tip:** Backslashes need to be escaped with another backslash in JavaScript strings.
### Optional
#### Unnamed parameters
Braces can be used to define parts of the path that are optional.
It is possible to define a parameter without a name. The name will be numerically indexed:
```js
const regexp = pathToRegexp("/:foo/(.*)");
// keys = [{ name: 'foo', ... }, { name: '0', ... }]
const fn = match("/users{/:id}/delete");
regexp.exec("/test/route");
//=> [ '/test/route', 'test', 'route', index: 0 ]
```
fn("/users/delete");
//=> { path: '/users/delete', params: {} }
##### Custom prefix and suffix
Parameters can be wrapped in `{}` to create custom prefixes or suffixes for your segment:
```js
const regexp = pathToRegexp("{/:attr1}?{-:attr2}?{-:attr3}?");
regexp.exec("/test");
// => ['/test', 'test', undefined, undefined]
regexp.exec("/test-test");
// => ['/test', 'test', 'test', undefined]
fn("/users/123/delete");
//=> { path: '/users/123/delete', params: { id: '123' } }
```
#### Modifiers
## Match
Modifiers are used after parameters with custom prefixes and suffixes (`{}`).
The `match` function returns a function for matching strings against a path:
##### Optional
- **path** String or array of strings.
- **options** _(optional)_ (See [parse](#parse) for more options)
- **sensitive** Regexp will be case sensitive. (default: `false`)
- **end** Validate the match reaches the end of the string. (default: `true`)
- **trailing** Allows optional trailing delimiter to match. (default: `true`)
- **decode** Function for decoding strings to params, or `false` to disable all processing. (default: `decodeURIComponent`)
Parameters can be suffixed with a question mark (`?`) to make the parameter optional.
```js
const regexp = pathToRegexp("/:foo{/:bar}?");
// keys = [{ name: 'foo', ... }, { name: 'bar', prefix: '/', modifier: '?' }]
regexp.exec("/test");
//=> [ '/test', 'test', undefined, index: 0 ]
regexp.exec("/test/route");
//=> [ '/test/route', 'test', 'route', index: 0 ]
const fn = match("/foo/:bar");
```
##### Zero or more
**Please note:** `path-to-regexp` is intended for ordered data (e.g. pathnames, hostnames). It can not handle arbitrarily ordered data (e.g. query strings, URL fragments, JSON, etc).
Parameters can be suffixed with an asterisk (`*`) to denote a zero or more parameter matches.
## Compile ("Reverse" Path-To-RegExp)
```js
const regexp = pathToRegexp("{/:foo}*");
// keys = [{ name: 'foo', prefix: '/', modifier: '*' }]
regexp.exec("/foo");
//=> [ '/foo', "foo", index: 0 ]
regexp.exec("/bar/baz");
//=> [ '/bar/baz', 'bar/baz', index: 0 ]
```
##### One or more
Parameters can be suffixed with a plus sign (`+`) to denote a one or more parameter matches.
```js
const regexp = pathToRegexp("{/:foo}+");
// keys = [{ name: 'foo', prefix: '/', modifier: '+' }]
regexp.exec("/");
//=> null
regexp.exec("/bar/baz");
//=> [ '/bar/baz', 'bar/baz', index: 0 ]
```
##### Custom separator
By default, parameters set the separator as the `prefix + suffix` of the token. Using `;` you can modify this:
```js
const regexp = pathToRegexp("/name{/:parts;-}+");
regexp.exec("/name");
//=> null
regexp.exec("/bar/1-2-3");
//=> [ '/name/1-2-3', '1-2-3', index: 0 ]
```
#### Wildcard
A wildcard can also be used. It is roughly equivalent to `(.*)`.
```js
const regexp = pathToRegexp("/*");
// keys = [{ name: '0', pattern: '[^\\/]*', separator: '/', modifier: '*' }]
regexp.exec("/");
//=> [ '/', '', index: 0 ]
regexp.exec("/bar/baz");
//=> [ '/bar/baz', 'bar/baz', index: 0 ]
```
### Match
The `match` function returns a function for transforming paths into parameters:
- **path** A string.
- **options** _(optional)_ The same options as `pathToRegexp`, plus:
- **decode** Function for decoding strings for params, or `false` to disable entirely. (default: `decodeURIComponent`)
```js
const fn = match("/user/:id");
fn("/user/123"); //=> { path: '/user/123', index: 0, params: { id: '123' } }
fn("/invalid"); //=> false
fn("/user/caf%C3%A9"); //=> { path: '/user/caf%C3%A9', index: 0, params: { id: 'café' } }
```
**Note:** Setting `decode: false` disables the "splitting" behavior of repeated parameters, which is useful if you need the exactly matched parameter back.
### Compile ("Reverse" Path-To-RegExp)
The `compile` function will return a function for transforming parameters into a valid path:
- **path** A string.
- **options** _(optional)_ Similar to `pathToRegexp` (`delimiter`, `encodePath`, `sensitive`, and `loose`), plus:
- **validate** When `false` the function can produce an invalid (unmatched) path. (default: `true`)
- **options** (See [parse](#parse) for more options)
- **encode** Function for encoding input strings for output into the path, or `false` to disable entirely. (default: `encodeURIComponent`)

@@ -231,16 +95,11 @@

// When disabling `encode`, you need to make sure inputs are encoded correctly. No arrays are accepted.
const toPathRaw = compile("/user/:id", { encode: false });
const toPathRepeated = compile("/*segment");
toPathRaw({ id: "%3A%2F" }); //=> "/user/%3A%2F"
toPathRaw({ id: ":/" }); //=> Throws, "/user/:/" when `validate` is `false`.
const toPathRepeated = compile("{/:segment}+");
toPathRepeated({ segment: ["foo"] }); //=> "/foo"
toPathRepeated({ segment: ["a", "b", "c"] }); //=> "/a/b/c"
const toPathRegexp = compile("/user/:id(\\d+)");
// When disabling `encode`, you need to make sure inputs are encoded correctly. No arrays are accepted.
const toPathRaw = compile("/user/:id", { encode: false });
toPathRegexp({ id: "123" }); //=> "/user/123"
toPathRaw({ id: "%3A%2F" }); //=> "/user/%3A%2F"
```

@@ -250,25 +109,21 @@

- If you are rewriting paths with match and compiler, consider using `encode: false` and `decode: false` to keep raw paths passed around.
- To ensure matches work on paths containing characters usually encoded, consider using [encodeurl](https://github.com/pillarjs/encodeurl) for `encodePath`.
- If matches are intended to be exact, you need to set `loose: false`, `trailing: false`, and `sensitive: true`.
- Enable `strict: true` to detect ReDOS issues.
- If you are rewriting paths with match and compile, consider using `encode: false` and `decode: false` to keep raw paths passed around.
- To ensure matches work on paths containing characters usually encoded, such as emoji, consider using [encodeurl](https://github.com/pillarjs/encodeurl) for `encodePath`.
### Parse
A `parse` function is available and returns `TokenData`, the set of tokens and other metadata parsed from the input string. `TokenData` is can passed directly into `pathToRegexp`, `match`, and `compile`. It accepts only two options, `delimiter` and `encodePath`, which makes those options redundant in the above methods.
The `parse` function accepts a string and returns `TokenData`, the set of tokens and other metadata parsed from the input string. `TokenData` is can used with `match` and `compile`.
- **path** A string.
- **options** _(optional)_
- **delimiter** The default delimiter for segments, e.g. `[^/]` for `:named` parameters. (default: `'/'`)
- **encodePath** A function for encoding input strings. (default: `x => x`, recommended: [`encodeurl`](https://github.com/pillarjs/encodeurl))
### Tokens
The `tokens` returned by `TokenData` is an array of strings or keys, represented as objects, with the following properties:
`TokenData` is a sequence of tokens, currently of types `text`, `parameter`, `wildcard`, or `group`.
- `name` The name of the token
- `prefix` _(optional)_ The prefix string for the segment (e.g. `"/"`)
- `suffix` _(optional)_ The suffix string for the segment (e.g. `""`)
- `pattern` _(optional)_ The pattern defined to match this token
- `modifier` _(optional)_ The modifier character used for the segment (e.g. `?`)
- `separator` _(optional)_ The string used to separate repeated parameters
### Custom path
In some applications, you may not be able to use the `path-to-regexp` syntax (e.g. file-based routing), but you can still use this library for `match`, `compile`, and `pathToRegexp` by building your own `TokenData` instance. For example:
In some applications, you may not be able to use the `path-to-regexp` syntax, but still want to use this library for `match` and `compile`. For example:

@@ -278,4 +133,7 @@ ```js

const tokens = ["/", { name: "foo" }];
const path = new TokenData(tokens, "/");
const tokens = [
{ type: "text", value: "/" },
{ type: "parameter", name: "foo" },
];
const path = new TokenData(tokens);
const fn = match(path);

@@ -290,18 +148,22 @@

### Unexpected `?`, `*`, or `+`
### Unexpected `?` or `+`
In previous major versions `/` and `.` were used as implicit prefixes of parameters. So `/:key?` was implicitly `{/:key}?`. For example:
In past releases, `?`, `*`, and `+` were used to denote optional or repeating parameters. As an alternative, try these:
- `/:key?` → `{/:key}?` or `/:key*` → `{/:key}*` or `/:key+` → `{/:key}+`
- `.:key?` → `{.:key}?` or `.:key*` → `{.:key}*` or `.:key+` → `{.:key}+`
- `:key?` → `{:key}?` or `:key*` → `{:key}*` or `:key+` → `{:key}+`
- For optional (`?`), use an empty segment in a group such as `/:file{.:ext}`.
- For repeating (`+`), only wildcard matching is supported, such as `/*path`.
- For optional repeating (`*`), use a group and a wildcard parameter such as `/files{/*path}`.
### Unexpected `;`
### Unexpected `(`, `)`, `[`, `]`, etc.
Used as a [custom separator](#custom-separator) for repeated parameters.
Previous versions of Path-to-RegExp used these for RegExp features. This version no longer supports them so they've been reserved to avoid ambiguity. To use these characters literally, escape them with a backslash, e.g. `"\\("`.
### Unexpected `!`, `@`, or `,`
### Missing parameter name
These characters have been reserved for future use.
Parameter names, the part after `:` or `*`, must be a valid JavaScript identifier. For example, it cannot start with a number or contain a dash. If you want a parameter name that uses these characters you can wrap the name in quotes, e.g. `:"my-name"`.
### Unterminated quote
Parameter names can be wrapped in double quote characters, and this error means you forgot to close the quote character.
### Express <= 4.x

@@ -311,7 +173,6 @@

- The only part of the string that is a regex is within `()`.
- In Express.js 4.x, everything was passed as-is after a simple replacement, so you could write `/[a-z]+` to match `/test`.
- The `?` optional character must be used after `{}`.
- Regexp characters can no longer be provided.
- The optional character `?` is no longer supported, use braces instead: `/:file{.:ext}`.
- Some characters have new meaning or have been reserved (`{}?*+@!;`).
- The parameter name now supports all unicode identifier characters, previously it was only `[a-z0-9]`.
- The parameter name now supports all JavaScript identifier characters, previously it was only `[a-z0-9]`.

@@ -318,0 +179,0 @@ ## License

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc