@web-ts-toolkit/access-router 0.44.0 → 0.45.0
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/README.md +77 -2
- package/advanced.d.mts +655 -19
- package/advanced.d.ts +655 -19
- package/advanced.js +322 -5
- package/advanced.mjs +322 -5
- package/index.d.mts +2 -2
- package/index.d.ts +2 -2
- package/index.js +1448 -197
- package/index.mjs +1323 -70
- package/llms.txt +16 -0
- package/package.json +3 -3
- package/{parsers-B5dbVmjm.d.mts → parsers-DppKW-Fd.d.mts} +79 -5
- package/{parsers-B5dbVmjm.d.ts → parsers-DppKW-Fd.d.ts} +79 -5
- package/processors.d.mts +151 -1
- package/processors.d.ts +151 -1
- package/processors.js +170 -6
- package/processors.mjs +165 -6
package/index.mjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { isPlainObject as
|
|
2
|
+
import { isPlainObject as isPlainObject20, isString as isString10, isUndefined as isUndefined4 } from "@web-ts-toolkit/utils";
|
|
3
3
|
|
|
4
4
|
// src/middleware.ts
|
|
5
5
|
import JsonRouter2 from "@web-ts-toolkit/express-json-router";
|
|
6
|
-
import { isArray as
|
|
6
|
+
import { isArray as isArray12, isFunction as isFunction6, isPlainObject as isPlainObject16, isString as isString7 } from "@web-ts-toolkit/utils";
|
|
7
7
|
|
|
8
8
|
// src/core.ts
|
|
9
9
|
import {
|
|
10
10
|
castArray as castArray4,
|
|
11
11
|
compact as compact3,
|
|
12
12
|
forEach as forEach7,
|
|
13
|
-
isArray as
|
|
13
|
+
isArray as isArray11,
|
|
14
14
|
isBoolean as isBoolean5,
|
|
15
15
|
isFunction as isFunction5,
|
|
16
16
|
isString as isString6,
|
|
@@ -943,7 +943,9 @@ var defaultRequestComplexity = {
|
|
|
943
943
|
maxIncludeCount: 10,
|
|
944
944
|
maxSubQueryCount: 10,
|
|
945
945
|
maxBulkConcurrency: 10,
|
|
946
|
-
maxHookConcurrency: 10
|
|
946
|
+
maxHookConcurrency: 10,
|
|
947
|
+
maxCorrelatedQueries: 100,
|
|
948
|
+
maxCorrelatedDepth: 5
|
|
947
949
|
};
|
|
948
950
|
var normalizePositiveInteger = (value, fallback) => {
|
|
949
951
|
return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : fallback;
|
|
@@ -963,6 +965,14 @@ var resolveRequestComplexity = (options) => ({
|
|
|
963
965
|
maxHookConcurrency: normalizePositiveInteger(
|
|
964
966
|
options?.maxHookConcurrency,
|
|
965
967
|
defaultRequestComplexity.maxHookConcurrency
|
|
968
|
+
),
|
|
969
|
+
maxCorrelatedQueries: normalizePositiveInteger(
|
|
970
|
+
options?.maxCorrelatedQueries,
|
|
971
|
+
defaultRequestComplexity.maxCorrelatedQueries
|
|
972
|
+
),
|
|
973
|
+
maxCorrelatedDepth: normalizePositiveInteger(
|
|
974
|
+
options?.maxCorrelatedDepth,
|
|
975
|
+
defaultRequestComplexity.maxCorrelatedDepth
|
|
966
976
|
)
|
|
967
977
|
});
|
|
968
978
|
function validateRequestComplexity(value, options, scope = "request") {
|
|
@@ -1550,14 +1560,471 @@ var getModelRef = (modelName, refPath) => {
|
|
|
1550
1560
|
// src/services/base.ts
|
|
1551
1561
|
import {
|
|
1552
1562
|
castArray,
|
|
1563
|
+
cloneDeep as cloneDeep2,
|
|
1553
1564
|
compact,
|
|
1554
1565
|
flatten,
|
|
1555
1566
|
forEach as forEach3,
|
|
1556
1567
|
get as get4,
|
|
1557
|
-
isArray as
|
|
1558
|
-
isPlainObject as
|
|
1568
|
+
isArray as isArray7,
|
|
1569
|
+
isPlainObject as isPlainObject12,
|
|
1559
1570
|
uniq
|
|
1560
1571
|
} from "@web-ts-toolkit/utils";
|
|
1572
|
+
|
|
1573
|
+
// src/correlated-includes.ts
|
|
1574
|
+
import { cloneDeep, isArray as isArray6, isPlainObject as isPlainObject11 } from "@web-ts-toolkit/utils";
|
|
1575
|
+
var PARENT_MARKER_KEY = "$parent";
|
|
1576
|
+
var ESCAPE_WRAPPER_KEY = "$escape";
|
|
1577
|
+
var SUBQUERY_PAYLOAD_KEYS = /* @__PURE__ */ new Set(["$$sq", "$$date"]);
|
|
1578
|
+
var MARKER_FORBIDDEN_OPERATOR_KEYS = /* @__PURE__ */ new Set(["$text", "$where", "$comment"]);
|
|
1579
|
+
var LOGICAL_CLAUSE_KEYS = /* @__PURE__ */ new Set(["$and", "$or", "$nor"]);
|
|
1580
|
+
var DANGEROUS_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
1581
|
+
var FIELD_SEGMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_$]*$/;
|
|
1582
|
+
var ARRAY_INDEX_SEGMENT_PATTERN = /^(0|[1-9]\d*)$/;
|
|
1583
|
+
var ownKeys = (value) => Object.keys(value);
|
|
1584
|
+
function isParentRefMarker(value) {
|
|
1585
|
+
return isPlainObject11(value) && ownKeys(value).length === 1 && ownKeys(value)[0] === PARENT_MARKER_KEY;
|
|
1586
|
+
}
|
|
1587
|
+
function isEscapedParentRef(value) {
|
|
1588
|
+
return isPlainObject11(value) && ownKeys(value).length === 1 && ownKeys(value)[0] === ESCAPE_WRAPPER_KEY;
|
|
1589
|
+
}
|
|
1590
|
+
function hasMarkerKey(value) {
|
|
1591
|
+
return ownKeys(value).includes(PARENT_MARKER_KEY) || ownKeys(value).includes(ESCAPE_WRAPPER_KEY);
|
|
1592
|
+
}
|
|
1593
|
+
function isValidParentRefPath(path) {
|
|
1594
|
+
if (typeof path !== "string" || path.length === 0) return false;
|
|
1595
|
+
const segments = path.split(".");
|
|
1596
|
+
return segments.every(
|
|
1597
|
+
(segment) => (FIELD_SEGMENT_PATTERN.test(segment) || ARRAY_INDEX_SEGMENT_PATTERN.test(segment)) && !DANGEROUS_PATH_SEGMENTS.has(segment)
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
function isValidCorrelatedOutputPath(path) {
|
|
1601
|
+
return typeof path === "string" && path.length > 0 && isValidFieldPath(path) && path !== "_id" && !path.startsWith("$");
|
|
1602
|
+
}
|
|
1603
|
+
function containsParentMarkerShape(value) {
|
|
1604
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1605
|
+
const visit = (current) => {
|
|
1606
|
+
if (isParentRefMarker(current) || isEscapedParentRef(current)) return true;
|
|
1607
|
+
if (isArray6(current)) {
|
|
1608
|
+
if (seen.has(current)) return false;
|
|
1609
|
+
seen.add(current);
|
|
1610
|
+
return current.some(visit);
|
|
1611
|
+
}
|
|
1612
|
+
if (isPlainObject11(current)) {
|
|
1613
|
+
if (seen.has(current)) return false;
|
|
1614
|
+
seen.add(current);
|
|
1615
|
+
if (hasMarkerKey(current)) return true;
|
|
1616
|
+
return Object.values(current).some(visit);
|
|
1617
|
+
}
|
|
1618
|
+
return false;
|
|
1619
|
+
};
|
|
1620
|
+
return visit(value);
|
|
1621
|
+
}
|
|
1622
|
+
function scanTemplateValueNode(value, path, issues) {
|
|
1623
|
+
if (isEscapedParentRef(value)) {
|
|
1624
|
+
const inner = value.$escape;
|
|
1625
|
+
if (!isParentRefMarker(inner) || typeof inner.$parent !== "string" || (inner.$parent?.length ?? 0) === 0) {
|
|
1626
|
+
issues.push({
|
|
1627
|
+
message: 'Invalid $escape payload: expected { "$escape": { "$parent": "<field>" } }',
|
|
1628
|
+
path
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
if (isParentRefMarker(value)) {
|
|
1634
|
+
if (!isValidParentRefPath(value.$parent)) {
|
|
1635
|
+
issues.push({
|
|
1636
|
+
message: `Invalid $parent reference path at ${path.length === 0 ? "#" : path.join(".")}`,
|
|
1637
|
+
path
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
if (isPlainObject11(value)) {
|
|
1643
|
+
if (hasMarkerKey(value)) {
|
|
1644
|
+
issues.push({
|
|
1645
|
+
message: 'Malformed $parent reference: objects carrying $parent must be exactly { "$parent": "<field>" }',
|
|
1646
|
+
path
|
|
1647
|
+
});
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
scanTemplateObjectEntries(value, path, issues);
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
if (isArray6(value)) {
|
|
1654
|
+
value.forEach((item, index) => scanTemplateValueNode(item, [...path, index], issues));
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
function scanTemplateObjectEntries(obj, basePath, issues) {
|
|
1658
|
+
for (const [key, child] of Object.entries(obj)) {
|
|
1659
|
+
const childPath = [...basePath, key];
|
|
1660
|
+
if (key === PARENT_MARKER_KEY) {
|
|
1661
|
+
issues.push({ message: "$parent is never a field name in correlated templates", path: childPath });
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
if (SUBQUERY_PAYLOAD_KEYS.has(key)) {
|
|
1665
|
+
if (containsParentMarkerShape(child)) {
|
|
1666
|
+
issues.push({
|
|
1667
|
+
message: `Unsupported $parent reference inside ${key}: markers are not resolved in subquery payloads`,
|
|
1668
|
+
path: childPath
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
if (MARKER_FORBIDDEN_OPERATOR_KEYS.has(key)) {
|
|
1674
|
+
if (containsParentMarkerShape(child)) {
|
|
1675
|
+
issues.push({
|
|
1676
|
+
message: `Unsupported $parent reference inside ${key}: markers are not resolved in ${key} values`,
|
|
1677
|
+
path: childPath
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
continue;
|
|
1681
|
+
}
|
|
1682
|
+
if (key === "$elemMatch") {
|
|
1683
|
+
if (isParentRefMarker(child) || isEscapedParentRef(child)) {
|
|
1684
|
+
issues.push({
|
|
1685
|
+
message: "Unsupported $parent reference as a direct $elemMatch value",
|
|
1686
|
+
path: childPath
|
|
1687
|
+
});
|
|
1688
|
+
continue;
|
|
1689
|
+
}
|
|
1690
|
+
scanTemplateValueNode(child, childPath, issues);
|
|
1691
|
+
continue;
|
|
1692
|
+
}
|
|
1693
|
+
if (LOGICAL_CLAUSE_KEYS.has(key)) {
|
|
1694
|
+
if (!isArray6(child)) {
|
|
1695
|
+
scanTemplateValueNode(child, childPath, issues);
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
child.forEach((clause, index) => {
|
|
1699
|
+
const clausePath = [...childPath, index];
|
|
1700
|
+
if (isParentRefMarker(clause) || isEscapedParentRef(clause)) {
|
|
1701
|
+
issues.push({
|
|
1702
|
+
message: `Unsupported $parent reference as a direct ${key} clause: clauses must be filter objects`,
|
|
1703
|
+
path: clausePath
|
|
1704
|
+
});
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
if (isPlainObject11(clause)) {
|
|
1708
|
+
scanTemplateObjectEntries(clause, clausePath, issues);
|
|
1709
|
+
}
|
|
1710
|
+
});
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
scanTemplateValueNode(child, childPath, issues);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
function validateCorrelatedFilterTemplate(filter2) {
|
|
1717
|
+
const issues = [];
|
|
1718
|
+
if (!isPlainObject11(filter2)) {
|
|
1719
|
+
issues.push({ message: "Correlated include filter must be an object", path: [] });
|
|
1720
|
+
return issues;
|
|
1721
|
+
}
|
|
1722
|
+
if (isParentRefMarker(filter2) || isEscapedParentRef(filter2)) {
|
|
1723
|
+
issues.push({ message: "Correlated include filter must not itself be a $parent reference", path: [] });
|
|
1724
|
+
return issues;
|
|
1725
|
+
}
|
|
1726
|
+
scanTemplateObjectEntries(filter2, [], issues);
|
|
1727
|
+
return issues;
|
|
1728
|
+
}
|
|
1729
|
+
function validateCorrelatedIdTemplate(id) {
|
|
1730
|
+
if (typeof id === "string") {
|
|
1731
|
+
return id.length > 0 ? [] : [{ message: "Correlated include id must be a non-empty string or $parent reference", path: [] }];
|
|
1732
|
+
}
|
|
1733
|
+
if (isEscapedParentRef(id)) {
|
|
1734
|
+
return [{ message: "Unsupported $escape as a correlated include id", path: [] }];
|
|
1735
|
+
}
|
|
1736
|
+
if (isParentRefMarker(id)) {
|
|
1737
|
+
return isValidParentRefPath(id.$parent) ? [] : [{ message: "Invalid $parent reference path in correlated include id", path: [] }];
|
|
1738
|
+
}
|
|
1739
|
+
return [{ message: "Correlated include id must be a string or $parent reference", path: [] }];
|
|
1740
|
+
}
|
|
1741
|
+
function findArgsMarkerIssue(value, where) {
|
|
1742
|
+
if (value !== void 0 && containsParentMarkerShape(value)) {
|
|
1743
|
+
return {
|
|
1744
|
+
message: `Unsupported $parent reference in ${where}: references are only supported in filter and id positions`,
|
|
1745
|
+
path: []
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
return null;
|
|
1749
|
+
}
|
|
1750
|
+
function collectFilterReferencePaths(filter2) {
|
|
1751
|
+
const paths = [];
|
|
1752
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1753
|
+
const visitValue = (value) => {
|
|
1754
|
+
if (isParentRefMarker(value)) {
|
|
1755
|
+
if (typeof value.$parent === "string") {
|
|
1756
|
+
paths.push(value.$parent);
|
|
1757
|
+
}
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
if (isEscapedParentRef(value)) return;
|
|
1761
|
+
if (isArray6(value)) {
|
|
1762
|
+
if (seen.has(value)) return;
|
|
1763
|
+
seen.add(value);
|
|
1764
|
+
value.forEach(visitValue);
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
if (isPlainObject11(value)) {
|
|
1768
|
+
if (seen.has(value)) return;
|
|
1769
|
+
seen.add(value);
|
|
1770
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1771
|
+
if (SUBQUERY_PAYLOAD_KEYS.has(key) || MARKER_FORBIDDEN_OPERATOR_KEYS.has(key)) continue;
|
|
1772
|
+
visitValue(child);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
visitValue(filter2);
|
|
1777
|
+
return paths;
|
|
1778
|
+
}
|
|
1779
|
+
function collectCorrelatedReferencePaths(include) {
|
|
1780
|
+
const paths = [];
|
|
1781
|
+
if (isParentRefMarker(include.id) && typeof include.id !== "string") {
|
|
1782
|
+
const ref = include.id;
|
|
1783
|
+
if (typeof ref.$parent === "string") paths.push(ref.$parent);
|
|
1784
|
+
} else if (typeof include.id === "string") {
|
|
1785
|
+
}
|
|
1786
|
+
const filter2 = include.filter;
|
|
1787
|
+
if (filter2 !== void 0) {
|
|
1788
|
+
paths.push(...collectFilterReferencePaths(filter2));
|
|
1789
|
+
}
|
|
1790
|
+
return [...new Set(paths)];
|
|
1791
|
+
}
|
|
1792
|
+
var CorrelatedReferenceError = class extends Error {
|
|
1793
|
+
constructor(message) {
|
|
1794
|
+
super(message);
|
|
1795
|
+
this.name = "CorrelatedReferenceError";
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
var UNRESOLVABLE = /* @__PURE__ */ Symbol("correlated-include-unresolvable");
|
|
1799
|
+
function getOwnPathValue(parent, path) {
|
|
1800
|
+
const segments = path.split(".");
|
|
1801
|
+
let current = parent;
|
|
1802
|
+
for (const segment of segments) {
|
|
1803
|
+
if (DANGEROUS_PATH_SEGMENTS.has(segment)) {
|
|
1804
|
+
throw new CorrelatedReferenceError(`Invalid $parent reference path: ${path}`);
|
|
1805
|
+
}
|
|
1806
|
+
if (typeof current !== "object" || current === null) return void 0;
|
|
1807
|
+
if (!Object.prototype.hasOwnProperty.call(current, segment)) return void 0;
|
|
1808
|
+
current = current[segment];
|
|
1809
|
+
}
|
|
1810
|
+
return current;
|
|
1811
|
+
}
|
|
1812
|
+
function cloneSubstitutedValue(value) {
|
|
1813
|
+
if (isPlainObject11(value) || isArray6(value)) return cloneDeep(value);
|
|
1814
|
+
return value;
|
|
1815
|
+
}
|
|
1816
|
+
function literalParentRefObject(path) {
|
|
1817
|
+
const literal = {};
|
|
1818
|
+
Object.defineProperty(literal, PARENT_MARKER_KEY, {
|
|
1819
|
+
value: path,
|
|
1820
|
+
writable: true,
|
|
1821
|
+
enumerable: true,
|
|
1822
|
+
configurable: true
|
|
1823
|
+
});
|
|
1824
|
+
return literal;
|
|
1825
|
+
}
|
|
1826
|
+
function resolveObjectEntries(obj, parent) {
|
|
1827
|
+
const out = {};
|
|
1828
|
+
for (const [key, child] of Object.entries(obj)) {
|
|
1829
|
+
if (key === PARENT_MARKER_KEY) {
|
|
1830
|
+
throw new CorrelatedReferenceError("$parent is never a field name in correlated templates");
|
|
1831
|
+
}
|
|
1832
|
+
if (SUBQUERY_PAYLOAD_KEYS.has(key)) {
|
|
1833
|
+
if (containsParentMarkerShape(child)) {
|
|
1834
|
+
throw new CorrelatedReferenceError(`Unsupported $parent reference inside ${key}`);
|
|
1835
|
+
}
|
|
1836
|
+
out[key] = cloneSubstitutedValue(child);
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
if (MARKER_FORBIDDEN_OPERATOR_KEYS.has(key)) {
|
|
1840
|
+
if (containsParentMarkerShape(child)) {
|
|
1841
|
+
throw new CorrelatedReferenceError(`Unsupported $parent reference inside ${key}`);
|
|
1842
|
+
}
|
|
1843
|
+
out[key] = cloneSubstitutedValue(child);
|
|
1844
|
+
continue;
|
|
1845
|
+
}
|
|
1846
|
+
if (key === "$elemMatch") {
|
|
1847
|
+
if (isParentRefMarker(child) || isEscapedParentRef(child)) {
|
|
1848
|
+
throw new CorrelatedReferenceError("Unsupported $parent reference as a direct $elemMatch value");
|
|
1849
|
+
}
|
|
1850
|
+
out[key] = resolveValueNode(child, parent, false);
|
|
1851
|
+
continue;
|
|
1852
|
+
}
|
|
1853
|
+
if (LOGICAL_CLAUSE_KEYS.has(key)) {
|
|
1854
|
+
if (!isArray6(child)) {
|
|
1855
|
+
out[key] = resolveValueNode(child, parent, false);
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1858
|
+
out[key] = child.map((clause) => {
|
|
1859
|
+
if (isParentRefMarker(clause) || isEscapedParentRef(clause)) {
|
|
1860
|
+
throw new CorrelatedReferenceError(`Unsupported $parent reference as a direct ${key} clause`);
|
|
1861
|
+
}
|
|
1862
|
+
if (!isPlainObject11(clause)) return cloneSubstitutedValue(clause);
|
|
1863
|
+
return resolveObjectEntries(clause, parent);
|
|
1864
|
+
});
|
|
1865
|
+
continue;
|
|
1866
|
+
}
|
|
1867
|
+
if (key.startsWith("$")) {
|
|
1868
|
+
out[key] = resolveValueNode(child, parent, false);
|
|
1869
|
+
continue;
|
|
1870
|
+
}
|
|
1871
|
+
out[key] = resolveValueNode(child, parent, true);
|
|
1872
|
+
}
|
|
1873
|
+
return out;
|
|
1874
|
+
}
|
|
1875
|
+
function resolveValueNode(template, parent, bare) {
|
|
1876
|
+
if (isEscapedParentRef(template)) {
|
|
1877
|
+
const inner = template.$escape;
|
|
1878
|
+
if (!isParentRefMarker(inner) || typeof inner.$parent !== "string" || (inner.$parent?.length ?? 0) === 0) {
|
|
1879
|
+
throw new CorrelatedReferenceError('Invalid $escape payload: expected { "$escape": { "$parent": "<field>" } }');
|
|
1880
|
+
}
|
|
1881
|
+
return literalParentRefObject(inner.$parent);
|
|
1882
|
+
}
|
|
1883
|
+
if (isParentRefMarker(template)) {
|
|
1884
|
+
const path = template.$parent;
|
|
1885
|
+
if (!isValidParentRefPath(path)) {
|
|
1886
|
+
throw new CorrelatedReferenceError(`Invalid $parent reference path: ${String(path)}`);
|
|
1887
|
+
}
|
|
1888
|
+
const value = getOwnPathValue(parent, path);
|
|
1889
|
+
if (value === void 0 || value === null) throw UNRESOLVABLE;
|
|
1890
|
+
if (bare && isPlainObject11(value)) return { $eq: cloneDeep(value) };
|
|
1891
|
+
return cloneSubstitutedValue(value);
|
|
1892
|
+
}
|
|
1893
|
+
if (isPlainObject11(template)) {
|
|
1894
|
+
if (hasMarkerKey(template)) {
|
|
1895
|
+
throw new CorrelatedReferenceError(
|
|
1896
|
+
'Malformed $parent reference: objects carrying $parent must be exactly { "$parent": "<field>" }'
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
return resolveObjectEntries(template, parent);
|
|
1900
|
+
}
|
|
1901
|
+
if (isArray6(template)) {
|
|
1902
|
+
return template.map((item) => resolveValueNode(item, parent, false));
|
|
1903
|
+
}
|
|
1904
|
+
return template;
|
|
1905
|
+
}
|
|
1906
|
+
function isUnresolvableSignal(error2) {
|
|
1907
|
+
return error2 === UNRESOLVABLE;
|
|
1908
|
+
}
|
|
1909
|
+
function resolveCorrelatedIdTemplate(id, parent) {
|
|
1910
|
+
try {
|
|
1911
|
+
if (typeof id === "string") {
|
|
1912
|
+
if (id.length === 0) throw new CorrelatedReferenceError("Correlated include id must be a non-empty string");
|
|
1913
|
+
return { status: "resolved", id };
|
|
1914
|
+
}
|
|
1915
|
+
if (isParentRefMarker(id)) {
|
|
1916
|
+
const path = id.$parent;
|
|
1917
|
+
if (!isValidParentRefPath(path)) {
|
|
1918
|
+
throw new CorrelatedReferenceError(`Invalid $parent reference path: ${String(path)}`);
|
|
1919
|
+
}
|
|
1920
|
+
const value = getOwnPathValue(parent, path);
|
|
1921
|
+
if (typeof value === "string") return { status: "resolved", id: value };
|
|
1922
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
1923
|
+
return { status: "resolved", id: String(value) };
|
|
1924
|
+
}
|
|
1925
|
+
throw UNRESOLVABLE;
|
|
1926
|
+
}
|
|
1927
|
+
throw new CorrelatedReferenceError("Correlated include id must be a string or $parent reference");
|
|
1928
|
+
} catch (error2) {
|
|
1929
|
+
if (isUnresolvableSignal(error2)) return { status: "unresolvable" };
|
|
1930
|
+
throw error2;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
function resolveCorrelatedFilterTemplate(filter2, parent) {
|
|
1934
|
+
try {
|
|
1935
|
+
if (!isPlainObject11(filter2) || isParentRefMarker(filter2) || isEscapedParentRef(filter2)) {
|
|
1936
|
+
throw new CorrelatedReferenceError("Correlated include filter must be an object");
|
|
1937
|
+
}
|
|
1938
|
+
return { status: "resolved", filter: resolveObjectEntries(filter2, parent) };
|
|
1939
|
+
} catch (error2) {
|
|
1940
|
+
if (isUnresolvableSignal(error2)) return { status: "unresolvable" };
|
|
1941
|
+
throw error2;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
function validateExpandedCorrelatedOperands(expanded, limits) {
|
|
1945
|
+
return validateRequestComplexity(expanded, limits ?? null, "filter");
|
|
1946
|
+
}
|
|
1947
|
+
var MAX_NESTED_INCLUDE_DEPTH = 10;
|
|
1948
|
+
function validateCorrelatedArgsShape(args, op, depth) {
|
|
1949
|
+
if (args === void 0) return [];
|
|
1950
|
+
if (!isPlainObject11(args)) return ["Correlated include args must be an object"];
|
|
1951
|
+
const record = args;
|
|
1952
|
+
const allowed = op === "list" ? /* @__PURE__ */ new Set(["select", "sort", "skip", "limit", "page", "pageSize", "include"]) : /* @__PURE__ */ new Set(["select", "sort", "include"]);
|
|
1953
|
+
for (const key of Object.keys(record)) {
|
|
1954
|
+
if (!allowed.has(key)) return [`Unsupported correlated include arg: ${key}`];
|
|
1955
|
+
}
|
|
1956
|
+
if (op === "count" && Object.keys(record).length > 0) return ["Count includes accept no args"];
|
|
1957
|
+
const selectIssue = findArgsMarkerIssue(record.select, "args.select");
|
|
1958
|
+
if (selectIssue) return [selectIssue.message];
|
|
1959
|
+
const sortIssue = findArgsMarkerIssue(record.sort, "args.sort");
|
|
1960
|
+
if (sortIssue) return [sortIssue.message];
|
|
1961
|
+
if (record.include !== void 0) {
|
|
1962
|
+
if (depth >= MAX_NESTED_INCLUDE_DEPTH) return ["Correlated include nesting exceeds the supported depth"];
|
|
1963
|
+
const nested = Array.isArray(record.include) ? record.include : [record.include];
|
|
1964
|
+
for (const entry of nested) {
|
|
1965
|
+
const errors = validateCorrelatedIncludeShape(entry, depth + 1);
|
|
1966
|
+
if (errors.length > 0) return errors;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
return [];
|
|
1970
|
+
}
|
|
1971
|
+
function validateCorrelatedIncludeShape(entry, depth = 0) {
|
|
1972
|
+
if (!isPlainObject11(entry)) return ["Correlated include must be an object"];
|
|
1973
|
+
const record = entry;
|
|
1974
|
+
if (record.mode !== "correlated") return ['Correlated include mode must be "correlated"'];
|
|
1975
|
+
if (typeof record.model !== "string" || record.model.length === 0)
|
|
1976
|
+
return ["Correlated include model must be a non-empty string"];
|
|
1977
|
+
if (record.op !== "list" && record.op !== "read" && record.op !== "count") {
|
|
1978
|
+
return ['Correlated include op must be one of "list", "read", or "count"'];
|
|
1979
|
+
}
|
|
1980
|
+
if (!isValidCorrelatedOutputPath(record.path))
|
|
1981
|
+
return [`Invalid correlated include output path: ${String(record.path)}`];
|
|
1982
|
+
if ("localField" in record || "foreignField" in record) {
|
|
1983
|
+
return ["Correlated includes must not carry localField or foreignField"];
|
|
1984
|
+
}
|
|
1985
|
+
if (record.options !== void 0 && !(isPlainObject11(record.options) && Object.keys(record.options).length === 0)) {
|
|
1986
|
+
return ["Correlated include options must be absent or empty"];
|
|
1987
|
+
}
|
|
1988
|
+
const hasId = record.id !== void 0;
|
|
1989
|
+
const hasFilter = record.filter !== void 0;
|
|
1990
|
+
if (record.op === "read") {
|
|
1991
|
+
if (hasId === hasFilter) return ['Correlated read includes require exactly one of "id" or "filter"'];
|
|
1992
|
+
if (hasId) {
|
|
1993
|
+
const issues = validateCorrelatedIdTemplate(record.id);
|
|
1994
|
+
if (issues.length > 0) return [issues[0].message];
|
|
1995
|
+
} else {
|
|
1996
|
+
const issues = validateCorrelatedFilterTemplate(record.filter);
|
|
1997
|
+
if (issues.length > 0) return [issues[0].message];
|
|
1998
|
+
}
|
|
1999
|
+
} else {
|
|
2000
|
+
if (!hasFilter || hasId) return [`Correlated ${record.op} includes require a filter and no id`];
|
|
2001
|
+
const issues = validateCorrelatedFilterTemplate(record.filter);
|
|
2002
|
+
if (issues.length > 0) return [issues[0].message];
|
|
2003
|
+
}
|
|
2004
|
+
return validateCorrelatedArgsShape(record.args, record.op, depth);
|
|
2005
|
+
}
|
|
2006
|
+
function isCorrelatedInclude(value) {
|
|
2007
|
+
return isPlainObject11(value) && value.mode === "correlated";
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
// src/services/base.ts
|
|
2011
|
+
var correlatedExecStates = /* @__PURE__ */ new WeakMap();
|
|
2012
|
+
function getCorrelatedTemplateDepth(includes) {
|
|
2013
|
+
let max = 0;
|
|
2014
|
+
const visit = (entries, depth) => {
|
|
2015
|
+
const list = castArray(entries).filter(Boolean);
|
|
2016
|
+
for (const entry of list) {
|
|
2017
|
+
if (!isPlainObject12(entry)) continue;
|
|
2018
|
+
max = Math.max(max, depth);
|
|
2019
|
+
const nested = entry.args?.include;
|
|
2020
|
+
if (nested !== void 0) {
|
|
2021
|
+
visit(nested, depth + 1);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
if (includes.length > 0) visit(includes, 1);
|
|
2026
|
+
return max;
|
|
2027
|
+
}
|
|
1561
2028
|
var ClientRequestError = class extends Error {
|
|
1562
2029
|
constructor(result) {
|
|
1563
2030
|
super(String(result.code));
|
|
@@ -1572,11 +2039,11 @@ function validateClientFilter(filter2) {
|
|
|
1572
2039
|
}
|
|
1573
2040
|
const blockedOperators = /* @__PURE__ */ new Set(["$where", "$expr", "$function", "$accumulator"]);
|
|
1574
2041
|
const visit = (value, path) => {
|
|
1575
|
-
if (
|
|
2042
|
+
if (isArray7(value)) {
|
|
1576
2043
|
value.forEach((item, index) => visit(item, `${path}[${index}]`));
|
|
1577
2044
|
return;
|
|
1578
2045
|
}
|
|
1579
|
-
if (!
|
|
2046
|
+
if (!isPlainObject12(value)) return;
|
|
1580
2047
|
Object.entries(value).forEach(([key, child]) => {
|
|
1581
2048
|
const nextPath = path ? `${path}.${key}` : key;
|
|
1582
2049
|
if (blockedOperators.has(key)) {
|
|
@@ -1696,21 +2163,52 @@ var Base = class {
|
|
|
1696
2163
|
return this.req.macl.getPublicService(modelName);
|
|
1697
2164
|
}
|
|
1698
2165
|
processInclude(include) {
|
|
1699
|
-
const
|
|
1700
|
-
|
|
1701
|
-
|
|
2166
|
+
const items = compact(castArray(include));
|
|
2167
|
+
const legacy = [];
|
|
2168
|
+
const correlated = [];
|
|
2169
|
+
const correlatedReferenceFields = [];
|
|
2170
|
+
for (const entry of items) {
|
|
2171
|
+
if (isCorrelatedInclude(entry)) {
|
|
2172
|
+
const shapeErrors = validateCorrelatedIncludeShape(entry);
|
|
2173
|
+
if (shapeErrors.length > 0) {
|
|
2174
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, shapeErrors[0]);
|
|
2175
|
+
}
|
|
2176
|
+
correlated.push(entry);
|
|
2177
|
+
correlatedReferenceFields.push(...collectCorrelatedReferencePaths(entry));
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
if (containsParentMarkerShape(entry)) {
|
|
2181
|
+
this.throwClientRequestError(
|
|
2182
|
+
"bad_request" /* BadRequest */,
|
|
2183
|
+
'Legacy includes must not contain $parent markers; use a correlated include (mode: "correlated")'
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
const candidate = entry;
|
|
2187
|
+
if (candidate.model && candidate.op && candidate.path && candidate.localField && candidate.foreignField) {
|
|
2188
|
+
legacy.push(entry);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
const seenCorrelatedPaths = /* @__PURE__ */ new Set();
|
|
2192
|
+
for (const entry of correlated) {
|
|
2193
|
+
if (seenCorrelatedPaths.has(entry.path)) {
|
|
2194
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, `Duplicate correlated include output path: ${entry.path}`);
|
|
2195
|
+
}
|
|
2196
|
+
seenCorrelatedPaths.add(entry.path);
|
|
2197
|
+
}
|
|
1702
2198
|
let includeLocalFields = [];
|
|
1703
2199
|
let includePaths = [];
|
|
1704
|
-
forEach3(
|
|
2200
|
+
forEach3(legacy, (inc) => {
|
|
1705
2201
|
includeLocalFields.push(inc.localField);
|
|
1706
2202
|
includePaths.push(inc.path);
|
|
1707
2203
|
});
|
|
1708
2204
|
includeLocalFields = uniq(compact(includeLocalFields));
|
|
1709
2205
|
includePaths = uniq(compact(includePaths));
|
|
1710
2206
|
return {
|
|
1711
|
-
includes,
|
|
2207
|
+
includes: legacy,
|
|
2208
|
+
correlatedIncludes: correlated,
|
|
1712
2209
|
includeLocalFields,
|
|
1713
|
-
includePaths
|
|
2210
|
+
includePaths,
|
|
2211
|
+
correlatedReferenceFields: uniq(compact(correlatedReferenceFields))
|
|
1714
2212
|
};
|
|
1715
2213
|
}
|
|
1716
2214
|
getForeignKeyValues(value) {
|
|
@@ -1753,7 +2251,7 @@ var Base = class {
|
|
|
1753
2251
|
return ids.size;
|
|
1754
2252
|
}
|
|
1755
2253
|
sanitizeIncludeArgs(args) {
|
|
1756
|
-
if (!
|
|
2254
|
+
if (!isPlainObject12(args)) return {};
|
|
1757
2255
|
const { overrides: _clientOverrides, ...trustedArgs } = args;
|
|
1758
2256
|
return trustedArgs;
|
|
1759
2257
|
}
|
|
@@ -1766,10 +2264,16 @@ var Base = class {
|
|
|
1766
2264
|
if (!include) return docs;
|
|
1767
2265
|
const includes = compact(castArray(include));
|
|
1768
2266
|
if (includes.length === 0) return docs;
|
|
1769
|
-
const isSingle = !
|
|
2267
|
+
const isSingle = !isArray7(docs);
|
|
1770
2268
|
let docList = isSingle ? [docs] : docs;
|
|
1771
2269
|
for (let x = 0; x < includes.length; x++) {
|
|
1772
2270
|
const include2 = includes[x];
|
|
2271
|
+
if (isCorrelatedInclude(include2)) {
|
|
2272
|
+
this.throwClientRequestError(
|
|
2273
|
+
"bad_request" /* BadRequest */,
|
|
2274
|
+
"Correlated includes are not supported by the legacy include executor"
|
|
2275
|
+
);
|
|
2276
|
+
}
|
|
1773
2277
|
this.assertIncludeForeignField(include2);
|
|
1774
2278
|
switch (include2.op) {
|
|
1775
2279
|
case "count":
|
|
@@ -1848,6 +2352,356 @@ var Base = class {
|
|
|
1848
2352
|
}
|
|
1849
2353
|
return docs;
|
|
1850
2354
|
}
|
|
2355
|
+
getCorrelatedExecState() {
|
|
2356
|
+
const key = this.req;
|
|
2357
|
+
let state = correlatedExecStates.get(key);
|
|
2358
|
+
if (!state) {
|
|
2359
|
+
state = {
|
|
2360
|
+
totalQueries: 0,
|
|
2361
|
+
scheduler: new RequestConcurrencyScheduler(this.getRequestComplexity().maxBulkConcurrency)
|
|
2362
|
+
};
|
|
2363
|
+
correlatedExecStates.set(key, state);
|
|
2364
|
+
}
|
|
2365
|
+
return state;
|
|
2366
|
+
}
|
|
2367
|
+
claimCorrelatedQuerySlot() {
|
|
2368
|
+
const limits = this.getRequestComplexity();
|
|
2369
|
+
const state = this.getCorrelatedExecState();
|
|
2370
|
+
if (state.totalQueries >= limits.maxCorrelatedQueries) {
|
|
2371
|
+
this.throwClientRequestError(
|
|
2372
|
+
"bad_request" /* BadRequest */,
|
|
2373
|
+
`Correlated include query budget exceeded (max ${limits.maxCorrelatedQueries})`
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
state.totalQueries += 1;
|
|
2377
|
+
}
|
|
2378
|
+
sanitizeCorrelatedArgs(include) {
|
|
2379
|
+
const args = include.args ?? {};
|
|
2380
|
+
if (include.op === "list") {
|
|
2381
|
+
const { select: select2, sort: sort2, skip, limit, page, pageSize, include: nestedInclude2 } = args;
|
|
2382
|
+
return { select: select2, sort: sort2, skip, limit, page, pageSize, nestedInclude: nestedInclude2 };
|
|
2383
|
+
}
|
|
2384
|
+
const { select, sort, include: nestedInclude } = args;
|
|
2385
|
+
return { select, sort, nestedInclude };
|
|
2386
|
+
}
|
|
2387
|
+
async includeCorrelatedDocs(docs, correlatedIncludes, snapshots, depth = 0) {
|
|
2388
|
+
if (correlatedIncludes.length === 0) return docs;
|
|
2389
|
+
const limits = this.getRequestComplexity();
|
|
2390
|
+
const templateDepth = getCorrelatedTemplateDepth(correlatedIncludes);
|
|
2391
|
+
if (depth + templateDepth > limits.maxCorrelatedDepth) {
|
|
2392
|
+
this.throwClientRequestError(
|
|
2393
|
+
"bad_request" /* BadRequest */,
|
|
2394
|
+
`Correlated include depth exceeded (max ${limits.maxCorrelatedDepth})`
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
const state = this.getCorrelatedExecState();
|
|
2398
|
+
const resolvedSnapshots = snapshots ?? docs.map((doc) => cloneDeep2(toObject(doc)));
|
|
2399
|
+
for (const include of correlatedIncludes) {
|
|
2400
|
+
switch (include.op) {
|
|
2401
|
+
case "read":
|
|
2402
|
+
await this.includeCorrelatedRead(docs, include, resolvedSnapshots, state, depth);
|
|
2403
|
+
break;
|
|
2404
|
+
case "list":
|
|
2405
|
+
await this.includeCorrelatedList(docs, include, resolvedSnapshots, state, depth);
|
|
2406
|
+
break;
|
|
2407
|
+
case "count":
|
|
2408
|
+
await this.includeCorrelatedCount(docs, include, resolvedSnapshots, state, depth);
|
|
2409
|
+
break;
|
|
2410
|
+
default:
|
|
2411
|
+
this.throwClientRequestError(
|
|
2412
|
+
"bad_request" /* BadRequest */,
|
|
2413
|
+
`Unsupported correlated include op: ${include.op}`
|
|
2414
|
+
);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
return docs;
|
|
2418
|
+
}
|
|
2419
|
+
async includeCorrelatedRead(docs, include, snapshots, state, depth) {
|
|
2420
|
+
const svc = await this.getAuthorizedTargetService(include.model, "read");
|
|
2421
|
+
const { select, sort, nestedInclude } = this.sanitizeCorrelatedArgs(include);
|
|
2422
|
+
const hasId = include.id !== void 0;
|
|
2423
|
+
if (hasId) {
|
|
2424
|
+
const idTemplate = include.id;
|
|
2425
|
+
const tasks2 = docs.map((_, index) => ({ index, snapshot: snapshots[index] }));
|
|
2426
|
+
await state.scheduler.map(tasks2, async ({ index, snapshot }) => {
|
|
2427
|
+
let resolution;
|
|
2428
|
+
try {
|
|
2429
|
+
resolution = resolveCorrelatedIdTemplate(idTemplate, snapshot);
|
|
2430
|
+
} catch (error2) {
|
|
2431
|
+
if (error2 instanceof CorrelatedReferenceError) {
|
|
2432
|
+
throw new ClientRequestError({
|
|
2433
|
+
success: false,
|
|
2434
|
+
kind: "error",
|
|
2435
|
+
code: "bad_request" /* BadRequest */,
|
|
2436
|
+
errors: [{ detail: error2.message }]
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
throw error2;
|
|
2440
|
+
}
|
|
2441
|
+
if (resolution.status === "unresolvable") {
|
|
2442
|
+
setDocValue(docs[index], include.path, null);
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
const resolvedId = resolution.id;
|
|
2446
|
+
let idFilter;
|
|
2447
|
+
try {
|
|
2448
|
+
idFilter = await svc.genIDFilter(resolvedId);
|
|
2449
|
+
} catch (error2) {
|
|
2450
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2451
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2452
|
+
throw error2;
|
|
2453
|
+
}
|
|
2454
|
+
const authorized = await svc.genFilter("read", idFilter);
|
|
2455
|
+
if (authorized === false) {
|
|
2456
|
+
throw new ClientRequestError({ success: false, kind: "error", code: "forbidden" /* Forbidden */ });
|
|
2457
|
+
}
|
|
2458
|
+
this.claimCorrelatedQuerySlot();
|
|
2459
|
+
let result;
|
|
2460
|
+
try {
|
|
2461
|
+
result = await svc.findOne(
|
|
2462
|
+
{},
|
|
2463
|
+
{
|
|
2464
|
+
select,
|
|
2465
|
+
sort,
|
|
2466
|
+
include: nestedInclude,
|
|
2467
|
+
overrides: { filter: authorized }
|
|
2468
|
+
},
|
|
2469
|
+
{ access: "read", lean: true, includePermissions: false }
|
|
2470
|
+
);
|
|
2471
|
+
} catch (error2) {
|
|
2472
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2473
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2474
|
+
throw error2;
|
|
2475
|
+
}
|
|
2476
|
+
if (result.success) {
|
|
2477
|
+
setDocValue(docs[index], include.path, result.data);
|
|
2478
|
+
} else if (result.code === "not_found" /* NotFound */) {
|
|
2479
|
+
setDocValue(docs[index], include.path, null);
|
|
2480
|
+
} else {
|
|
2481
|
+
throw new ClientRequestError(result);
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
const filterTemplate = include.filter;
|
|
2487
|
+
const templateErrors = this.validateClientFilter(filterTemplate);
|
|
2488
|
+
if (templateErrors.length > 0) {
|
|
2489
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, templateErrors[0]);
|
|
2490
|
+
}
|
|
2491
|
+
let parsedTemplate;
|
|
2492
|
+
try {
|
|
2493
|
+
parsedTemplate = await this.parseClientData(
|
|
2494
|
+
filterTemplate,
|
|
2495
|
+
state.scheduler
|
|
2496
|
+
);
|
|
2497
|
+
} catch (error2) {
|
|
2498
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2499
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2500
|
+
throw error2;
|
|
2501
|
+
}
|
|
2502
|
+
const tasks = docs.map((_, index) => ({ index, snapshot: snapshots[index] }));
|
|
2503
|
+
await state.scheduler.map(tasks, async ({ index, snapshot }) => {
|
|
2504
|
+
let resolution;
|
|
2505
|
+
try {
|
|
2506
|
+
resolution = resolveCorrelatedFilterTemplate(parsedTemplate, snapshot);
|
|
2507
|
+
} catch (error2) {
|
|
2508
|
+
if (error2 instanceof CorrelatedReferenceError) {
|
|
2509
|
+
throw new ClientRequestError({
|
|
2510
|
+
success: false,
|
|
2511
|
+
kind: "error",
|
|
2512
|
+
code: "bad_request" /* BadRequest */,
|
|
2513
|
+
errors: [{ detail: error2.message }]
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
throw error2;
|
|
2517
|
+
}
|
|
2518
|
+
if (resolution.status === "unresolvable") {
|
|
2519
|
+
setDocValue(docs[index], include.path, null);
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
const expanded = resolution.filter;
|
|
2523
|
+
const operandErrors = validateExpandedCorrelatedOperands(expanded, this.getRequestComplexity());
|
|
2524
|
+
if (operandErrors.length > 0) {
|
|
2525
|
+
throw new ClientRequestError({
|
|
2526
|
+
success: false,
|
|
2527
|
+
kind: "error",
|
|
2528
|
+
code: "bad_request" /* BadRequest */,
|
|
2529
|
+
errors: [{ detail: operandErrors[0].detail }]
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
const authorized = await svc.genFilter("read", expanded);
|
|
2533
|
+
if (authorized === false) {
|
|
2534
|
+
throw new ClientRequestError({ success: false, kind: "error", code: "forbidden" /* Forbidden */ });
|
|
2535
|
+
}
|
|
2536
|
+
this.claimCorrelatedQuerySlot();
|
|
2537
|
+
let result;
|
|
2538
|
+
try {
|
|
2539
|
+
result = await svc.findOne(
|
|
2540
|
+
{},
|
|
2541
|
+
{
|
|
2542
|
+
select,
|
|
2543
|
+
sort,
|
|
2544
|
+
include: nestedInclude,
|
|
2545
|
+
overrides: { filter: authorized }
|
|
2546
|
+
},
|
|
2547
|
+
{ access: "read", lean: true, includePermissions: false }
|
|
2548
|
+
);
|
|
2549
|
+
} catch (error2) {
|
|
2550
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2551
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2552
|
+
throw error2;
|
|
2553
|
+
}
|
|
2554
|
+
if (result.success) {
|
|
2555
|
+
setDocValue(docs[index], include.path, result.data);
|
|
2556
|
+
} else if (result.code === "not_found" /* NotFound */) {
|
|
2557
|
+
setDocValue(docs[index], include.path, null);
|
|
2558
|
+
} else {
|
|
2559
|
+
throw new ClientRequestError(result);
|
|
2560
|
+
}
|
|
2561
|
+
});
|
|
2562
|
+
void depth;
|
|
2563
|
+
}
|
|
2564
|
+
async includeCorrelatedList(docs, include, snapshots, state, depth) {
|
|
2565
|
+
const svc = await this.getAuthorizedTargetService(include.model, "list");
|
|
2566
|
+
const { select, sort, skip, limit, page, pageSize, nestedInclude } = this.sanitizeCorrelatedArgs(include);
|
|
2567
|
+
const filterTemplate = include.filter;
|
|
2568
|
+
const templateErrors = this.validateClientFilter(filterTemplate);
|
|
2569
|
+
if (templateErrors.length > 0) {
|
|
2570
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, templateErrors[0]);
|
|
2571
|
+
}
|
|
2572
|
+
let parsedTemplate;
|
|
2573
|
+
try {
|
|
2574
|
+
parsedTemplate = await this.parseClientData(
|
|
2575
|
+
filterTemplate,
|
|
2576
|
+
state.scheduler
|
|
2577
|
+
);
|
|
2578
|
+
} catch (error2) {
|
|
2579
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2580
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2581
|
+
throw error2;
|
|
2582
|
+
}
|
|
2583
|
+
const tasks = docs.map((_, index) => ({ index, snapshot: snapshots[index] }));
|
|
2584
|
+
await state.scheduler.map(tasks, async ({ index, snapshot }) => {
|
|
2585
|
+
let resolution;
|
|
2586
|
+
try {
|
|
2587
|
+
resolution = resolveCorrelatedFilterTemplate(parsedTemplate, snapshot);
|
|
2588
|
+
} catch (error2) {
|
|
2589
|
+
if (error2 instanceof CorrelatedReferenceError) {
|
|
2590
|
+
throw new ClientRequestError({
|
|
2591
|
+
success: false,
|
|
2592
|
+
kind: "error",
|
|
2593
|
+
code: "bad_request" /* BadRequest */,
|
|
2594
|
+
errors: [{ detail: error2.message }]
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
throw error2;
|
|
2598
|
+
}
|
|
2599
|
+
if (resolution.status === "unresolvable") {
|
|
2600
|
+
setDocValue(docs[index], include.path, []);
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2603
|
+
const expanded = resolution.filter;
|
|
2604
|
+
const operandErrors = validateExpandedCorrelatedOperands(expanded, this.getRequestComplexity());
|
|
2605
|
+
if (operandErrors.length > 0) {
|
|
2606
|
+
throw new ClientRequestError({
|
|
2607
|
+
success: false,
|
|
2608
|
+
kind: "error",
|
|
2609
|
+
code: "bad_request" /* BadRequest */,
|
|
2610
|
+
errors: [{ detail: operandErrors[0].detail }]
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
const authorized = await svc.genFilter("list", expanded);
|
|
2614
|
+
if (authorized === false) {
|
|
2615
|
+
throw new ClientRequestError({ success: false, kind: "error", code: "forbidden" /* Forbidden */ });
|
|
2616
|
+
}
|
|
2617
|
+
this.claimCorrelatedQuerySlot();
|
|
2618
|
+
let result;
|
|
2619
|
+
try {
|
|
2620
|
+
result = await svc.find(
|
|
2621
|
+
{},
|
|
2622
|
+
{
|
|
2623
|
+
select,
|
|
2624
|
+
sort,
|
|
2625
|
+
skip,
|
|
2626
|
+
limit,
|
|
2627
|
+
page,
|
|
2628
|
+
pageSize,
|
|
2629
|
+
include: nestedInclude,
|
|
2630
|
+
overrides: { filter: authorized }
|
|
2631
|
+
},
|
|
2632
|
+
{ includeCount: false, lean: true, includePermissions: false }
|
|
2633
|
+
);
|
|
2634
|
+
} catch (error2) {
|
|
2635
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2636
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2637
|
+
throw error2;
|
|
2638
|
+
}
|
|
2639
|
+
if (result.success) {
|
|
2640
|
+
setDocValue(docs[index], include.path, result.data);
|
|
2641
|
+
} else {
|
|
2642
|
+
throw new ClientRequestError(result);
|
|
2643
|
+
}
|
|
2644
|
+
});
|
|
2645
|
+
void depth;
|
|
2646
|
+
}
|
|
2647
|
+
async includeCorrelatedCount(docs, include, snapshots, state, depth) {
|
|
2648
|
+
const svc = await this.getAuthorizedTargetService(include.model, "count");
|
|
2649
|
+
const filterTemplate = include.filter;
|
|
2650
|
+
const templateErrors = this.validateClientFilter(filterTemplate);
|
|
2651
|
+
if (templateErrors.length > 0) {
|
|
2652
|
+
this.throwClientRequestError("bad_request" /* BadRequest */, templateErrors[0]);
|
|
2653
|
+
}
|
|
2654
|
+
const tasks = docs.map((_, index) => ({ index, snapshot: snapshots[index] }));
|
|
2655
|
+
await state.scheduler.map(tasks, async ({ index, snapshot }) => {
|
|
2656
|
+
let resolution;
|
|
2657
|
+
try {
|
|
2658
|
+
resolution = resolveCorrelatedFilterTemplate(filterTemplate, snapshot);
|
|
2659
|
+
} catch (error2) {
|
|
2660
|
+
if (error2 instanceof CorrelatedReferenceError) {
|
|
2661
|
+
throw new ClientRequestError({
|
|
2662
|
+
success: false,
|
|
2663
|
+
kind: "error",
|
|
2664
|
+
code: "bad_request" /* BadRequest */,
|
|
2665
|
+
errors: [{ detail: error2.message }]
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
throw error2;
|
|
2669
|
+
}
|
|
2670
|
+
if (resolution.status === "unresolvable") {
|
|
2671
|
+
setDocValue(docs[index], include.path, 0);
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
const expanded = resolution.filter;
|
|
2675
|
+
const operandErrors = validateExpandedCorrelatedOperands(expanded, this.getRequestComplexity());
|
|
2676
|
+
if (operandErrors.length > 0) {
|
|
2677
|
+
throw new ClientRequestError({
|
|
2678
|
+
success: false,
|
|
2679
|
+
kind: "error",
|
|
2680
|
+
code: "bad_request" /* BadRequest */,
|
|
2681
|
+
errors: [{ detail: operandErrors[0].detail }]
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
const authorized = await svc.genFilter("count", expanded);
|
|
2685
|
+
if (authorized === false) {
|
|
2686
|
+
throw new ClientRequestError({ success: false, kind: "error", code: "forbidden" /* Forbidden */ });
|
|
2687
|
+
}
|
|
2688
|
+
this.claimCorrelatedQuerySlot();
|
|
2689
|
+
let result;
|
|
2690
|
+
try {
|
|
2691
|
+
result = await svc.countTrusted(authorized);
|
|
2692
|
+
} catch (error2) {
|
|
2693
|
+
const clientResult = this.getClientRequestErrorResult(error2);
|
|
2694
|
+
if (clientResult) throw new ClientRequestError(clientResult);
|
|
2695
|
+
throw error2;
|
|
2696
|
+
}
|
|
2697
|
+
if (result.success) {
|
|
2698
|
+
setDocValue(docs[index], include.path, result.data);
|
|
2699
|
+
} else {
|
|
2700
|
+
throw new ClientRequestError(result);
|
|
2701
|
+
}
|
|
2702
|
+
});
|
|
2703
|
+
void depth;
|
|
2704
|
+
}
|
|
1851
2705
|
async parseClientData(filter2, scheduler = new RequestConcurrencyScheduler(this.getRequestComplexity().maxBulkConcurrency), scheduled = false) {
|
|
1852
2706
|
const result = await iterateQuery(
|
|
1853
2707
|
filter2,
|
|
@@ -1890,7 +2744,7 @@ var Base = class {
|
|
|
1890
2744
|
}
|
|
1891
2745
|
let ret = result.data;
|
|
1892
2746
|
if (sqOptions.path) {
|
|
1893
|
-
ret =
|
|
2747
|
+
ret = isArray7(ret) ? flatten(ret.map((v) => get4(v, sqOptions.path))) : get4(ret, sqOptions.path);
|
|
1894
2748
|
}
|
|
1895
2749
|
if (sqOptions.compact) {
|
|
1896
2750
|
ret = compact(castArray(ret));
|
|
@@ -1907,8 +2761,9 @@ var Base = class {
|
|
|
1907
2761
|
// src/services/service.ts
|
|
1908
2762
|
import {
|
|
1909
2763
|
castArray as castArray2,
|
|
2764
|
+
cloneDeep as cloneDeep3,
|
|
1910
2765
|
compact as compact2,
|
|
1911
|
-
isArray as
|
|
2766
|
+
isArray as isArray8,
|
|
1912
2767
|
isBoolean as isBoolean3,
|
|
1913
2768
|
isFunction as isFunction3,
|
|
1914
2769
|
omit,
|
|
@@ -2411,6 +3266,29 @@ var assertModelDocument = (value, modelName, hookName) => {
|
|
|
2411
3266
|
}
|
|
2412
3267
|
throw new Error(`${hookName} hook for model=${modelName} must return a Mongoose document instance`);
|
|
2413
3268
|
};
|
|
3269
|
+
var unsetDocPath = (doc, path) => {
|
|
3270
|
+
const segments = path.split(".");
|
|
3271
|
+
let current = doc;
|
|
3272
|
+
const unwrap = (value) => value != null && typeof value === "object" && "_doc" in value ? value._doc : value;
|
|
3273
|
+
current = unwrap(current);
|
|
3274
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
3275
|
+
if (current == null || typeof current !== "object") return;
|
|
3276
|
+
if (!Object.prototype.hasOwnProperty.call(current, segments[i])) return;
|
|
3277
|
+
current = unwrap(current[segments[i]]);
|
|
3278
|
+
}
|
|
3279
|
+
if (current != null && typeof current === "object") {
|
|
3280
|
+
delete current[segments[segments.length - 1]];
|
|
3281
|
+
}
|
|
3282
|
+
};
|
|
3283
|
+
var shouldKeepCorrelatedRef = (ref, keep) => {
|
|
3284
|
+
if (keep.has(ref)) return true;
|
|
3285
|
+
const top = ref.split(".")[0];
|
|
3286
|
+
if (keep.has(top)) return true;
|
|
3287
|
+
for (const kept of keep) {
|
|
3288
|
+
if (kept === ref || kept.startsWith(`${ref}.`) || ref.startsWith(`${kept}.`)) return true;
|
|
3289
|
+
}
|
|
3290
|
+
return false;
|
|
3291
|
+
};
|
|
2414
3292
|
var Service = class extends Base {
|
|
2415
3293
|
findRawParentDoc(args) {
|
|
2416
3294
|
return this.model.findOne({
|
|
@@ -2477,8 +3355,9 @@ var Service = class extends Base {
|
|
|
2477
3355
|
overridePopulate || this.genPopulate(populateAccess || access, populate),
|
|
2478
3356
|
this.genAllowedFields({}, access, this.baseFieldsExt)
|
|
2479
3357
|
]);
|
|
2480
|
-
const { includes, includeLocalFields, includePaths } = this.processInclude(include);
|
|
2481
|
-
const
|
|
3358
|
+
const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } = this.processInclude(include);
|
|
3359
|
+
const correlatedOutputPaths = correlatedIncludes.map((entry) => entry.path);
|
|
3360
|
+
const finalSelect = normalizeSelect(_select).concat(includeLocalFields, correlatedReferenceFields);
|
|
2482
3361
|
const query = {
|
|
2483
3362
|
filter: _filter,
|
|
2484
3363
|
select: finalSelect,
|
|
@@ -2511,8 +3390,14 @@ var Service = class extends Base {
|
|
|
2511
3390
|
originalDocumentSnapshot: toObject(doc),
|
|
2512
3391
|
resolvedQuery: query
|
|
2513
3392
|
};
|
|
3393
|
+
const correlatedSnapshots = [cloneDeep3(toObject(doc))];
|
|
2514
3394
|
try {
|
|
2515
3395
|
doc = await this.includeDocs(doc, includes);
|
|
3396
|
+
if (correlatedIncludes.length > 0) {
|
|
3397
|
+
const docList = [doc];
|
|
3398
|
+
await this.includeCorrelatedDocs(docList, correlatedIncludes, correlatedSnapshots);
|
|
3399
|
+
doc = docList[0];
|
|
3400
|
+
}
|
|
2516
3401
|
} catch (error2) {
|
|
2517
3402
|
const result = this.getClientRequestErrorResult(error2);
|
|
2518
3403
|
if (result) {
|
|
@@ -2530,8 +3415,20 @@ var Service = class extends Base {
|
|
|
2530
3415
|
doc = await this.trimOutputFields(
|
|
2531
3416
|
doc,
|
|
2532
3417
|
access,
|
|
2533
|
-
this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
|
|
3418
|
+
this.baseFieldsExt.concat(includePaths, correlatedOutputPaths, normalizeSelect(overrideSelect))
|
|
2534
3419
|
);
|
|
3420
|
+
if (correlatedReferenceFields.length > 0) {
|
|
3421
|
+
const keep = /* @__PURE__ */ new Set([
|
|
3422
|
+
...normalizeSelect(_select),
|
|
3423
|
+
...normalizeSelect(overrideSelect),
|
|
3424
|
+
...this.baseFieldsExt,
|
|
3425
|
+
...includePaths,
|
|
3426
|
+
...correlatedOutputPaths
|
|
3427
|
+
]);
|
|
3428
|
+
for (const ref of correlatedReferenceFields) {
|
|
3429
|
+
if (!shouldKeepCorrelatedRef(ref, keep)) unsetDocPath(doc, ref);
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
2535
3432
|
if (!includePermissions) doc = this.addEmptyPermissions(doc);
|
|
2536
3433
|
this.completeOp("findOne", startedAt, "success" /* Success */, _filter);
|
|
2537
3434
|
return { success: true, kind: "single", code: "success" /* Success */, data: doc, query, context };
|
|
@@ -2577,11 +3474,12 @@ var Service = class extends Base {
|
|
|
2577
3474
|
this.genAllowedFields({}, "list", this.baseFieldsExt)
|
|
2578
3475
|
]);
|
|
2579
3476
|
const finalSelect = normalizeSelect(_select);
|
|
2580
|
-
const filteredPopulate =
|
|
2581
|
-
const { includes, includeLocalFields, includePaths } = this.processInclude(include);
|
|
3477
|
+
const filteredPopulate = isArray8(finalSelect) && isArray8(_populate) ? _populate.filter((p) => finalSelect.includes(p.path.split(".")[0])) : _populate;
|
|
3478
|
+
const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } = this.processInclude(include);
|
|
3479
|
+
const correlatedOutputPaths = correlatedIncludes.map((entry) => entry.path);
|
|
2582
3480
|
const query = {
|
|
2583
3481
|
filter: _filter,
|
|
2584
|
-
select: finalSelect.concat(includeLocalFields),
|
|
3482
|
+
select: finalSelect.concat(includeLocalFields, correlatedReferenceFields),
|
|
2585
3483
|
populate: filteredPopulate,
|
|
2586
3484
|
sort,
|
|
2587
3485
|
...pagination
|
|
@@ -2590,7 +3488,7 @@ var Service = class extends Base {
|
|
|
2590
3488
|
sort,
|
|
2591
3489
|
skip: pagination.skip,
|
|
2592
3490
|
limit: pagination.limit,
|
|
2593
|
-
selectCount: finalSelect.concat(includeLocalFields).length,
|
|
3491
|
+
selectCount: finalSelect.concat(includeLocalFields, correlatedReferenceFields).length,
|
|
2594
3492
|
populateCount: Array.isArray(filteredPopulate) ? filteredPopulate.length : filteredPopulate ? 1 : 0
|
|
2595
3493
|
});
|
|
2596
3494
|
if (_filter === false) {
|
|
@@ -2615,8 +3513,12 @@ var Service = class extends Base {
|
|
|
2615
3513
|
resolvedQuery: query
|
|
2616
3514
|
}));
|
|
2617
3515
|
const _decorate = isFunction3(decorate) ? decorate : (v) => v;
|
|
3516
|
+
const correlatedSnapshots = docs.map((doc) => cloneDeep3(toObject(doc)));
|
|
2618
3517
|
try {
|
|
2619
3518
|
docs = await this.includeDocs(docs, includes);
|
|
3519
|
+
if (correlatedIncludes.length > 0) {
|
|
3520
|
+
docs = await this.includeCorrelatedDocs(docs, correlatedIncludes, correlatedSnapshots);
|
|
3521
|
+
}
|
|
2620
3522
|
} catch (error2) {
|
|
2621
3523
|
const result = this.getClientRequestErrorResult(error2);
|
|
2622
3524
|
if (result) {
|
|
@@ -2638,8 +3540,20 @@ var Service = class extends Base {
|
|
|
2638
3540
|
doc = await this.trimOutputFields(
|
|
2639
3541
|
doc,
|
|
2640
3542
|
"list",
|
|
2641
|
-
this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
|
|
3543
|
+
this.baseFieldsExt.concat(includePaths, correlatedOutputPaths, normalizeSelect(overrideSelect))
|
|
2642
3544
|
);
|
|
3545
|
+
if (correlatedReferenceFields.length > 0) {
|
|
3546
|
+
const keep = /* @__PURE__ */ new Set([
|
|
3547
|
+
...normalizeSelect(_select),
|
|
3548
|
+
...normalizeSelect(overrideSelect),
|
|
3549
|
+
...this.baseFieldsExt,
|
|
3550
|
+
...includePaths,
|
|
3551
|
+
...correlatedOutputPaths
|
|
3552
|
+
]);
|
|
3553
|
+
for (const ref of correlatedReferenceFields) {
|
|
3554
|
+
if (!shouldKeepCorrelatedRef(ref, keep)) unsetDocPath(doc, ref);
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
2643
3557
|
doc = await _decorate(doc, contexts[i]);
|
|
2644
3558
|
if (!includePermissions) doc = this.addEmptyPermissions(doc);
|
|
2645
3559
|
return doc;
|
|
@@ -2724,7 +3638,7 @@ var Service = class extends Base {
|
|
|
2724
3638
|
validationErrors.push({ index, errors: [] });
|
|
2725
3639
|
return void 0;
|
|
2726
3640
|
}
|
|
2727
|
-
} else if (
|
|
3641
|
+
} else if (isArray8(validated)) {
|
|
2728
3642
|
if (validated.length > 0) {
|
|
2729
3643
|
validationErrors.push({
|
|
2730
3644
|
index,
|
|
@@ -2881,7 +3795,7 @@ var Service = class extends Base {
|
|
|
2881
3795
|
this.completeOp("updateOne", startedAt, "bad_request" /* BadRequest */, _filter);
|
|
2882
3796
|
return { success: false, kind: "error", code: "bad_request" /* BadRequest */ };
|
|
2883
3797
|
}
|
|
2884
|
-
} else if (
|
|
3798
|
+
} else if (isArray8(validated)) {
|
|
2885
3799
|
if (validated.length > 0) {
|
|
2886
3800
|
this.completeOp("updateOne", startedAt, "bad_request" /* BadRequest */, _filter);
|
|
2887
3801
|
return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: validated };
|
|
@@ -3068,6 +3982,23 @@ var Service = class extends Base {
|
|
|
3068
3982
|
if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
3069
3983
|
return { success: true, kind: "single", code: "success" /* Success */, data: await this.model.countDocuments(filter2), query };
|
|
3070
3984
|
}
|
|
3985
|
+
/**
|
|
3986
|
+
* Trusted count for correlated includes (ACI-03): the caller already
|
|
3987
|
+
* resolved parent references, revalidated expanded operands, and applied
|
|
3988
|
+
* explicit `count` access via `genFilter`. This skips client-filter
|
|
3989
|
+
* validation and subquery parsing so substituted parent data stays inert.
|
|
3990
|
+
*/
|
|
3991
|
+
async countTrusted(authorizedFilter) {
|
|
3992
|
+
const query = { filter: authorizedFilter };
|
|
3993
|
+
if (authorizedFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
|
|
3994
|
+
return {
|
|
3995
|
+
success: true,
|
|
3996
|
+
kind: "single",
|
|
3997
|
+
code: "success" /* Success */,
|
|
3998
|
+
data: await this.model.countDocuments(authorizedFilter),
|
|
3999
|
+
query
|
|
4000
|
+
};
|
|
4001
|
+
}
|
|
3071
4002
|
async countByFieldValues(foreignField, values, filter2 = {}, access = "list") {
|
|
3072
4003
|
if (!isValidFieldPath(foreignField)) {
|
|
3073
4004
|
return {
|
|
@@ -3583,29 +4514,64 @@ var DataService = class {
|
|
|
3583
4514
|
};
|
|
3584
4515
|
|
|
3585
4516
|
// src/processors.ts
|
|
3586
|
-
import { cloneDeep, forEach as forEach6, get as get7, isArray as
|
|
4517
|
+
import { cloneDeep as cloneDeep4, forEach as forEach6, get as get7, hasOwn, isArray as isArray9, isPlainObject as isPlainObject14, map, set as set5 } from "@web-ts-toolkit/utils";
|
|
3587
4518
|
var UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
3588
|
-
|
|
4519
|
+
var DEFAULT_MAX_SLICE = 1e3;
|
|
4520
|
+
var DEFAULT_MASK_REPLACEMENT = "***";
|
|
4521
|
+
function failUnsafeSegment(path, fn = "copyAndDepopulate") {
|
|
3589
4522
|
throw new Error(
|
|
3590
|
-
|
|
4523
|
+
`${fn}: refusing path '${path}' because it contains an unsafe segment (__proto__, prototype, or constructor).`
|
|
3591
4524
|
);
|
|
3592
4525
|
}
|
|
3593
4526
|
function failMissingId(path) {
|
|
3594
4527
|
throw new Error(`copyAndDepopulate: path '${path}' resolved to a record missing the configured id field.`);
|
|
3595
4528
|
}
|
|
3596
|
-
function
|
|
4529
|
+
function failInvalidSlice(value, field, fn) {
|
|
4530
|
+
throw new Error(`${fn}: '${field}' must be a non-negative safe integer, received ${String(value)}.`);
|
|
4531
|
+
}
|
|
4532
|
+
function assertSafePath(path, fn = "copyAndDepopulate") {
|
|
3597
4533
|
const segs = path.split(".");
|
|
3598
4534
|
for (const seg of segs) {
|
|
3599
4535
|
if (seg.length === 0) continue;
|
|
3600
4536
|
if (UNSAFE_PATH_KEYS.has(seg)) {
|
|
3601
|
-
failUnsafeSegment(path);
|
|
4537
|
+
failUnsafeSegment(path, fn);
|
|
3602
4538
|
}
|
|
3603
4539
|
}
|
|
3604
4540
|
}
|
|
4541
|
+
function assertNonNegativeSafeInteger(value, field, fn) {
|
|
4542
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
4543
|
+
failInvalidSlice(value, field, fn);
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
function resolveLeafParents(obj, src) {
|
|
4547
|
+
const segs = src.split(".");
|
|
4548
|
+
let targets = [obj];
|
|
4549
|
+
for (let ind = 0; ind < segs.length - 1; ind += 1) {
|
|
4550
|
+
const seg = segs[ind];
|
|
4551
|
+
targets = targets.reduce((ret, target) => {
|
|
4552
|
+
if (!isPlainObject14(target)) return ret;
|
|
4553
|
+
const targetObject = target;
|
|
4554
|
+
const next = targetObject[seg];
|
|
4555
|
+
if (isArray9(next)) {
|
|
4556
|
+
for (const item of next) {
|
|
4557
|
+
ret.push(item);
|
|
4558
|
+
}
|
|
4559
|
+
} else if (next !== null && next !== void 0) ret.push(next);
|
|
4560
|
+
return ret;
|
|
4561
|
+
}, []);
|
|
4562
|
+
}
|
|
4563
|
+
const leaf = segs[segs.length - 1];
|
|
4564
|
+
const pairs = [];
|
|
4565
|
+
forEach6(targets, (target) => {
|
|
4566
|
+
if (!isPlainObject14(target)) return;
|
|
4567
|
+
pairs.push({ parent: target, leaf });
|
|
4568
|
+
});
|
|
4569
|
+
return pairs;
|
|
4570
|
+
}
|
|
3605
4571
|
var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
|
|
3606
4572
|
const mutable = options.mutable !== false;
|
|
3607
4573
|
const idField = options.idField ?? "_id";
|
|
3608
|
-
const obj = mutable ? docObject :
|
|
4574
|
+
const obj = mutable ? docObject : cloneDeep4(docObject);
|
|
3609
4575
|
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
3610
4576
|
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
3611
4577
|
const src = op.src;
|
|
@@ -3618,15 +4584,15 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
3618
4584
|
forEach6(segs, (seg, ind) => {
|
|
3619
4585
|
if (segs.length === ind + 1) {
|
|
3620
4586
|
forEach6(targets, (target) => {
|
|
3621
|
-
if (!
|
|
4587
|
+
if (!isPlainObject14(target)) return;
|
|
3622
4588
|
const targetObject = target;
|
|
3623
4589
|
const leaf = targetObject[seg];
|
|
3624
4590
|
const sourceForDest = leaf;
|
|
3625
4591
|
let depopulated;
|
|
3626
|
-
if (
|
|
4592
|
+
if (isArray9(leaf)) {
|
|
3627
4593
|
if (leaf.length === 0) {
|
|
3628
4594
|
depopulated = [];
|
|
3629
|
-
} else if (leaf.every((item) =>
|
|
4595
|
+
} else if (leaf.every((item) => isPlainObject14(item))) {
|
|
3630
4596
|
depopulated = map(leaf, idField).map((id, i) => {
|
|
3631
4597
|
if (id === void 0) {
|
|
3632
4598
|
failMissingId(`${src}[${i}]`);
|
|
@@ -3636,7 +4602,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
3636
4602
|
} else {
|
|
3637
4603
|
return;
|
|
3638
4604
|
}
|
|
3639
|
-
} else if (
|
|
4605
|
+
} else if (isPlainObject14(leaf)) {
|
|
3640
4606
|
const id = get7(leaf, idField);
|
|
3641
4607
|
if (id === void 0) {
|
|
3642
4608
|
failMissingId(src);
|
|
@@ -3650,10 +4616,10 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
3650
4616
|
});
|
|
3651
4617
|
} else {
|
|
3652
4618
|
targets = targets.reduce((ret, target) => {
|
|
3653
|
-
if (!
|
|
4619
|
+
if (!isPlainObject14(target)) return ret;
|
|
3654
4620
|
const targetObject = target;
|
|
3655
4621
|
const next = targetObject[seg];
|
|
3656
|
-
if (
|
|
4622
|
+
if (isArray9(next)) {
|
|
3657
4623
|
for (const item of next) {
|
|
3658
4624
|
ret.push(item);
|
|
3659
4625
|
}
|
|
@@ -3665,6 +4631,125 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
3665
4631
|
});
|
|
3666
4632
|
return obj;
|
|
3667
4633
|
};
|
|
4634
|
+
var copyPaths = (docObject, operations, options = { mutable: true }) => {
|
|
4635
|
+
const mutable = options.mutable !== false;
|
|
4636
|
+
const obj = mutable ? docObject : cloneDeep4(docObject);
|
|
4637
|
+
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4638
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
4639
|
+
const src = op.src;
|
|
4640
|
+
const dest = op.dest;
|
|
4641
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
4642
|
+
assertSafePath(src, "copyPaths");
|
|
4643
|
+
assertSafePath(dest, "copyPaths");
|
|
4644
|
+
if (src === dest) return;
|
|
4645
|
+
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4646
|
+
if (!hasOwn(parent, leaf)) return;
|
|
4647
|
+
set5(parent, dest, cloneDeep4(parent[leaf]));
|
|
4648
|
+
});
|
|
4649
|
+
});
|
|
4650
|
+
return obj;
|
|
4651
|
+
};
|
|
4652
|
+
var movePaths = (docObject, operations, options = { mutable: true }) => {
|
|
4653
|
+
const mutable = options.mutable !== false;
|
|
4654
|
+
const obj = mutable ? docObject : cloneDeep4(docObject);
|
|
4655
|
+
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4656
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
4657
|
+
const src = op.src;
|
|
4658
|
+
const dest = op.dest;
|
|
4659
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
4660
|
+
assertSafePath(src, "movePaths");
|
|
4661
|
+
assertSafePath(dest, "movePaths");
|
|
4662
|
+
if (src === dest) return;
|
|
4663
|
+
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4664
|
+
if (!hasOwn(parent, leaf)) return;
|
|
4665
|
+
const value = cloneDeep4(parent[leaf]);
|
|
4666
|
+
set5(parent, dest, value);
|
|
4667
|
+
if (dest === leaf || dest.startsWith(`${leaf}.`)) return;
|
|
4668
|
+
delete parent[leaf];
|
|
4669
|
+
});
|
|
4670
|
+
});
|
|
4671
|
+
return obj;
|
|
4672
|
+
};
|
|
4673
|
+
var sliceArrays = (docObject, operations, options = { mutable: true, maxSlice: DEFAULT_MAX_SLICE }) => {
|
|
4674
|
+
const mutable = options.mutable !== false;
|
|
4675
|
+
const maxSlice = options.maxSlice ?? DEFAULT_MAX_SLICE;
|
|
4676
|
+
assertNonNegativeSafeInteger(maxSlice, "maxSlice", "sliceArrays");
|
|
4677
|
+
const obj = mutable ? docObject : cloneDeep4(docObject);
|
|
4678
|
+
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4679
|
+
if (!op || typeof op.src !== "string") return;
|
|
4680
|
+
const src = op.src;
|
|
4681
|
+
if (src.length === 0) return;
|
|
4682
|
+
assertSafePath(src, "sliceArrays");
|
|
4683
|
+
const rawDest = typeof op.dest === "undefined" ? void 0 : op.dest;
|
|
4684
|
+
if (typeof rawDest !== "undefined") {
|
|
4685
|
+
if (typeof rawDest !== "string" || rawDest.length === 0) return;
|
|
4686
|
+
assertSafePath(rawDest, "sliceArrays");
|
|
4687
|
+
}
|
|
4688
|
+
const skip = op.skip ?? 0;
|
|
4689
|
+
assertNonNegativeSafeInteger(skip, "skip", "sliceArrays");
|
|
4690
|
+
let limit;
|
|
4691
|
+
if (typeof op.limit !== "undefined") {
|
|
4692
|
+
assertNonNegativeSafeInteger(op.limit, "limit", "sliceArrays");
|
|
4693
|
+
limit = Math.min(op.limit, maxSlice);
|
|
4694
|
+
}
|
|
4695
|
+
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4696
|
+
const leafValue = parent[leaf];
|
|
4697
|
+
if (!isArray9(leafValue)) return;
|
|
4698
|
+
const end = typeof limit === "undefined" ? void 0 : skip + limit;
|
|
4699
|
+
const window = leafValue.slice(skip, end);
|
|
4700
|
+
if (typeof rawDest === "undefined") {
|
|
4701
|
+
set5(parent, leaf, window);
|
|
4702
|
+
} else {
|
|
4703
|
+
set5(parent, rawDest, window);
|
|
4704
|
+
}
|
|
4705
|
+
});
|
|
4706
|
+
});
|
|
4707
|
+
return obj;
|
|
4708
|
+
};
|
|
4709
|
+
var countPaths = (docObject, operations, options = { mutable: true }) => {
|
|
4710
|
+
const mutable = options.mutable !== false;
|
|
4711
|
+
const obj = mutable ? docObject : cloneDeep4(docObject);
|
|
4712
|
+
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4713
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
4714
|
+
const src = op.src;
|
|
4715
|
+
const dest = op.dest;
|
|
4716
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
4717
|
+
assertSafePath(src, "countPaths");
|
|
4718
|
+
assertSafePath(dest, "countPaths");
|
|
4719
|
+
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4720
|
+
if (!hasOwn(parent, leaf)) return;
|
|
4721
|
+
const leafValue = parent[leaf];
|
|
4722
|
+
let count;
|
|
4723
|
+
if (isArray9(leafValue)) {
|
|
4724
|
+
count = leafValue.length;
|
|
4725
|
+
} else if (isPlainObject14(leafValue)) {
|
|
4726
|
+
count = Object.keys(leafValue).length;
|
|
4727
|
+
} else if (leafValue === null || typeof leafValue === "undefined") {
|
|
4728
|
+
count = 0;
|
|
4729
|
+
} else {
|
|
4730
|
+
count = 1;
|
|
4731
|
+
}
|
|
4732
|
+
set5(parent, dest, count);
|
|
4733
|
+
});
|
|
4734
|
+
});
|
|
4735
|
+
return obj;
|
|
4736
|
+
};
|
|
4737
|
+
var maskPaths = (docObject, operations, options = { mutable: true }) => {
|
|
4738
|
+
const mutable = options.mutable !== false;
|
|
4739
|
+
const obj = mutable ? docObject : cloneDeep4(docObject);
|
|
4740
|
+
forEach6(Array.isArray(operations) ? operations : [], (op) => {
|
|
4741
|
+
if (!op || typeof op.src !== "string") return;
|
|
4742
|
+
const src = op.src;
|
|
4743
|
+
if (src.length === 0) return;
|
|
4744
|
+
assertSafePath(src, "maskPaths");
|
|
4745
|
+
const replacement = Object.prototype.hasOwnProperty.call(op, "replacement") ? op.replacement : DEFAULT_MASK_REPLACEMENT;
|
|
4746
|
+
forEach6(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
4747
|
+
if (!hasOwn(parent, leaf)) return;
|
|
4748
|
+
set5(parent, leaf, replacement);
|
|
4749
|
+
});
|
|
4750
|
+
});
|
|
4751
|
+
return obj;
|
|
4752
|
+
};
|
|
3668
4753
|
|
|
3669
4754
|
// src/symbols.ts
|
|
3670
4755
|
var MIDDLEWARE = /* @__PURE__ */ Symbol.for("@web-ts-toolkit/access-router/middleware");
|
|
@@ -3707,18 +4792,18 @@ var Cache = class {
|
|
|
3707
4792
|
import {
|
|
3708
4793
|
castArray as castArray3,
|
|
3709
4794
|
arrayToRecord,
|
|
3710
|
-
hasOwn as
|
|
3711
|
-
isArray as
|
|
4795
|
+
hasOwn as hasOwn3,
|
|
4796
|
+
isArray as isArray10,
|
|
3712
4797
|
isBoolean as isBoolean4,
|
|
3713
4798
|
isEmpty as isEmpty2,
|
|
3714
4799
|
isEqual,
|
|
3715
4800
|
isFunction as isFunction4,
|
|
3716
|
-
isPlainObject as
|
|
4801
|
+
isPlainObject as isPlainObject15,
|
|
3717
4802
|
isString as isString5
|
|
3718
4803
|
} from "@web-ts-toolkit/utils";
|
|
3719
4804
|
|
|
3720
4805
|
// src/permission.ts
|
|
3721
|
-
import { hasOwn } from "@web-ts-toolkit/utils";
|
|
4806
|
+
import { hasOwn as hasOwn2 } from "@web-ts-toolkit/utils";
|
|
3722
4807
|
var Permission = class {
|
|
3723
4808
|
constructor(permissions) {
|
|
3724
4809
|
this.permissions = { ...permissions };
|
|
@@ -3734,7 +4819,7 @@ var Permission = class {
|
|
|
3734
4819
|
return [permissionOrPermissions].concat(permissions);
|
|
3735
4820
|
}
|
|
3736
4821
|
hasKey(permission) {
|
|
3737
|
-
return
|
|
4822
|
+
return hasOwn2(this.permissions, permission);
|
|
3738
4823
|
}
|
|
3739
4824
|
has(permission) {
|
|
3740
4825
|
return this.permissions[permission] === true;
|
|
@@ -3754,10 +4839,10 @@ var permission_default = Permission;
|
|
|
3754
4839
|
|
|
3755
4840
|
// src/core-shared.ts
|
|
3756
4841
|
function isAndFilter(filter2) {
|
|
3757
|
-
return !!filter2 &&
|
|
4842
|
+
return !!filter2 && isPlainObject15(filter2) && Object.keys(filter2).length === 1 && isArray10(filter2.$and);
|
|
3758
4843
|
}
|
|
3759
4844
|
function isMergeableClause(filter2) {
|
|
3760
|
-
return !!filter2 &&
|
|
4845
|
+
return !!filter2 && isPlainObject15(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
|
|
3761
4846
|
}
|
|
3762
4847
|
function optimizeAndFilter(clausesInput) {
|
|
3763
4848
|
const clauses = [];
|
|
@@ -3772,10 +4857,10 @@ function optimizeAndFilter(clausesInput) {
|
|
|
3772
4857
|
}
|
|
3773
4858
|
}
|
|
3774
4859
|
const dedupeKey = (value) => {
|
|
3775
|
-
if (
|
|
4860
|
+
if (isArray10(value)) {
|
|
3776
4861
|
return `[${value.map(dedupeKey).join(",")}]`;
|
|
3777
4862
|
}
|
|
3778
|
-
if (
|
|
4863
|
+
if (isPlainObject15(value)) {
|
|
3779
4864
|
return `{${Object.keys(value).sort().map((key) => `${key}:${dedupeKey(value[key])}`).join(",")}}`;
|
|
3780
4865
|
}
|
|
3781
4866
|
return JSON.stringify(value);
|
|
@@ -3799,7 +4884,7 @@ function optimizeAndFilter(clausesInput) {
|
|
|
3799
4884
|
}
|
|
3800
4885
|
let canMerge = true;
|
|
3801
4886
|
for (const [key, value] of Object.entries(clause)) {
|
|
3802
|
-
if (
|
|
4887
|
+
if (hasOwn3(mergedClause, key) && !isEqual(mergedClause[key], value)) {
|
|
3803
4888
|
canMerge = false;
|
|
3804
4889
|
break;
|
|
3805
4890
|
}
|
|
@@ -3817,7 +4902,7 @@ function optimizeAndFilter(clausesInput) {
|
|
|
3817
4902
|
}
|
|
3818
4903
|
function normalizeFilter(filter2) {
|
|
3819
4904
|
if (filter2 === false) return false;
|
|
3820
|
-
if (!filter2 || !
|
|
4905
|
+
if (!filter2 || !isPlainObject15(filter2) || isEmpty2(filter2)) return null;
|
|
3821
4906
|
if (!isAndFilter(filter2)) {
|
|
3822
4907
|
return filter2;
|
|
3823
4908
|
}
|
|
@@ -3884,8 +4969,8 @@ async function setRequestPermissions(req) {
|
|
|
3884
4969
|
const globalPermissions = getGlobalOption("globalPermissions");
|
|
3885
4970
|
if (!isFunction4(globalPermissions)) return;
|
|
3886
4971
|
const permissions = await globalPermissions.call(req, req);
|
|
3887
|
-
if (
|
|
3888
|
-
else if (
|
|
4972
|
+
if (isPlainObject15(permissions)) req[requestPermissionField] = permissions;
|
|
4973
|
+
else if (isArray10(permissions)) req[requestPermissionField] = arrayToRecord(permissions);
|
|
3889
4974
|
else if (isString5(permissions)) req[requestPermissionField] = { [permissions]: true };
|
|
3890
4975
|
owners.set(requestPermissionField, activeRuntime);
|
|
3891
4976
|
}
|
|
@@ -3898,7 +4983,7 @@ async function evaluateRouteGuard(req, permissions, routeGuard) {
|
|
|
3898
4983
|
if (isString5(routeGuard)) {
|
|
3899
4984
|
return stringHandler(routeGuard);
|
|
3900
4985
|
}
|
|
3901
|
-
if (
|
|
4986
|
+
if (isArray10(routeGuard)) {
|
|
3902
4987
|
return arrayHandler(routeGuard);
|
|
3903
4988
|
}
|
|
3904
4989
|
if (isFunction4(routeGuard)) {
|
|
@@ -3936,7 +5021,7 @@ async function collectSchemaFields({
|
|
|
3936
5021
|
if (value) fields.push(key);
|
|
3937
5022
|
} else if (isString5(value)) {
|
|
3938
5023
|
if (stringHandler(value)) fields.push(key);
|
|
3939
|
-
} else if (
|
|
5024
|
+
} else if (isArray10(value)) {
|
|
3940
5025
|
if (arrayHandler(value)) fields.push(key);
|
|
3941
5026
|
} else if (isFunction4(value)) {
|
|
3942
5027
|
if (await value.call(req, ...functionArgs)) fields.push(key);
|
|
@@ -4241,7 +5326,7 @@ var Core = class {
|
|
|
4241
5326
|
if (isFunction5(validate)) {
|
|
4242
5327
|
const permissions = this.getGlobalPermissions();
|
|
4243
5328
|
return validate.call(this.req, allowedData, permissions, context);
|
|
4244
|
-
} else if (isBoolean5(validate) ||
|
|
5329
|
+
} else if (isBoolean5(validate) || isArray11(validate)) {
|
|
4245
5330
|
return validate;
|
|
4246
5331
|
} else {
|
|
4247
5332
|
return true;
|
|
@@ -4385,6 +5470,41 @@ var Core = class {
|
|
|
4385
5470
|
options
|
|
4386
5471
|
);
|
|
4387
5472
|
break;
|
|
5473
|
+
case "COPY":
|
|
5474
|
+
docObject = copyPaths(
|
|
5475
|
+
docObject,
|
|
5476
|
+
args,
|
|
5477
|
+
options
|
|
5478
|
+
);
|
|
5479
|
+
break;
|
|
5480
|
+
case "MOVE":
|
|
5481
|
+
docObject = movePaths(
|
|
5482
|
+
docObject,
|
|
5483
|
+
args,
|
|
5484
|
+
options
|
|
5485
|
+
);
|
|
5486
|
+
break;
|
|
5487
|
+
case "SLICE":
|
|
5488
|
+
docObject = sliceArrays(
|
|
5489
|
+
docObject,
|
|
5490
|
+
args,
|
|
5491
|
+
options
|
|
5492
|
+
);
|
|
5493
|
+
break;
|
|
5494
|
+
case "COUNT":
|
|
5495
|
+
docObject = countPaths(
|
|
5496
|
+
docObject,
|
|
5497
|
+
args,
|
|
5498
|
+
options
|
|
5499
|
+
);
|
|
5500
|
+
break;
|
|
5501
|
+
case "MASK":
|
|
5502
|
+
docObject = maskPaths(
|
|
5503
|
+
docObject,
|
|
5504
|
+
args,
|
|
5505
|
+
options
|
|
5506
|
+
);
|
|
5507
|
+
break;
|
|
4388
5508
|
}
|
|
4389
5509
|
});
|
|
4390
5510
|
return docObject;
|
|
@@ -4462,19 +5582,19 @@ function guard(condition) {
|
|
|
4462
5582
|
const permissions = req[PERMISSIONS];
|
|
4463
5583
|
let cond = condition;
|
|
4464
5584
|
let phas = (key) => permissions.has(key);
|
|
4465
|
-
if (
|
|
5585
|
+
if (isPlainObject16(condition)) {
|
|
4466
5586
|
const { modelName, id, condition: _cond } = condition;
|
|
4467
5587
|
const svc = req.macl.getPublicService(modelName);
|
|
4468
5588
|
const select = getModelOption(modelName, `alwaysSelectFields.read`, void 0);
|
|
4469
5589
|
let _id = isString7(id) ? id : null;
|
|
4470
|
-
if (
|
|
5590
|
+
if (isPlainObject16(id)) {
|
|
4471
5591
|
const { type = "param", key } = id;
|
|
4472
5592
|
if (type === "param") {
|
|
4473
5593
|
const paramValue = req.params[key];
|
|
4474
|
-
_id =
|
|
5594
|
+
_id = isArray12(paramValue) ? paramValue[0] ?? null : paramValue ?? null;
|
|
4475
5595
|
} else if (type === "query") {
|
|
4476
5596
|
const _qval = req.query[key];
|
|
4477
|
-
if (
|
|
5597
|
+
if (isArray12(_qval)) _id = _qval.length > 0 ? _qval[0] : null;
|
|
4478
5598
|
else _id = _qval;
|
|
4479
5599
|
} else {
|
|
4480
5600
|
_id = null;
|
|
@@ -4494,7 +5614,7 @@ function guard(condition) {
|
|
|
4494
5614
|
const { stringHandler, arrayHandler } = createValidator(phas);
|
|
4495
5615
|
if (isString7(cond)) {
|
|
4496
5616
|
if (stringHandler(cond)) return next();
|
|
4497
|
-
} else if (
|
|
5617
|
+
} else if (isArray12(cond)) {
|
|
4498
5618
|
if (arrayHandler(cond)) return next();
|
|
4499
5619
|
} else if (isFunction6(cond)) {
|
|
4500
5620
|
const result = await cond.call(req, permissions);
|
|
@@ -4509,7 +5629,7 @@ import express2 from "express";
|
|
|
4509
5629
|
|
|
4510
5630
|
// src/routers/model-router.ts
|
|
4511
5631
|
import JsonRouter5 from "@web-ts-toolkit/express-json-router";
|
|
4512
|
-
import { forEach as forEach8, isPlainObject as
|
|
5632
|
+
import { forEach as forEach8, isPlainObject as isPlainObject18, isString as isString8, isUndefined as isUndefined2, normalizeUrlPath as normalizeUrlPath2, padEnd } from "@web-ts-toolkit/utils";
|
|
4513
5633
|
|
|
4514
5634
|
// src/routers/router-mutation.ts
|
|
4515
5635
|
var MODEL_BUILD_TIME_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -4638,6 +5758,7 @@ import JsonRouter4 from "@web-ts-toolkit/express-json-router";
|
|
|
4638
5758
|
|
|
4639
5759
|
// src/validation/common.ts
|
|
4640
5760
|
import { z as z2 } from "zod";
|
|
5761
|
+
import { isPlainObject as isPlainObject17 } from "@web-ts-toolkit/utils";
|
|
4641
5762
|
var stringOrStringArray = z2.union([z2.string(), z2.array(z2.string())]);
|
|
4642
5763
|
var safeIntegerString = (min, message) => z2.string().regex(/^\d+$/, message).refine((value) => Number.isSafeInteger(Number(value)), "Expected a safe integer").refine((value) => Number(value) >= min, message);
|
|
4643
5764
|
var queryBooleanString = z2.enum(["true", "false"]);
|
|
@@ -4698,7 +5819,8 @@ var subPopulateSchema = z2.union([
|
|
|
4698
5819
|
select: projectionSchema.optional()
|
|
4699
5820
|
}).passthrough()
|
|
4700
5821
|
]);
|
|
4701
|
-
var
|
|
5822
|
+
var legacyIncludeItemSchema = z2.object({
|
|
5823
|
+
mode: z2.enum(["legacy"]).optional(),
|
|
4702
5824
|
model: z2.string().min(1),
|
|
4703
5825
|
op: z2.enum(["list", "read", "count"]),
|
|
4704
5826
|
path: z2.string().min(1),
|
|
@@ -4707,8 +5829,139 @@ var includeItemSchema = z2.object({
|
|
|
4707
5829
|
foreignField: z2.string().min(1),
|
|
4708
5830
|
args: z2.unknown().optional().describe("Forwarded to list/read includes. Count includes ignore pagination fields so counts remain exact."),
|
|
4709
5831
|
options: z2.unknown().optional()
|
|
4710
|
-
}).passthrough()
|
|
4711
|
-
|
|
5832
|
+
}).passthrough().superRefine((entry, ctx) => {
|
|
5833
|
+
if (entry.filter !== void 0 && containsParentMarkerShape(entry.filter)) {
|
|
5834
|
+
ctx.addIssue({
|
|
5835
|
+
code: z2.ZodIssueCode.custom,
|
|
5836
|
+
message: 'Legacy includes must not contain $parent markers; use a correlated include (mode: "correlated")',
|
|
5837
|
+
path: ["filter"]
|
|
5838
|
+
});
|
|
5839
|
+
}
|
|
5840
|
+
});
|
|
5841
|
+
var parentRefSchema = z2.object({ $parent: z2.string().min(1) }).strict().superRefine((value, ctx) => {
|
|
5842
|
+
if (!isValidParentRefPath(value.$parent)) {
|
|
5843
|
+
ctx.addIssue({
|
|
5844
|
+
code: z2.ZodIssueCode.custom,
|
|
5845
|
+
message: `Invalid $parent reference path: ${value.$parent}`,
|
|
5846
|
+
path: ["$parent"]
|
|
5847
|
+
});
|
|
5848
|
+
}
|
|
5849
|
+
});
|
|
5850
|
+
var correlatedIdSchema = z2.union([z2.string().min(1), parentRefSchema]);
|
|
5851
|
+
var correlatedFilterSchema = z2.record(z2.string(), z2.unknown()).superRefine((filter2, ctx) => {
|
|
5852
|
+
for (const issue of validateCorrelatedFilterTemplate(filter2)) {
|
|
5853
|
+
ctx.addIssue({
|
|
5854
|
+
code: z2.ZodIssueCode.custom,
|
|
5855
|
+
message: issue.message,
|
|
5856
|
+
path: issue.path.map(String)
|
|
5857
|
+
});
|
|
5858
|
+
}
|
|
5859
|
+
});
|
|
5860
|
+
var correlatedEmptyOptionsSchema = z2.unknown().refine(
|
|
5861
|
+
(value) => value === void 0 || isPlainObject17(value) && Object.keys(value).length === 0,
|
|
5862
|
+
"Correlated include options must be absent or empty"
|
|
5863
|
+
).optional();
|
|
5864
|
+
var correlatedPathSchema = z2.string().min(1).superRefine((path, ctx) => {
|
|
5865
|
+
if (!isValidCorrelatedOutputPath(path)) {
|
|
5866
|
+
ctx.addIssue({
|
|
5867
|
+
code: z2.ZodIssueCode.custom,
|
|
5868
|
+
message: `Invalid correlated include output path: ${path}`,
|
|
5869
|
+
path: []
|
|
5870
|
+
});
|
|
5871
|
+
}
|
|
5872
|
+
});
|
|
5873
|
+
var checkArgsMarkers = (select, sort, ctx) => {
|
|
5874
|
+
const selectIssue = findArgsMarkerIssue(select, "args.select");
|
|
5875
|
+
if (selectIssue) {
|
|
5876
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, message: selectIssue.message, path: ["select"] });
|
|
5877
|
+
}
|
|
5878
|
+
const sortIssue = findArgsMarkerIssue(sort, "args.sort");
|
|
5879
|
+
if (sortIssue) {
|
|
5880
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, message: sortIssue.message, path: ["sort"] });
|
|
5881
|
+
}
|
|
5882
|
+
};
|
|
5883
|
+
var correlatedReadArgsSchema = z2.object({
|
|
5884
|
+
select: projectionSchema.optional(),
|
|
5885
|
+
sort: sortSchema.optional(),
|
|
5886
|
+
include: z2.lazy(() => correlatedNestedIncludeSchema).optional()
|
|
5887
|
+
}).strict().superRefine((args, ctx) => checkArgsMarkers(args.select, args.sort, ctx));
|
|
5888
|
+
var correlatedListArgsSchema = z2.object({
|
|
5889
|
+
select: projectionSchema.optional(),
|
|
5890
|
+
sort: sortSchema.optional(),
|
|
5891
|
+
skip: z2.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
5892
|
+
limit: z2.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
5893
|
+
page: z2.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
|
|
5894
|
+
pageSize: z2.union([positiveIntegerSchema, positiveIntegerString]).optional(),
|
|
5895
|
+
include: z2.lazy(() => correlatedNestedIncludeSchema).optional()
|
|
5896
|
+
}).strict().superRefine((args, ctx) => checkArgsMarkers(args.select, args.sort, ctx));
|
|
5897
|
+
var correlatedCountArgsSchema = z2.unknown().refine(
|
|
5898
|
+
(value) => value === void 0 || isPlainObject17(value) && Object.keys(value).length === 0,
|
|
5899
|
+
"Count includes accept no args"
|
|
5900
|
+
).optional();
|
|
5901
|
+
var correlatedReadByIdSchema = z2.object({
|
|
5902
|
+
mode: z2.literal("correlated"),
|
|
5903
|
+
model: z2.string().min(1),
|
|
5904
|
+
op: z2.literal("read"),
|
|
5905
|
+
path: correlatedPathSchema,
|
|
5906
|
+
id: correlatedIdSchema,
|
|
5907
|
+
args: correlatedReadArgsSchema.optional(),
|
|
5908
|
+
options: correlatedEmptyOptionsSchema
|
|
5909
|
+
}).strict();
|
|
5910
|
+
var correlatedReadByFilterSchema = z2.object({
|
|
5911
|
+
mode: z2.literal("correlated"),
|
|
5912
|
+
model: z2.string().min(1),
|
|
5913
|
+
op: z2.literal("read"),
|
|
5914
|
+
path: correlatedPathSchema,
|
|
5915
|
+
filter: correlatedFilterSchema,
|
|
5916
|
+
args: correlatedReadArgsSchema.optional(),
|
|
5917
|
+
options: correlatedEmptyOptionsSchema
|
|
5918
|
+
}).strict();
|
|
5919
|
+
var correlatedListSchema = z2.object({
|
|
5920
|
+
mode: z2.literal("correlated"),
|
|
5921
|
+
model: z2.string().min(1),
|
|
5922
|
+
op: z2.literal("list"),
|
|
5923
|
+
path: correlatedPathSchema,
|
|
5924
|
+
filter: correlatedFilterSchema,
|
|
5925
|
+
args: correlatedListArgsSchema.optional(),
|
|
5926
|
+
options: correlatedEmptyOptionsSchema
|
|
5927
|
+
}).strict();
|
|
5928
|
+
var correlatedCountSchema = z2.object({
|
|
5929
|
+
mode: z2.literal("correlated"),
|
|
5930
|
+
model: z2.string().min(1),
|
|
5931
|
+
op: z2.literal("count"),
|
|
5932
|
+
path: correlatedPathSchema,
|
|
5933
|
+
filter: correlatedFilterSchema,
|
|
5934
|
+
args: correlatedCountArgsSchema,
|
|
5935
|
+
options: correlatedEmptyOptionsSchema
|
|
5936
|
+
}).strict();
|
|
5937
|
+
var includeItemSchema = z2.union([
|
|
5938
|
+
legacyIncludeItemSchema,
|
|
5939
|
+
correlatedReadByIdSchema,
|
|
5940
|
+
correlatedReadByFilterSchema,
|
|
5941
|
+
correlatedListSchema,
|
|
5942
|
+
correlatedCountSchema
|
|
5943
|
+
]);
|
|
5944
|
+
var correlatedNestedIncludeSchema = z2.union([includeItemSchema, z2.array(includeItemSchema)]);
|
|
5945
|
+
var rejectDuplicateCorrelatedPaths = (items, ctx) => {
|
|
5946
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5947
|
+
items.forEach((item, index) => {
|
|
5948
|
+
if (isPlainObject17(item) && item.mode === "correlated" && typeof item.path === "string") {
|
|
5949
|
+
if (seen.has(item.path)) {
|
|
5950
|
+
ctx.addIssue({
|
|
5951
|
+
code: z2.ZodIssueCode.custom,
|
|
5952
|
+
message: `Duplicate correlated include output path: ${item.path}`,
|
|
5953
|
+
path: [index, "path"]
|
|
5954
|
+
});
|
|
5955
|
+
} else {
|
|
5956
|
+
seen.add(item.path);
|
|
5957
|
+
}
|
|
5958
|
+
}
|
|
5959
|
+
});
|
|
5960
|
+
};
|
|
5961
|
+
var includeSchema = z2.union([
|
|
5962
|
+
includeItemSchema,
|
|
5963
|
+
z2.array(includeItemSchema).superRefine(rejectDuplicateCorrelatedPaths)
|
|
5964
|
+
]);
|
|
4712
5965
|
var fieldsSchema = z2.array(z2.string().min(1));
|
|
4713
5966
|
var taskSchema = z2.object({
|
|
4714
5967
|
type: z2.string().min(1),
|
|
@@ -5245,7 +6498,7 @@ var listBodySchema = z3.object({
|
|
|
5245
6498
|
}).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query"]));
|
|
5246
6499
|
var countBodySchema = z3.object({
|
|
5247
6500
|
filter: objectOrArraySchema.optional()
|
|
5248
|
-
}).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query", "access", "options"]));
|
|
6501
|
+
}).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query", "access", "options", "include"]));
|
|
5249
6502
|
var readFilterBodySchema = z3.object({
|
|
5250
6503
|
filter: objectOrArraySchema.optional(),
|
|
5251
6504
|
select: projectionSchema.optional(),
|
|
@@ -6523,7 +7776,7 @@ var ModelRouter = class _ModelRouter {
|
|
|
6523
7776
|
value
|
|
6524
7777
|
);
|
|
6525
7778
|
}
|
|
6526
|
-
if (arguments.length === 1 &&
|
|
7779
|
+
if (arguments.length === 1 && isPlainObject18(keyOrOptions)) {
|
|
6527
7780
|
assertMutableRouterOptions("model", keyOrOptions);
|
|
6528
7781
|
this.runtime.setModelOptions(this.modelName, keyOrOptions);
|
|
6529
7782
|
}
|
|
@@ -6962,7 +8215,7 @@ var RootRouter = class {
|
|
|
6962
8215
|
|
|
6963
8216
|
// src/routers/data-router.ts
|
|
6964
8217
|
import JsonRouter7 from "@web-ts-toolkit/express-json-router";
|
|
6965
|
-
import { isPlainObject as
|
|
8218
|
+
import { isPlainObject as isPlainObject19, isString as isString9, isUndefined as isUndefined3, normalizeUrlPath as normalizeUrlPath4 } from "@web-ts-toolkit/utils";
|
|
6966
8219
|
var clientErrors4 = JsonRouter7.clientErrors;
|
|
6967
8220
|
function setOption2(parentKey, optionKey, option) {
|
|
6968
8221
|
const key = isUndefined3(option) ? parentKey : `${parentKey}.${optionKey}`;
|
|
@@ -7163,7 +8416,7 @@ var DataRouter = class {
|
|
|
7163
8416
|
assertMutableRouterOption("data", keyOrOptions);
|
|
7164
8417
|
this.runtime.setDataOption(this.dataName, keyOrOptions, value);
|
|
7165
8418
|
}
|
|
7166
|
-
if (arguments.length === 1 &&
|
|
8419
|
+
if (arguments.length === 1 && isPlainObject19(keyOrOptions)) {
|
|
7167
8420
|
assertMutableRouterOptions("data", keyOrOptions);
|
|
7168
8421
|
this.runtime.setDataOptions(this.dataName, keyOrOptions);
|
|
7169
8422
|
}
|
|
@@ -7310,7 +8563,7 @@ function createRuntimeApi(runtime) {
|
|
|
7310
8563
|
if (arguments.length === 2 && isString10(keyOrOptions)) {
|
|
7311
8564
|
return runtime.setGlobalOption(keyOrOptions, value);
|
|
7312
8565
|
}
|
|
7313
|
-
if (arguments.length === 1 &&
|
|
8566
|
+
if (arguments.length === 1 && isPlainObject20(keyOrOptions)) {
|
|
7314
8567
|
return runtime.setGlobalOptions(keyOrOptions);
|
|
7315
8568
|
}
|
|
7316
8569
|
};
|