- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
247 lines
6.2 KiB
TypeScript
247 lines
6.2 KiB
TypeScript
/* https://github.com/axtgr/wildcard-match */
|
|
|
|
/**
|
|
* Escapes a character if it has a special meaning in regular expressions
|
|
* and returns the character as is if it doesn't
|
|
*/
|
|
function escapeRegExpChar(char: string) {
|
|
if (
|
|
char === "-" ||
|
|
char === "^" ||
|
|
char === "$" ||
|
|
char === "+" ||
|
|
char === "." ||
|
|
char === "(" ||
|
|
char === ")" ||
|
|
char === "|" ||
|
|
char === "[" ||
|
|
char === "]" ||
|
|
char === "{" ||
|
|
char === "}" ||
|
|
char === "*" ||
|
|
char === "?" ||
|
|
char === "\\"
|
|
) {
|
|
return `\\${char}`;
|
|
} else {
|
|
return char;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Escapes all characters in a given string that have a special meaning in regular expressions
|
|
*/
|
|
function escapeRegExpString(str: string) {
|
|
let result = "";
|
|
for (const char of str) {
|
|
result += escapeRegExpChar(char);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Transforms one or more glob patterns into a RegExp pattern
|
|
*/
|
|
function transform(
|
|
pattern: string | string[],
|
|
separator: string | boolean = true,
|
|
): string {
|
|
if (Array.isArray(pattern)) {
|
|
const regExpPatterns = pattern.map((p) => `^${transform(p, separator)}$`);
|
|
return `(?:${regExpPatterns.join("|")})`;
|
|
}
|
|
|
|
let separatorSplitter = "";
|
|
let separatorMatcher = "";
|
|
let wildcard = ".";
|
|
|
|
if (separator === true) {
|
|
// In this case forward slashes in patterns match both forward and backslashes in samples:
|
|
//
|
|
// `foo/bar` will match `foo/bar`
|
|
// will match `foo\bar`
|
|
//
|
|
separatorSplitter = "/";
|
|
separatorMatcher = "[/\\\\]";
|
|
wildcard = "[^/\\\\]";
|
|
} else if (separator) {
|
|
separatorSplitter = separator;
|
|
separatorMatcher = escapeRegExpString(separatorSplitter);
|
|
|
|
if (separatorMatcher.length > 1) {
|
|
separatorMatcher = `(?:${separatorMatcher})`;
|
|
wildcard = `((?!${separatorMatcher}).)`;
|
|
} else {
|
|
wildcard = `[^${separatorMatcher}]`;
|
|
}
|
|
}
|
|
|
|
// When a separator is explicitly specified in a pattern,
|
|
// it MUST match ONE OR MORE separators in a sample:
|
|
//
|
|
// `foo/bar/` will match `foo//bar///`
|
|
// won't match `foo/bar`
|
|
//
|
|
// When a pattern doesn't have a trailing separator,
|
|
// a sample can still optionally have them:
|
|
//
|
|
// `foo/bar` will match `foo/bar//`
|
|
//
|
|
// So we use different quantifiers depending on the index of a segment.
|
|
const requiredSeparator = separator ? `${separatorMatcher}+?` : "";
|
|
const optionalSeparator = separator ? `${separatorMatcher}*?` : "";
|
|
|
|
const segments = separator ? pattern.split(separatorSplitter) : [pattern];
|
|
let result = "";
|
|
|
|
for (let s = 0; s < segments.length; s++) {
|
|
const segment = segments[s]!;
|
|
const nextSegment = segments[s + 1]!;
|
|
let currentSeparator = "";
|
|
|
|
if (!segment && s > 0) {
|
|
continue;
|
|
}
|
|
|
|
if (separator) {
|
|
if (s === segments.length - 1) {
|
|
currentSeparator = optionalSeparator;
|
|
} else if (nextSegment !== "**") {
|
|
currentSeparator = requiredSeparator;
|
|
} else {
|
|
currentSeparator = "";
|
|
}
|
|
}
|
|
|
|
if (separator && segment === "**") {
|
|
if (currentSeparator) {
|
|
result += s === 0 ? "" : currentSeparator;
|
|
result += `(?:${wildcard}*?${currentSeparator})*?`;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
for (let c = 0; c < segment.length; c++) {
|
|
const char = segment[c]!;
|
|
|
|
if (char === "\\") {
|
|
if (c < segment.length - 1) {
|
|
result += escapeRegExpChar(segment[c + 1]!);
|
|
c++;
|
|
}
|
|
} else if (char === "?") {
|
|
result += wildcard;
|
|
} else if (char === "*") {
|
|
result += `${wildcard}*?`;
|
|
} else {
|
|
result += escapeRegExpChar(char);
|
|
}
|
|
}
|
|
|
|
result += currentSeparator;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
export default transform;
|
|
|
|
interface WildcardMatchOptions {
|
|
/** Separator to be used to split patterns and samples into segments */
|
|
separator?: string | boolean;
|
|
|
|
/** Flags to pass to the RegExp */
|
|
flags?: string;
|
|
}
|
|
|
|
// This overrides the function's signature because for the end user
|
|
// the function is always bound to a RegExp
|
|
interface isMatch {
|
|
/**
|
|
* Tests if a sample string matches the pattern(s)
|
|
*
|
|
* ```js
|
|
* isMatch('foo') //=> true
|
|
* ```
|
|
*/
|
|
(sample: string): boolean;
|
|
|
|
/** Compiled regular expression */
|
|
regexp: RegExp;
|
|
|
|
/** Original pattern or array of patterns that was used to compile the RegExp */
|
|
pattern: string | string[];
|
|
|
|
/** Options that were used to compile the RegExp */
|
|
options: WildcardMatchOptions;
|
|
}
|
|
|
|
function isMatch(regexp: RegExp, sample: string) {
|
|
if (typeof sample !== "string") {
|
|
throw new TypeError(`Sample must be a string, but ${typeof sample} given`);
|
|
}
|
|
|
|
return regexp.test(sample);
|
|
}
|
|
|
|
/**
|
|
* Compiles one or more glob patterns into a RegExp and returns an isMatch function.
|
|
* The isMatch function takes a sample string as its only argument and returns `true`
|
|
* if the string matches the pattern(s).
|
|
*
|
|
* ```js
|
|
* wildcardMatch('src/*.js')('src/index.js') //=> true
|
|
* ```
|
|
*
|
|
* ```js
|
|
* const isMatch = wildcardMatch('*.example.com', '.')
|
|
* isMatch('foo.example.com') //=> true
|
|
* isMatch('foo.bar.com') //=> false
|
|
* ```
|
|
*/
|
|
function wildcardMatch(
|
|
pattern: string | string[],
|
|
options?: string | boolean | WildcardMatchOptions,
|
|
) {
|
|
if (typeof pattern !== "string" && !Array.isArray(pattern)) {
|
|
throw new TypeError(
|
|
`The first argument must be a single pattern string or an array of patterns, but ${typeof pattern} given`,
|
|
);
|
|
}
|
|
|
|
if (typeof options === "string" || typeof options === "boolean") {
|
|
options = { separator: options };
|
|
}
|
|
|
|
if (
|
|
arguments.length === 2 &&
|
|
!(
|
|
typeof options === "undefined" ||
|
|
(typeof options === "object" && !Array.isArray(options))
|
|
)
|
|
) {
|
|
throw new TypeError(
|
|
`The second argument must be an options object or a string/boolean separator, but ${typeof options} given`,
|
|
);
|
|
}
|
|
|
|
options = options ?? {};
|
|
|
|
if (options.separator === "\\") {
|
|
throw new Error(
|
|
"\\ is not a valid separator because it is used for escaping. Try setting the separator to `true` instead",
|
|
);
|
|
}
|
|
|
|
const regexpPattern = transform(pattern, options.separator);
|
|
const regexp = new RegExp(`^${regexpPattern}$`, options.flags);
|
|
|
|
const fn = isMatch.bind(null, regexp) as isMatch;
|
|
fn.options = options;
|
|
fn.pattern = pattern;
|
|
fn.regexp = regexp;
|
|
return fn;
|
|
}
|
|
|
|
export { wildcardMatch, isMatch };
|