Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
/**
* Glob-style field-path matcher for masking rule evaluation.
*
* Supports three matching modes that cover the common cases for nested-object
* traversal:
* - Exact match (`"user.email"` → `"user.email"`)
* - Single-segment wildcard (`*`) — matches any one dot-separated segment
* - Multi-segment wildcard (`**`) — matches any number of dot-separated
* segments (including zero when at the end of a pattern)
*/
/**
* Test whether a concrete `fieldPath` matches the supplied glob `pattern`.
*
* Rules:
* - `*` matches exactly one path segment (characters between dots).
* - `**` matches zero or more path segments.
* - All other characters must match literally (case-sensitive).
* - Leading/trailing dots are treated as empty segments and behave
* consistently with the wildcard rules above.
*
* @param fieldPath - The concrete dot-notation path, e.g. `"user.address.city"`.
* @param pattern - The glob pattern to test against, e.g. `"user.*"` or `"**.email"`.
* @returns `true` when `fieldPath` satisfies `pattern`, `false` otherwise.
*
* @example
* ```ts
* matchPath('user.email', 'user.email'); // true — exact
* matchPath('user.email', 'user.*'); // true — single-segment wildcard
* matchPath('user.address.city', 'user.**'); // true — multi-segment wildcard
* matchPath('billing.email', '**.email'); // true — leading multi-segment
* matchPath('user.name', 'user.email'); // false — no match
* ```
*/
export function matchPath(fieldPath: string, pattern: string): boolean {
const pathSegs = fieldPath.split(".");
const patSegs = pattern.split(".");
return matchSegments(pathSegs, 0, patSegs, 0);
}
/**
* Recursive backtracking matcher that aligns path segments with pattern
* segments, handling `**` greedily with fall-back.
*/
function matchSegments(
path: readonly string[],
pi: number,
pat: readonly string[],
si: number
): boolean {
// Both exhausted → match.
if (pi === path.length && si === pat.length) return true;
// Pattern exhausted but path has remaining segments → no match.
if (si === pat.length) return false;
const seg = pat[si];
if (seg === "**") {
// `**` can match zero or more path segments.
// Try consuming zero segments (advance only pattern).
if (matchSegments(path, pi, pat, si + 1)) return true;
// Try consuming one path segment at a time.
for (let consumed = pi; consumed < path.length; consumed++) {
if (matchSegments(path, consumed + 1, pat, si + 1)) return true;
}
return false;
}
// Path is exhausted but pattern still has non-`**` segments → no match.
if (pi === path.length) return false;
// `*` matches any single segment.
if (seg === "*") {
return matchSegments(path, pi + 1, pat, si + 1);
}
// Literal segment — must match exactly.
if (path[pi] === seg) {
return matchSegments(path, pi + 1, pat, si + 1);
}
return false;
}