@ztimson/utils 0.27.10 → 0.27.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +159 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +159 -46
- package/dist/index.mjs.map +1 -1
- package/dist/path-events.d.ts +37 -10
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1919,10 +1919,12 @@ ${opts.message || this.desc}`;
|
|
|
1919
1919
|
__publicField(this, "fullPath");
|
|
1920
1920
|
/** Path including the name, excluding the module */
|
|
1921
1921
|
__publicField(this, "path");
|
|
1922
|
-
/** Last
|
|
1922
|
+
/** Last segment of path */
|
|
1923
1923
|
__publicField(this, "name");
|
|
1924
1924
|
/** List of methods */
|
|
1925
1925
|
__publicField(this, "methods");
|
|
1926
|
+
/** Whether this path contains glob patterns */
|
|
1927
|
+
__publicField(this, "hasGlob");
|
|
1926
1928
|
if (typeof e == "object") {
|
|
1927
1929
|
Object.assign(this, e);
|
|
1928
1930
|
return;
|
|
@@ -1931,17 +1933,34 @@ ${opts.message || this.desc}`;
|
|
|
1931
1933
|
Object.assign(this, _PathEvent.pathEventCache.get(e));
|
|
1932
1934
|
return;
|
|
1933
1935
|
}
|
|
1934
|
-
let [p,
|
|
1935
|
-
if (!method) method =
|
|
1936
|
-
if (p
|
|
1937
|
-
|
|
1938
|
-
|
|
1936
|
+
let [p, method] = e.replaceAll(/\/{2,}/g, "/").split(":");
|
|
1937
|
+
if (!method) method = "*";
|
|
1938
|
+
if (p === "" || p === void 0) {
|
|
1939
|
+
this.module = "";
|
|
1940
|
+
this.path = "";
|
|
1941
|
+
this.fullPath = "";
|
|
1942
|
+
this.name = "";
|
|
1943
|
+
this.methods = new ASet(["n"]);
|
|
1944
|
+
this.hasGlob = false;
|
|
1945
|
+
_PathEvent.pathEventCache.set(e, this);
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
if (p === "*") {
|
|
1949
|
+
this.module = "";
|
|
1950
|
+
this.path = "";
|
|
1951
|
+
this.fullPath = "**";
|
|
1952
|
+
this.name = "";
|
|
1953
|
+
this.methods = new ASet(["*"]);
|
|
1954
|
+
this.hasGlob = true;
|
|
1955
|
+
_PathEvent.pathEventCache.set(e, this);
|
|
1956
|
+
return;
|
|
1939
1957
|
}
|
|
1940
1958
|
let temp = p.split("/").filter((p2) => !!p2);
|
|
1941
1959
|
this.module = temp.splice(0, 1)[0] || "";
|
|
1942
1960
|
this.path = temp.join("/");
|
|
1943
1961
|
this.fullPath = `${this.module}${this.module && this.path ? "/" : ""}${this.path}`;
|
|
1944
1962
|
this.name = temp.pop() || "";
|
|
1963
|
+
this.hasGlob = this.fullPath.includes("*");
|
|
1945
1964
|
this.methods = new ASet(method.split(""));
|
|
1946
1965
|
_PathEvent.pathEventCache.set(e, this);
|
|
1947
1966
|
}
|
|
@@ -1966,6 +1985,13 @@ ${opts.message || this.desc}`;
|
|
|
1966
1985
|
set create(v) {
|
|
1967
1986
|
v ? this.methods.delete("n").delete("*").add("c") : this.methods.delete("c");
|
|
1968
1987
|
}
|
|
1988
|
+
/** Execute method specified */
|
|
1989
|
+
get execute() {
|
|
1990
|
+
return !this.methods.has("n") && (this.methods.has("*") || this.methods.has("x"));
|
|
1991
|
+
}
|
|
1992
|
+
set execute(v) {
|
|
1993
|
+
v ? this.methods.delete("n").delete("*").add("x") : this.methods.delete("x");
|
|
1994
|
+
}
|
|
1969
1995
|
/** Read method specified */
|
|
1970
1996
|
get read() {
|
|
1971
1997
|
return !this.methods.has("n") && (this.methods.has("*") || this.methods.has("r"));
|
|
@@ -1991,6 +2017,64 @@ ${opts.message || this.desc}`;
|
|
|
1991
2017
|
static clearCache() {
|
|
1992
2018
|
_PathEvent.pathEventCache.clear();
|
|
1993
2019
|
}
|
|
2020
|
+
/** Clear the permission cache */
|
|
2021
|
+
static clearPermissionCache() {
|
|
2022
|
+
_PathEvent.permissionCache.clear();
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Score a path for specificity ranking (lower = more specific = higher priority)
|
|
2026
|
+
* @private
|
|
2027
|
+
*/
|
|
2028
|
+
static scoreSpecificity(path) {
|
|
2029
|
+
if (path === "**" || path === "") return Number.MAX_SAFE_INTEGER;
|
|
2030
|
+
const segments = path.split("/").filter((p) => !!p);
|
|
2031
|
+
let score = -segments.length;
|
|
2032
|
+
segments.forEach((seg) => {
|
|
2033
|
+
if (seg === "**") score += 0.5;
|
|
2034
|
+
else if (seg === "*") score += 0.25;
|
|
2035
|
+
});
|
|
2036
|
+
return score;
|
|
2037
|
+
}
|
|
2038
|
+
/**
|
|
2039
|
+
* Check if a path matches a glob pattern
|
|
2040
|
+
* @private
|
|
2041
|
+
*/
|
|
2042
|
+
static pathMatchesGlob(path, pattern) {
|
|
2043
|
+
if (pattern === path) return true;
|
|
2044
|
+
const pathParts = path.split("/").filter((p) => !!p);
|
|
2045
|
+
const patternParts = pattern.split("/").filter((p) => !!p);
|
|
2046
|
+
let pathIdx = 0;
|
|
2047
|
+
let patternIdx = 0;
|
|
2048
|
+
while (patternIdx < patternParts.length && pathIdx < pathParts.length) {
|
|
2049
|
+
const patternPart = patternParts[patternIdx];
|
|
2050
|
+
if (patternPart === "**") {
|
|
2051
|
+
if (patternIdx === patternParts.length - 1) {
|
|
2052
|
+
return true;
|
|
2053
|
+
}
|
|
2054
|
+
patternParts[patternIdx + 1];
|
|
2055
|
+
while (pathIdx < pathParts.length) {
|
|
2056
|
+
if (_PathEvent.pathMatchesGlob(pathParts.slice(pathIdx).join("/"), patternParts.slice(patternIdx + 1).join("/"))) {
|
|
2057
|
+
return true;
|
|
2058
|
+
}
|
|
2059
|
+
pathIdx++;
|
|
2060
|
+
}
|
|
2061
|
+
return false;
|
|
2062
|
+
} else if (patternPart === "*") {
|
|
2063
|
+
pathIdx++;
|
|
2064
|
+
patternIdx++;
|
|
2065
|
+
} else {
|
|
2066
|
+
if (patternPart !== pathParts[pathIdx]) {
|
|
2067
|
+
return false;
|
|
2068
|
+
}
|
|
2069
|
+
pathIdx++;
|
|
2070
|
+
patternIdx++;
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
if (patternIdx < patternParts.length) {
|
|
2074
|
+
return patternParts.slice(patternIdx).every((p) => p === "**");
|
|
2075
|
+
}
|
|
2076
|
+
return pathIdx === pathParts.length;
|
|
2077
|
+
}
|
|
1994
2078
|
/**
|
|
1995
2079
|
* Combine multiple events into one parsed object. Longest path takes precedent, but all subsequent methods are
|
|
1996
2080
|
* combined until a "none" is reached
|
|
@@ -1999,38 +2083,58 @@ ${opts.message || this.desc}`;
|
|
|
1999
2083
|
* @return {PathEvent} Final combined permission
|
|
2000
2084
|
*/
|
|
2001
2085
|
static combine(...paths) {
|
|
2002
|
-
|
|
2003
|
-
const
|
|
2004
|
-
const
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
if (
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2086
|
+
const parsed = paths.map((p) => p instanceof _PathEvent ? p : new _PathEvent(p));
|
|
2087
|
+
const sorted = parsed.toSorted((p1, p2) => {
|
|
2088
|
+
const score1 = _PathEvent.scoreSpecificity(p1.fullPath);
|
|
2089
|
+
const score2 = _PathEvent.scoreSpecificity(p2.fullPath);
|
|
2090
|
+
return score1 - score2;
|
|
2091
|
+
});
|
|
2092
|
+
let result = null;
|
|
2093
|
+
for (const p of sorted) {
|
|
2094
|
+
if (!result) {
|
|
2095
|
+
result = p;
|
|
2096
|
+
} else {
|
|
2097
|
+
if (result.fullPath.startsWith(p.fullPath)) {
|
|
2098
|
+
if (p.none) {
|
|
2099
|
+
break;
|
|
2100
|
+
}
|
|
2101
|
+
result.methods = new ASet([...result.methods, ...p.methods]);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
return result || new _PathEvent("");
|
|
2015
2106
|
}
|
|
2016
2107
|
/**
|
|
2017
2108
|
* Filter a set of paths based on the target
|
|
2018
2109
|
*
|
|
2019
2110
|
* @param {string | PathEvent | (string | PathEvent)[]} target Array of events that will filtered
|
|
2020
|
-
* @param filter {...PathEvent} Must
|
|
2021
|
-
* @return {
|
|
2111
|
+
* @param filter {...PathEvent} Must contain one of
|
|
2112
|
+
* @return {PathEvent[]} Filtered results
|
|
2022
2113
|
*/
|
|
2023
2114
|
static filter(target, ...filter) {
|
|
2024
2115
|
const parsedTarget = makeArray(target).map((pe) => pe instanceof _PathEvent ? pe : new _PathEvent(pe));
|
|
2025
2116
|
const parsedFilter = makeArray(filter).map((pe) => pe instanceof _PathEvent ? pe : new _PathEvent(pe));
|
|
2026
|
-
return parsedTarget.filter((t) =>
|
|
2027
|
-
const
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2117
|
+
return parsedTarget.filter((t) => {
|
|
2118
|
+
const combined = _PathEvent.combine(t);
|
|
2119
|
+
return !!parsedFilter.find((r) => _PathEvent.matches(r, combined));
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
* Check if a filter pattern matches a target path
|
|
2124
|
+
* @private
|
|
2125
|
+
*/
|
|
2126
|
+
static matches(pattern, target) {
|
|
2127
|
+
if (pattern.fullPath === "" || target.fullPath === "") return false;
|
|
2128
|
+
if (pattern.fullPath === "*" || target.fullPath === "*") return pattern.methods.has("*") || target.methods.has("*") || pattern.methods.intersection(target.methods).length > 0;
|
|
2129
|
+
const methodsMatch = pattern.all || target.all || pattern.methods.intersection(target.methods).length > 0;
|
|
2130
|
+
if (!methodsMatch) return false;
|
|
2131
|
+
if (!pattern.hasGlob && !target.hasGlob) {
|
|
2132
|
+
return pattern.fullPath === target.fullPath;
|
|
2133
|
+
}
|
|
2134
|
+
if (pattern.hasGlob) {
|
|
2135
|
+
return this.pathMatchesGlob(target.fullPath, pattern.fullPath);
|
|
2136
|
+
}
|
|
2137
|
+
return this.pathMatchesGlob(pattern.fullPath, target.fullPath);
|
|
2034
2138
|
}
|
|
2035
2139
|
/**
|
|
2036
2140
|
* Squash 2 sets of paths & return true if any overlap is found
|
|
@@ -2042,21 +2146,15 @@ ${opts.message || this.desc}`;
|
|
|
2042
2146
|
static has(target, ...has) {
|
|
2043
2147
|
const parsedTarget = makeArray(target).map((pe) => pe instanceof _PathEvent ? pe : new _PathEvent(pe));
|
|
2044
2148
|
const parsedRequired = makeArray(has).map((pe) => pe instanceof _PathEvent ? pe : new _PathEvent(pe));
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
const p1 = r.fullPath.includes("*") ? r.fullPath.slice(0, r.fullPath.indexOf("*")) : r.fullPath;
|
|
2048
|
-
const p2 = t.fullPath.includes("*") ? t.fullPath.slice(0, t.fullPath.indexOf("*")) : t.fullPath;
|
|
2049
|
-
const scope = p1.startsWith(p2);
|
|
2050
|
-
const methods = r.all || t.all || r.methods.intersection(t.methods).length;
|
|
2051
|
-
return (wildcard || scope) && methods;
|
|
2052
|
-
}));
|
|
2149
|
+
const effectiveTarget = parsedTarget.length === 1 ? parsedTarget[0] : _PathEvent.combine(...parsedTarget);
|
|
2150
|
+
return !!parsedRequired.find((r) => _PathEvent.matches(r, effectiveTarget));
|
|
2053
2151
|
}
|
|
2054
2152
|
/**
|
|
2055
2153
|
* Squash 2 sets of paths & return true if the target has all paths
|
|
2056
2154
|
*
|
|
2057
2155
|
* @param {string | PathEvent | (string | PathEvent)[]} target Array of Events as strings or pre-parsed
|
|
2058
2156
|
* @param has Target must have all these paths
|
|
2059
|
-
* @return {boolean} Whether
|
|
2157
|
+
* @return {boolean} Whether all are present
|
|
2060
2158
|
*/
|
|
2061
2159
|
static hasAll(target, ...has) {
|
|
2062
2160
|
return has.filter((h) => _PathEvent.has(target, h)).length == has.length;
|
|
@@ -2064,7 +2162,7 @@ ${opts.message || this.desc}`;
|
|
|
2064
2162
|
/**
|
|
2065
2163
|
* Same as `has` but raises an error if there is no overlap
|
|
2066
2164
|
*
|
|
2067
|
-
* @param {string | string[]} target Array of Events as strings or pre-parsed
|
|
2165
|
+
* @param {string | PathEvent | (string | PathEvent)[]} target Array of Events as strings or pre-parsed
|
|
2068
2166
|
* @param has Target must have at least one of these path
|
|
2069
2167
|
*/
|
|
2070
2168
|
static hasFatal(target, ...has) {
|
|
@@ -2073,7 +2171,7 @@ ${opts.message || this.desc}`;
|
|
|
2073
2171
|
/**
|
|
2074
2172
|
* Same as `hasAll` but raises an error if the target is missing any paths
|
|
2075
2173
|
*
|
|
2076
|
-
* @param {string | string[]} target Array of Events as strings or pre-parsed
|
|
2174
|
+
* @param {string | PathEvent | (string | PathEvent)[]} target Array of Events as strings or pre-parsed
|
|
2077
2175
|
* @param has Target must have all these paths
|
|
2078
2176
|
*/
|
|
2079
2177
|
static hasAllFatal(target, ...has) {
|
|
@@ -2105,7 +2203,7 @@ ${opts.message || this.desc}`;
|
|
|
2105
2203
|
* Squash 2 sets of paths & return true if the target has all paths
|
|
2106
2204
|
*
|
|
2107
2205
|
* @param has Target must have all these paths
|
|
2108
|
-
* @return {boolean} Whether
|
|
2206
|
+
* @return {boolean} Whether all are present
|
|
2109
2207
|
*/
|
|
2110
2208
|
hasAll(...has) {
|
|
2111
2209
|
return _PathEvent.hasAll(this, ...has);
|
|
@@ -2130,7 +2228,7 @@ ${opts.message || this.desc}`;
|
|
|
2130
2228
|
* Filter a set of paths based on this event
|
|
2131
2229
|
*
|
|
2132
2230
|
* @param {string | PathEvent | (string | PathEvent)[]} target Array of events that will filtered
|
|
2133
|
-
* @return {
|
|
2231
|
+
* @return {PathEvent[]} Filtered results
|
|
2134
2232
|
*/
|
|
2135
2233
|
filter(target) {
|
|
2136
2234
|
return _PathEvent.filter(target, this);
|
|
@@ -2146,6 +2244,10 @@ ${opts.message || this.desc}`;
|
|
|
2146
2244
|
};
|
|
2147
2245
|
/** Internal cache for PathEvent instances to avoid redundant parsing */
|
|
2148
2246
|
__publicField(_PathEvent, "pathEventCache", /* @__PURE__ */ new Map());
|
|
2247
|
+
/** Cache for compiled permissions (path + required permissions → result) */
|
|
2248
|
+
__publicField(_PathEvent, "permissionCache", /* @__PURE__ */ new Map());
|
|
2249
|
+
/** Max size for permission cache before LRU eviction */
|
|
2250
|
+
__publicField(_PathEvent, "MAX_PERMISSION_CACHE_SIZE", 1e3);
|
|
2149
2251
|
let PathEvent = _PathEvent;
|
|
2150
2252
|
class PathEventEmitter {
|
|
2151
2253
|
constructor(prefix = "") {
|
|
@@ -2154,16 +2256,27 @@ ${opts.message || this.desc}`;
|
|
|
2154
2256
|
}
|
|
2155
2257
|
emit(event, ...args) {
|
|
2156
2258
|
const parsed = event instanceof PathEvent ? event : new PathEvent(`${this.prefix}/${event}`);
|
|
2157
|
-
this.listeners.filter((l) => PathEvent.has(l[0], parsed)).forEach(
|
|
2259
|
+
this.listeners.filter((l) => PathEvent.has(l[0], parsed)).forEach((l) => l[1](parsed, ...args));
|
|
2158
2260
|
}
|
|
2159
2261
|
off(listener) {
|
|
2160
2262
|
this.listeners = this.listeners.filter((l) => l[1] != listener);
|
|
2161
2263
|
}
|
|
2162
2264
|
on(event, listener) {
|
|
2163
2265
|
makeArray(event).forEach((e) => {
|
|
2164
|
-
|
|
2266
|
+
let fullEvent;
|
|
2267
|
+
if (typeof e === "string") {
|
|
2268
|
+
if (e[0] === ":" && this.prefix) {
|
|
2269
|
+
fullEvent = `${this.prefix}${e}`;
|
|
2270
|
+
} else if (this.prefix) {
|
|
2271
|
+
fullEvent = `${this.prefix}/${e}`;
|
|
2272
|
+
} else {
|
|
2273
|
+
fullEvent = e;
|
|
2274
|
+
}
|
|
2275
|
+
} else {
|
|
2276
|
+
fullEvent = e instanceof PathEvent ? PathEvent.toString(e.fullPath, e.methods) : e;
|
|
2277
|
+
}
|
|
2165
2278
|
this.listeners.push([
|
|
2166
|
-
|
|
2279
|
+
new PathEvent(fullEvent),
|
|
2167
2280
|
listener
|
|
2168
2281
|
]);
|
|
2169
2282
|
});
|
|
@@ -2179,7 +2292,7 @@ ${opts.message || this.desc}`;
|
|
|
2179
2292
|
});
|
|
2180
2293
|
}
|
|
2181
2294
|
relayEvents(emitter) {
|
|
2182
|
-
emitter.on("
|
|
2295
|
+
emitter.on("**", (event, ...args) => this.emit(event, ...args));
|
|
2183
2296
|
}
|
|
2184
2297
|
}
|
|
2185
2298
|
function search(rows, search2, regex, transform = (r) => r) {
|