@web-ts-toolkit/access-router 0.43.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/index.mjs CHANGED
@@ -1,16 +1,16 @@
1
1
  // src/index.ts
2
- import { isPlainObject as isPlainObject18, isString as isString10, isUndefined as isUndefined4 } from "@web-ts-toolkit/utils";
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 isArray11, isFunction as isFunction6, isPlainObject as isPlainObject15, isString as isString7 } from "@web-ts-toolkit/utils";
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 isArray10,
13
+ isArray as isArray11,
14
14
  isBoolean as isBoolean5,
15
15
  isFunction as isFunction5,
16
16
  isString as isString6,
@@ -784,6 +784,29 @@ var cloneConfigValue = (value) => {
784
784
  ])
785
785
  );
786
786
  };
787
+ var cloneForRead = (value) => {
788
+ if (Array.isArray(value)) {
789
+ if (Object.isFrozen(value)) {
790
+ return value;
791
+ }
792
+ return value.map((item) => cloneForRead(item));
793
+ }
794
+ if (!value || typeof value !== "object") {
795
+ return value;
796
+ }
797
+ if (Object.getPrototypeOf(value) !== Object.prototype) {
798
+ return value;
799
+ }
800
+ if (Object.isFrozen(value)) {
801
+ return value;
802
+ }
803
+ return Object.fromEntries(
804
+ Object.entries(value).map(([entryKey, entryValue]) => [
805
+ entryKey,
806
+ cloneForRead(entryValue)
807
+ ])
808
+ );
809
+ };
787
810
  var freezeConfigValue = (value) => {
788
811
  if (!value || typeof value !== "object") {
789
812
  return value;
@@ -799,6 +822,7 @@ var freezeConfigValue = (value) => {
799
822
  }
800
823
  return value;
801
824
  };
825
+ var cloneAndFreezeForStore = (value) => freezeConfigValue(cloneForRead(value));
802
826
  var getNestedOption = (manager, key, defaultValue) => {
803
827
  const keys2 = String(key).split(".");
804
828
  if (keys2.length === 1) {
@@ -828,6 +852,14 @@ var OptionsManager = class {
828
852
  target[keystr] = value;
829
853
  _this.listeners[keystr] && _this.listeners[keystr].call(_this, value, keystr, target, oldvalue);
830
854
  return true;
855
+ },
856
+ defineProperty(target, key, descriptor) {
857
+ const keystr = String(key);
858
+ const oldvalue = target[keystr];
859
+ const result = Reflect.defineProperty(target, key, descriptor);
860
+ const newValue = target[keystr];
861
+ _this.listeners[keystr] && _this.listeners[keystr].call(_this, newValue, keystr, target, oldvalue);
862
+ return result;
831
863
  }
832
864
  });
833
865
  }
@@ -841,18 +873,18 @@ var OptionsManager = class {
841
873
  set(key, value) {
842
874
  const path = toPath(key);
843
875
  if (path.length <= 1) {
844
- set2(this.currentOptions, path, this.preserveKeys.has(String(path[0])) ? value : cloneConfigValue(value));
876
+ set2(this.currentOptions, path, this.preserveKeys.has(String(path[0])) ? value : cloneAndFreezeForStore(value));
845
877
  return;
846
878
  }
847
879
  const [rootKey, ...nestedPath] = path;
848
880
  const currentRoot = get2(this.currentOptions, [rootKey], void 0);
849
881
  const baseRoot = isPlainObject8(currentRoot) ? currentRoot : {};
850
882
  const nextRoot = cloneConfigValue(baseRoot);
851
- set2(nextRoot, nestedPath, cloneConfigValue(value));
852
- set2(this.currentOptions, [rootKey], nextRoot);
883
+ set2(nextRoot, nestedPath, cloneForRead(value));
884
+ set2(this.currentOptions, [rootKey], freezeConfigValue(nextRoot));
853
885
  }
854
886
  fetch() {
855
- const cloned = cloneConfigValue(this.currentOptions);
887
+ const cloned = cloneForRead(this.currentOptions);
856
888
  for (const key of this.preserveKeys) {
857
889
  if (key in this.currentOptions) {
858
890
  cloned[key] = this.currentOptions[key];
@@ -861,20 +893,20 @@ var OptionsManager = class {
861
893
  return freezeConfigValue(cloned);
862
894
  }
863
895
  assign(options) {
864
- const cloned = cloneConfigValue(options);
896
+ const cloned = cloneForRead(options);
865
897
  for (const key of this.preserveKeys) {
866
898
  if (key in options) {
867
899
  cloned[key] = options[key];
868
900
  }
869
901
  }
870
- assign(this.currentOptions, cloned);
902
+ assign(this.currentOptions, freezeConfigValue(cloned));
871
903
  }
872
904
  onchange(key, func) {
873
905
  set2(this.listeners, key, func);
874
906
  return this;
875
907
  }
876
908
  snapshot() {
877
- const cloned = cloneConfigValue(this.currentOptions);
909
+ const cloned = cloneForRead(this.currentOptions);
878
910
  for (const key of this.preserveKeys) {
879
911
  if (key in this.currentOptions) {
880
912
  cloned[key] = this.currentOptions[key];
@@ -890,13 +922,13 @@ var OptionsManager = class {
890
922
  delete current[key];
891
923
  }
892
924
  }
893
- const cloned = cloneConfigValue(snap);
925
+ const cloned = cloneForRead(snap);
894
926
  for (const key of this.preserveKeys) {
895
927
  if (key in snap) {
896
928
  cloned[key] = snap[key];
897
929
  }
898
930
  }
899
- assign(current, cloned);
931
+ assign(current, freezeConfigValue(cloned));
900
932
  }
901
933
  };
902
934
 
@@ -911,7 +943,9 @@ var defaultRequestComplexity = {
911
943
  maxIncludeCount: 10,
912
944
  maxSubQueryCount: 10,
913
945
  maxBulkConcurrency: 10,
914
- maxHookConcurrency: 10
946
+ maxHookConcurrency: 10,
947
+ maxCorrelatedQueries: 100,
948
+ maxCorrelatedDepth: 5
915
949
  };
916
950
  var normalizePositiveInteger = (value, fallback) => {
917
951
  return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : fallback;
@@ -931,6 +965,14 @@ var resolveRequestComplexity = (options) => ({
931
965
  maxHookConcurrency: normalizePositiveInteger(
932
966
  options?.maxHookConcurrency,
933
967
  defaultRequestComplexity.maxHookConcurrency
968
+ ),
969
+ maxCorrelatedQueries: normalizePositiveInteger(
970
+ options?.maxCorrelatedQueries,
971
+ defaultRequestComplexity.maxCorrelatedQueries
972
+ ),
973
+ maxCorrelatedDepth: normalizePositiveInteger(
974
+ options?.maxCorrelatedDepth,
975
+ defaultRequestComplexity.maxCorrelatedDepth
934
976
  )
935
977
  });
936
978
  function validateRequestComplexity(value, options, scope = "request") {
@@ -1366,8 +1408,7 @@ var AccessRuntime = class {
1366
1408
  }
1367
1409
  setDataOptions(dataName, options) {
1368
1410
  const manager = this.getOrCreateDataOptions(dataName);
1369
- const currentDataOptions = manager.fetch();
1370
- manager.assign({ ...currentDataOptions, ...options });
1411
+ manager.assign(options);
1371
1412
  }
1372
1413
  setDataOption(dataName, key, value) {
1373
1414
  const manager = this.getOrCreateDataOptions(dataName);
@@ -1377,6 +1418,11 @@ var AccessRuntime = class {
1377
1418
  const manager = this.getOrCreateDataOptions(dataName);
1378
1419
  return manager.fetch();
1379
1420
  }
1421
+ getDataSnapshot(dataName) {
1422
+ const manager = this.getOrCreateDataOptions(dataName);
1423
+ const snapshot = manager.get("data");
1424
+ return snapshot ?? [];
1425
+ }
1380
1426
  getDataOption(dataName, key, defaultValue) {
1381
1427
  const manager = this.getOrCreateDataOptions(dataName);
1382
1428
  return getNestedOption(manager, key, defaultValue);
@@ -1498,6 +1544,9 @@ var getRuntime4 = () => getActiveRuntime() ?? defaultRuntime;
1498
1544
  var getDataOptions = (dataName) => {
1499
1545
  return getRuntime4().getDataOptions(dataName);
1500
1546
  };
1547
+ var getDataSnapshot = (dataName) => {
1548
+ return getRuntime4().getDataSnapshot(dataName);
1549
+ };
1501
1550
  var getDataOption = (dataName, key, defaultValue) => {
1502
1551
  return getRuntime4().getDataOption(dataName, key, defaultValue);
1503
1552
  };
@@ -1507,21 +1556,475 @@ var getRuntime5 = () => getActiveRuntime() ?? defaultRuntime;
1507
1556
  var getModelRef = (modelName, refPath) => {
1508
1557
  return getRuntime5().getModelRef(modelName, refPath);
1509
1558
  };
1510
- var getModelSub = (modelName) => {
1511
- return getRuntime5().getModelSub(modelName);
1512
- };
1513
1559
 
1514
1560
  // src/services/base.ts
1515
1561
  import {
1516
1562
  castArray,
1563
+ cloneDeep as cloneDeep2,
1517
1564
  compact,
1518
1565
  flatten,
1519
1566
  forEach as forEach3,
1520
1567
  get as get4,
1521
- isArray as isArray6,
1522
- isPlainObject as isPlainObject11,
1568
+ isArray as isArray7,
1569
+ isPlainObject as isPlainObject12,
1523
1570
  uniq
1524
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
+ }
1525
2028
  var ClientRequestError = class extends Error {
1526
2029
  constructor(result) {
1527
2030
  super(String(result.code));
@@ -1536,11 +2039,11 @@ function validateClientFilter(filter2) {
1536
2039
  }
1537
2040
  const blockedOperators = /* @__PURE__ */ new Set(["$where", "$expr", "$function", "$accumulator"]);
1538
2041
  const visit = (value, path) => {
1539
- if (isArray6(value)) {
2042
+ if (isArray7(value)) {
1540
2043
  value.forEach((item, index) => visit(item, `${path}[${index}]`));
1541
2044
  return;
1542
2045
  }
1543
- if (!isPlainObject11(value)) return;
2046
+ if (!isPlainObject12(value)) return;
1544
2047
  Object.entries(value).forEach(([key, child]) => {
1545
2048
  const nextPath = path ? `${path}.${key}` : key;
1546
2049
  if (blockedOperators.has(key)) {
@@ -1660,21 +2163,52 @@ var Base = class {
1660
2163
  return this.req.macl.getPublicService(modelName);
1661
2164
  }
1662
2165
  processInclude(include) {
1663
- const includes = compact(castArray(include)).filter(({ model, op, path, localField, foreignField }) => {
1664
- return model && op && path && localField && foreignField;
1665
- });
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
+ }
1666
2198
  let includeLocalFields = [];
1667
2199
  let includePaths = [];
1668
- forEach3(includes, (inc) => {
2200
+ forEach3(legacy, (inc) => {
1669
2201
  includeLocalFields.push(inc.localField);
1670
2202
  includePaths.push(inc.path);
1671
2203
  });
1672
2204
  includeLocalFields = uniq(compact(includeLocalFields));
1673
2205
  includePaths = uniq(compact(includePaths));
1674
2206
  return {
1675
- includes,
2207
+ includes: legacy,
2208
+ correlatedIncludes: correlated,
1676
2209
  includeLocalFields,
1677
- includePaths
2210
+ includePaths,
2211
+ correlatedReferenceFields: uniq(compact(correlatedReferenceFields))
1678
2212
  };
1679
2213
  }
1680
2214
  getForeignKeyValues(value) {
@@ -1717,7 +2251,7 @@ var Base = class {
1717
2251
  return ids.size;
1718
2252
  }
1719
2253
  sanitizeIncludeArgs(args) {
1720
- if (!isPlainObject11(args)) return {};
2254
+ if (!isPlainObject12(args)) return {};
1721
2255
  const { overrides: _clientOverrides, ...trustedArgs } = args;
1722
2256
  return trustedArgs;
1723
2257
  }
@@ -1730,10 +2264,16 @@ var Base = class {
1730
2264
  if (!include) return docs;
1731
2265
  const includes = compact(castArray(include));
1732
2266
  if (includes.length === 0) return docs;
1733
- const isSingle = !isArray6(docs);
2267
+ const isSingle = !isArray7(docs);
1734
2268
  let docList = isSingle ? [docs] : docs;
1735
2269
  for (let x = 0; x < includes.length; x++) {
1736
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
+ }
1737
2277
  this.assertIncludeForeignField(include2);
1738
2278
  switch (include2.op) {
1739
2279
  case "count":
@@ -1812,6 +2352,356 @@ var Base = class {
1812
2352
  }
1813
2353
  return docs;
1814
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
+ }
1815
2705
  async parseClientData(filter2, scheduler = new RequestConcurrencyScheduler(this.getRequestComplexity().maxBulkConcurrency), scheduled = false) {
1816
2706
  const result = await iterateQuery(
1817
2707
  filter2,
@@ -1854,7 +2744,7 @@ var Base = class {
1854
2744
  }
1855
2745
  let ret = result.data;
1856
2746
  if (sqOptions.path) {
1857
- ret = isArray6(ret) ? flatten(ret.map((v) => get4(v, sqOptions.path))) : get4(ret, sqOptions.path);
2747
+ ret = isArray7(ret) ? flatten(ret.map((v) => get4(v, sqOptions.path))) : get4(ret, sqOptions.path);
1858
2748
  }
1859
2749
  if (sqOptions.compact) {
1860
2750
  ret = compact(castArray(ret));
@@ -1871,8 +2761,9 @@ var Base = class {
1871
2761
  // src/services/service.ts
1872
2762
  import {
1873
2763
  castArray as castArray2,
2764
+ cloneDeep as cloneDeep3,
1874
2765
  compact as compact2,
1875
- isArray as isArray7,
2766
+ isArray as isArray8,
1876
2767
  isBoolean as isBoolean3,
1877
2768
  isFunction as isFunction3,
1878
2769
  omit,
@@ -2375,6 +3266,29 @@ var assertModelDocument = (value, modelName, hookName) => {
2375
3266
  }
2376
3267
  throw new Error(`${hookName} hook for model=${modelName} must return a Mongoose document instance`);
2377
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
+ };
2378
3292
  var Service = class extends Base {
2379
3293
  findRawParentDoc(args) {
2380
3294
  return this.model.findOne({
@@ -2441,8 +3355,9 @@ var Service = class extends Base {
2441
3355
  overridePopulate || this.genPopulate(populateAccess || access, populate),
2442
3356
  this.genAllowedFields({}, access, this.baseFieldsExt)
2443
3357
  ]);
2444
- const { includes, includeLocalFields, includePaths } = this.processInclude(include);
2445
- const finalSelect = normalizeSelect(_select).concat(includeLocalFields);
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);
2446
3361
  const query = {
2447
3362
  filter: _filter,
2448
3363
  select: finalSelect,
@@ -2475,8 +3390,14 @@ var Service = class extends Base {
2475
3390
  originalDocumentSnapshot: toObject(doc),
2476
3391
  resolvedQuery: query
2477
3392
  };
3393
+ const correlatedSnapshots = [cloneDeep3(toObject(doc))];
2478
3394
  try {
2479
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
+ }
2480
3401
  } catch (error2) {
2481
3402
  const result = this.getClientRequestErrorResult(error2);
2482
3403
  if (result) {
@@ -2494,8 +3415,20 @@ var Service = class extends Base {
2494
3415
  doc = await this.trimOutputFields(
2495
3416
  doc,
2496
3417
  access,
2497
- this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
3418
+ this.baseFieldsExt.concat(includePaths, correlatedOutputPaths, normalizeSelect(overrideSelect))
2498
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
+ }
2499
3432
  if (!includePermissions) doc = this.addEmptyPermissions(doc);
2500
3433
  this.completeOp("findOne", startedAt, "success" /* Success */, _filter);
2501
3434
  return { success: true, kind: "single", code: "success" /* Success */, data: doc, query, context };
@@ -2541,11 +3474,12 @@ var Service = class extends Base {
2541
3474
  this.genAllowedFields({}, "list", this.baseFieldsExt)
2542
3475
  ]);
2543
3476
  const finalSelect = normalizeSelect(_select);
2544
- const filteredPopulate = isArray7(finalSelect) && isArray7(_populate) ? _populate.filter((p) => finalSelect.includes(p.path.split(".")[0])) : _populate;
2545
- 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);
2546
3480
  const query = {
2547
3481
  filter: _filter,
2548
- select: finalSelect.concat(includeLocalFields),
3482
+ select: finalSelect.concat(includeLocalFields, correlatedReferenceFields),
2549
3483
  populate: filteredPopulate,
2550
3484
  sort,
2551
3485
  ...pagination
@@ -2554,7 +3488,7 @@ var Service = class extends Base {
2554
3488
  sort,
2555
3489
  skip: pagination.skip,
2556
3490
  limit: pagination.limit,
2557
- selectCount: finalSelect.concat(includeLocalFields).length,
3491
+ selectCount: finalSelect.concat(includeLocalFields, correlatedReferenceFields).length,
2558
3492
  populateCount: Array.isArray(filteredPopulate) ? filteredPopulate.length : filteredPopulate ? 1 : 0
2559
3493
  });
2560
3494
  if (_filter === false) {
@@ -2579,8 +3513,12 @@ var Service = class extends Base {
2579
3513
  resolvedQuery: query
2580
3514
  }));
2581
3515
  const _decorate = isFunction3(decorate) ? decorate : (v) => v;
3516
+ const correlatedSnapshots = docs.map((doc) => cloneDeep3(toObject(doc)));
2582
3517
  try {
2583
3518
  docs = await this.includeDocs(docs, includes);
3519
+ if (correlatedIncludes.length > 0) {
3520
+ docs = await this.includeCorrelatedDocs(docs, correlatedIncludes, correlatedSnapshots);
3521
+ }
2584
3522
  } catch (error2) {
2585
3523
  const result = this.getClientRequestErrorResult(error2);
2586
3524
  if (result) {
@@ -2602,8 +3540,20 @@ var Service = class extends Base {
2602
3540
  doc = await this.trimOutputFields(
2603
3541
  doc,
2604
3542
  "list",
2605
- this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
3543
+ this.baseFieldsExt.concat(includePaths, correlatedOutputPaths, normalizeSelect(overrideSelect))
2606
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
+ }
2607
3557
  doc = await _decorate(doc, contexts[i]);
2608
3558
  if (!includePermissions) doc = this.addEmptyPermissions(doc);
2609
3559
  return doc;
@@ -2688,7 +3638,7 @@ var Service = class extends Base {
2688
3638
  validationErrors.push({ index, errors: [] });
2689
3639
  return void 0;
2690
3640
  }
2691
- } else if (isArray7(validated)) {
3641
+ } else if (isArray8(validated)) {
2692
3642
  if (validated.length > 0) {
2693
3643
  validationErrors.push({
2694
3644
  index,
@@ -2845,7 +3795,7 @@ var Service = class extends Base {
2845
3795
  this.completeOp("updateOne", startedAt, "bad_request" /* BadRequest */, _filter);
2846
3796
  return { success: false, kind: "error", code: "bad_request" /* BadRequest */ };
2847
3797
  }
2848
- } else if (isArray7(validated)) {
3798
+ } else if (isArray8(validated)) {
2849
3799
  if (validated.length > 0) {
2850
3800
  this.completeOp("updateOne", startedAt, "bad_request" /* BadRequest */, _filter);
2851
3801
  return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: validated };
@@ -3032,6 +3982,23 @@ var Service = class extends Base {
3032
3982
  if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
3033
3983
  return { success: true, kind: "single", code: "success" /* Success */, data: await this.model.countDocuments(filter2), query };
3034
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
+ }
3035
4002
  async countByFieldValues(foreignField, values, filter2 = {}, access = "list") {
3036
4003
  if (!isValidFieldPath(foreignField)) {
3037
4004
  return {
@@ -3436,7 +4403,7 @@ var DataService = class {
3436
4403
  this.req = req;
3437
4404
  this.dataName = dataName;
3438
4405
  this.options = getDataOptions(dataName);
3439
- this.data = this.options.data ?? [];
4406
+ this.data = getDataSnapshot(dataName);
3440
4407
  }
3441
4408
  async findOne(filter2, args, options) {
3442
4409
  const filterErrors = validateClientFilter(filter2);
@@ -3547,29 +4514,64 @@ var DataService = class {
3547
4514
  };
3548
4515
 
3549
4516
  // src/processors.ts
3550
- import { cloneDeep, forEach as forEach6, get as get7, isArray as isArray8, isPlainObject as isPlainObject13, map, set as set5 } from "@web-ts-toolkit/utils";
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";
3551
4518
  var UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
3552
- function failUnsafeSegment(path) {
4519
+ var DEFAULT_MAX_SLICE = 1e3;
4520
+ var DEFAULT_MASK_REPLACEMENT = "***";
4521
+ function failUnsafeSegment(path, fn = "copyAndDepopulate") {
3553
4522
  throw new Error(
3554
- `copyAndDepopulate: refusing path '${path}' because it contains an unsafe segment (__proto__, prototype, or constructor).`
4523
+ `${fn}: refusing path '${path}' because it contains an unsafe segment (__proto__, prototype, or constructor).`
3555
4524
  );
3556
4525
  }
3557
4526
  function failMissingId(path) {
3558
4527
  throw new Error(`copyAndDepopulate: path '${path}' resolved to a record missing the configured id field.`);
3559
4528
  }
3560
- function assertSafePath(path) {
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") {
3561
4533
  const segs = path.split(".");
3562
4534
  for (const seg of segs) {
3563
4535
  if (seg.length === 0) continue;
3564
4536
  if (UNSAFE_PATH_KEYS.has(seg)) {
3565
- failUnsafeSegment(path);
4537
+ failUnsafeSegment(path, fn);
3566
4538
  }
3567
4539
  }
3568
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
+ }
3569
4571
  var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
3570
4572
  const mutable = options.mutable !== false;
3571
4573
  const idField = options.idField ?? "_id";
3572
- const obj = mutable ? docObject : cloneDeep(docObject);
4574
+ const obj = mutable ? docObject : cloneDeep4(docObject);
3573
4575
  forEach6(Array.isArray(operations) ? operations : [], (op) => {
3574
4576
  if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
3575
4577
  const src = op.src;
@@ -3582,15 +4584,15 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
3582
4584
  forEach6(segs, (seg, ind) => {
3583
4585
  if (segs.length === ind + 1) {
3584
4586
  forEach6(targets, (target) => {
3585
- if (!isPlainObject13(target)) return;
4587
+ if (!isPlainObject14(target)) return;
3586
4588
  const targetObject = target;
3587
4589
  const leaf = targetObject[seg];
3588
4590
  const sourceForDest = leaf;
3589
4591
  let depopulated;
3590
- if (isArray8(leaf)) {
4592
+ if (isArray9(leaf)) {
3591
4593
  if (leaf.length === 0) {
3592
4594
  depopulated = [];
3593
- } else if (leaf.every((item) => isPlainObject13(item))) {
4595
+ } else if (leaf.every((item) => isPlainObject14(item))) {
3594
4596
  depopulated = map(leaf, idField).map((id, i) => {
3595
4597
  if (id === void 0) {
3596
4598
  failMissingId(`${src}[${i}]`);
@@ -3600,7 +4602,7 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
3600
4602
  } else {
3601
4603
  return;
3602
4604
  }
3603
- } else if (isPlainObject13(leaf)) {
4605
+ } else if (isPlainObject14(leaf)) {
3604
4606
  const id = get7(leaf, idField);
3605
4607
  if (id === void 0) {
3606
4608
  failMissingId(src);
@@ -3614,11 +4616,14 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
3614
4616
  });
3615
4617
  } else {
3616
4618
  targets = targets.reduce((ret, target) => {
3617
- if (!isPlainObject13(target)) return ret;
4619
+ if (!isPlainObject14(target)) return ret;
3618
4620
  const targetObject = target;
3619
4621
  const next = targetObject[seg];
3620
- if (isArray8(next)) ret.push(...next);
3621
- else if (next !== null && next !== void 0) ret.push(next);
4622
+ if (isArray9(next)) {
4623
+ for (const item of next) {
4624
+ ret.push(item);
4625
+ }
4626
+ } else if (next !== null && next !== void 0) ret.push(next);
3622
4627
  return ret;
3623
4628
  }, []);
3624
4629
  }
@@ -3626,6 +4631,125 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
3626
4631
  });
3627
4632
  return obj;
3628
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
+ };
3629
4753
 
3630
4754
  // src/symbols.ts
3631
4755
  var MIDDLEWARE = /* @__PURE__ */ Symbol.for("@web-ts-toolkit/access-router/middleware");
@@ -3668,18 +4792,18 @@ var Cache = class {
3668
4792
  import {
3669
4793
  castArray as castArray3,
3670
4794
  arrayToRecord,
3671
- hasOwn as hasOwn2,
3672
- isArray as isArray9,
4795
+ hasOwn as hasOwn3,
4796
+ isArray as isArray10,
3673
4797
  isBoolean as isBoolean4,
3674
4798
  isEmpty as isEmpty2,
3675
4799
  isEqual,
3676
4800
  isFunction as isFunction4,
3677
- isPlainObject as isPlainObject14,
4801
+ isPlainObject as isPlainObject15,
3678
4802
  isString as isString5
3679
4803
  } from "@web-ts-toolkit/utils";
3680
4804
 
3681
4805
  // src/permission.ts
3682
- import { hasOwn } from "@web-ts-toolkit/utils";
4806
+ import { hasOwn as hasOwn2 } from "@web-ts-toolkit/utils";
3683
4807
  var Permission = class {
3684
4808
  constructor(permissions) {
3685
4809
  this.permissions = { ...permissions };
@@ -3695,7 +4819,7 @@ var Permission = class {
3695
4819
  return [permissionOrPermissions].concat(permissions);
3696
4820
  }
3697
4821
  hasKey(permission) {
3698
- return hasOwn(this.permissions, permission);
4822
+ return hasOwn2(this.permissions, permission);
3699
4823
  }
3700
4824
  has(permission) {
3701
4825
  return this.permissions[permission] === true;
@@ -3715,10 +4839,10 @@ var permission_default = Permission;
3715
4839
 
3716
4840
  // src/core-shared.ts
3717
4841
  function isAndFilter(filter2) {
3718
- return !!filter2 && isPlainObject14(filter2) && Object.keys(filter2).length === 1 && isArray9(filter2.$and);
4842
+ return !!filter2 && isPlainObject15(filter2) && Object.keys(filter2).length === 1 && isArray10(filter2.$and);
3719
4843
  }
3720
4844
  function isMergeableClause(filter2) {
3721
- return !!filter2 && isPlainObject14(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
4845
+ return !!filter2 && isPlainObject15(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
3722
4846
  }
3723
4847
  function optimizeAndFilter(clausesInput) {
3724
4848
  const clauses = [];
@@ -3733,10 +4857,10 @@ function optimizeAndFilter(clausesInput) {
3733
4857
  }
3734
4858
  }
3735
4859
  const dedupeKey = (value) => {
3736
- if (isArray9(value)) {
4860
+ if (isArray10(value)) {
3737
4861
  return `[${value.map(dedupeKey).join(",")}]`;
3738
4862
  }
3739
- if (isPlainObject14(value)) {
4863
+ if (isPlainObject15(value)) {
3740
4864
  return `{${Object.keys(value).sort().map((key) => `${key}:${dedupeKey(value[key])}`).join(",")}}`;
3741
4865
  }
3742
4866
  return JSON.stringify(value);
@@ -3760,7 +4884,7 @@ function optimizeAndFilter(clausesInput) {
3760
4884
  }
3761
4885
  let canMerge = true;
3762
4886
  for (const [key, value] of Object.entries(clause)) {
3763
- if (hasOwn2(mergedClause, key) && !isEqual(mergedClause[key], value)) {
4887
+ if (hasOwn3(mergedClause, key) && !isEqual(mergedClause[key], value)) {
3764
4888
  canMerge = false;
3765
4889
  break;
3766
4890
  }
@@ -3778,7 +4902,7 @@ function optimizeAndFilter(clausesInput) {
3778
4902
  }
3779
4903
  function normalizeFilter(filter2) {
3780
4904
  if (filter2 === false) return false;
3781
- if (!filter2 || !isPlainObject14(filter2) || isEmpty2(filter2)) return null;
4905
+ if (!filter2 || !isPlainObject15(filter2) || isEmpty2(filter2)) return null;
3782
4906
  if (!isAndFilter(filter2)) {
3783
4907
  return filter2;
3784
4908
  }
@@ -3804,11 +4928,12 @@ async function resolveAccessFilter({
3804
4928
  }) {
3805
4929
  let nextFilter = normalizeFilter(filter2);
3806
4930
  const overrideFilterFn = getOption(`overrideFilter.${access}`, null);
3807
- if (isFunction4(overrideFilterFn)) {
4931
+ if (nextFilter !== false && isFunction4(overrideFilterFn)) {
3808
4932
  nextFilter = normalizeFilter(await overrideFilterFn.call(req, nextFilter, permissions));
3809
4933
  }
4934
+ if (nextFilter === false) return false;
3810
4935
  const baseFilterFn = getOption(`baseFilter.${access}`, null);
3811
- if (!isFunction4(baseFilterFn)) return nextFilter || {};
4936
+ if (!isFunction4(baseFilterFn)) return nextFilter ?? {};
3812
4937
  const baseFilter = normalizeFilter(
3813
4938
  cache.has(cacheKey) ? cache.get(cacheKey) : await baseFilterFn.call(req, permissions)
3814
4939
  );
@@ -3816,7 +4941,7 @@ async function resolveAccessFilter({
3816
4941
  cache.set(cacheKey, baseFilter);
3817
4942
  }
3818
4943
  if (baseFilter === false) return false;
3819
- if (!baseFilter) return nextFilter || {};
4944
+ if (!baseFilter) return nextFilter ?? {};
3820
4945
  if (!nextFilter) return baseFilter;
3821
4946
  return optimizeAndFilter([baseFilter, nextFilter]);
3822
4947
  }
@@ -3824,15 +4949,30 @@ function getRequestPermissions(req) {
3824
4949
  const requestPermissionField = getGlobalOption("requestPermissionField");
3825
4950
  return new permission_default(req[requestPermissionField] || {});
3826
4951
  }
4952
+ var permissionFieldOwners = /* @__PURE__ */ new WeakMap();
4953
+ function getPermissionFieldOwnerMap(req) {
4954
+ let owners = permissionFieldOwners.get(req);
4955
+ if (!owners) {
4956
+ owners = /* @__PURE__ */ new Map();
4957
+ permissionFieldOwners.set(req, owners);
4958
+ }
4959
+ return owners;
4960
+ }
3827
4961
  async function setRequestPermissions(req) {
3828
4962
  const requestPermissionField = getGlobalOption("requestPermissionField");
3829
- if (req[requestPermissionField]) return;
4963
+ const activeRuntime = getActiveRuntime();
4964
+ const owners = getPermissionFieldOwnerMap(req);
4965
+ const owner = owners.get(requestPermissionField);
4966
+ const hasFieldValue = Boolean(req[requestPermissionField]);
4967
+ if (owner === activeRuntime) return;
4968
+ if (hasFieldValue && owner === void 0) return;
3830
4969
  const globalPermissions = getGlobalOption("globalPermissions");
3831
4970
  if (!isFunction4(globalPermissions)) return;
3832
4971
  const permissions = await globalPermissions.call(req, req);
3833
- if (isPlainObject14(permissions)) req[requestPermissionField] = permissions;
3834
- else if (isArray9(permissions)) req[requestPermissionField] = arrayToRecord(permissions);
4972
+ if (isPlainObject15(permissions)) req[requestPermissionField] = permissions;
4973
+ else if (isArray10(permissions)) req[requestPermissionField] = arrayToRecord(permissions);
3835
4974
  else if (isString5(permissions)) req[requestPermissionField] = { [permissions]: true };
4975
+ owners.set(requestPermissionField, activeRuntime);
3836
4976
  }
3837
4977
  async function evaluateRouteGuard(req, permissions, routeGuard) {
3838
4978
  const phas = (key) => permissions.has(key);
@@ -3843,7 +4983,7 @@ async function evaluateRouteGuard(req, permissions, routeGuard) {
3843
4983
  if (isString5(routeGuard)) {
3844
4984
  return stringHandler(routeGuard);
3845
4985
  }
3846
- if (isArray9(routeGuard)) {
4986
+ if (isArray10(routeGuard)) {
3847
4987
  return arrayHandler(routeGuard);
3848
4988
  }
3849
4989
  if (isFunction4(routeGuard)) {
@@ -3881,7 +5021,7 @@ async function collectSchemaFields({
3881
5021
  if (value) fields.push(key);
3882
5022
  } else if (isString5(value)) {
3883
5023
  if (stringHandler(value)) fields.push(key);
3884
- } else if (isArray9(value)) {
5024
+ } else if (isArray10(value)) {
3885
5025
  if (arrayHandler(value)) fields.push(key);
3886
5026
  } else if (isFunction4(value)) {
3887
5027
  if (await value.call(req, ...functionArgs)) fields.push(key);
@@ -3900,17 +5040,14 @@ function getResolvedRequestPermissions(req) {
3900
5040
  async function setResolvedRequestPermissions(req) {
3901
5041
  await setRequestPermissions(req);
3902
5042
  }
3903
- async function canActivateRequest(req, routeGuard) {
3904
- const permissions = getGlobalPermissions(req);
3905
- return evaluateRouteGuard(req, permissions, routeGuard);
3906
- }
3907
5043
  async function initializeAclRequest({
3908
5044
  req,
3909
5045
  flag,
5046
+ runtime,
3910
5047
  createCore,
3911
5048
  assignCore
3912
5049
  }) {
3913
- if (req[flag]) {
5050
+ if (req[flag] === runtime) {
3914
5051
  return null;
3915
5052
  }
3916
5053
  const core = createCore(req);
@@ -3918,7 +5055,7 @@ async function initializeAclRequest({
3918
5055
  assignCore(core);
3919
5056
  req[PERMISSIONS] = core.getPermissions();
3920
5057
  req[PERMISSION_KEYS] = [...req[PERMISSIONS].keys];
3921
- req[flag] = true;
5058
+ req[flag] = runtime;
3922
5059
  return core;
3923
5060
  }
3924
5061
 
@@ -4054,6 +5191,7 @@ async function resolveSelectForRequest({
4054
5191
  // src/core.ts
4055
5192
  var Core = class {
4056
5193
  constructor(req) {
5194
+ this.cachedPermissions = null;
4057
5195
  this.req = req;
4058
5196
  this.caches = {
4059
5197
  baseFilter: new Cache()
@@ -4188,7 +5326,7 @@ var Core = class {
4188
5326
  if (isFunction5(validate)) {
4189
5327
  const permissions = this.getGlobalPermissions();
4190
5328
  return validate.call(this.req, allowedData, permissions, context);
4191
- } else if (isBoolean5(validate) || isArray10(validate)) {
5329
+ } else if (isBoolean5(validate) || isArray11(validate)) {
4192
5330
  return validate;
4193
5331
  } else {
4194
5332
  return true;
@@ -4332,18 +5470,55 @@ var Core = class {
4332
5470
  options
4333
5471
  );
4334
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;
4335
5508
  }
4336
5509
  });
4337
5510
  return docObject;
4338
5511
  }
4339
5512
  getPermissions() {
5513
+ if (this.cachedPermissions) return this.cachedPermissions;
4340
5514
  return getResolvedRequestPermissions(this.req);
4341
5515
  }
4342
5516
  async setPermissions() {
4343
5517
  await setResolvedRequestPermissions(this.req);
5518
+ this.cachedPermissions = getResolvedRequestPermissions(this.req);
4344
5519
  }
4345
5520
  async canActivate(routeGuard) {
4346
- return canActivateRequest(this.req, routeGuard);
5521
+ return evaluateRouteGuard(this.req, this.getGlobalPermissions(), routeGuard);
4347
5522
  }
4348
5523
  async isAllowed(modelName, access) {
4349
5524
  if (access.startsWith("subs")) {
@@ -4379,6 +5554,7 @@ var Core = class {
4379
5554
  return this.getPublicService(modelName);
4380
5555
  }
4381
5556
  getGlobalPermissions() {
5557
+ if (this.cachedPermissions) return this.cachedPermissions;
4382
5558
  return getGlobalPermissions(this.req);
4383
5559
  }
4384
5560
  };
@@ -4388,6 +5564,7 @@ var createSetCore = (runtime = defaultRuntime) => {
4388
5564
  await initializeAclRequest({
4389
5565
  req,
4390
5566
  flag: MIDDLEWARE,
5567
+ runtime,
4391
5568
  createCore: (request) => new Core(request),
4392
5569
  assignCore: (core) => {
4393
5570
  req.macl = core;
@@ -4405,19 +5582,19 @@ function guard(condition) {
4405
5582
  const permissions = req[PERMISSIONS];
4406
5583
  let cond = condition;
4407
5584
  let phas = (key) => permissions.has(key);
4408
- if (isPlainObject15(condition)) {
5585
+ if (isPlainObject16(condition)) {
4409
5586
  const { modelName, id, condition: _cond } = condition;
4410
5587
  const svc = req.macl.getPublicService(modelName);
4411
5588
  const select = getModelOption(modelName, `alwaysSelectFields.read`, void 0);
4412
5589
  let _id = isString7(id) ? id : null;
4413
- if (isPlainObject15(id)) {
5590
+ if (isPlainObject16(id)) {
4414
5591
  const { type = "param", key } = id;
4415
5592
  if (type === "param") {
4416
5593
  const paramValue = req.params[key];
4417
- _id = isArray11(paramValue) ? paramValue[0] ?? null : paramValue ?? null;
5594
+ _id = isArray12(paramValue) ? paramValue[0] ?? null : paramValue ?? null;
4418
5595
  } else if (type === "query") {
4419
5596
  const _qval = req.query[key];
4420
- if (isArray11(_qval)) _id = _qval.length > 0 ? _qval[0] : null;
5597
+ if (isArray12(_qval)) _id = _qval.length > 0 ? _qval[0] : null;
4421
5598
  else _id = _qval;
4422
5599
  } else {
4423
5600
  _id = null;
@@ -4437,7 +5614,7 @@ function guard(condition) {
4437
5614
  const { stringHandler, arrayHandler } = createValidator(phas);
4438
5615
  if (isString7(cond)) {
4439
5616
  if (stringHandler(cond)) return next();
4440
- } else if (isArray11(cond)) {
5617
+ } else if (isArray12(cond)) {
4441
5618
  if (arrayHandler(cond)) return next();
4442
5619
  } else if (isFunction6(cond)) {
4443
5620
  const result = await cond.call(req, permissions);
@@ -4452,7 +5629,7 @@ import express2 from "express";
4452
5629
 
4453
5630
  // src/routers/model-router.ts
4454
5631
  import JsonRouter5 from "@web-ts-toolkit/express-json-router";
4455
- import { forEach as forEach8, isPlainObject as isPlainObject16, isString as isString8, isUndefined as isUndefined2, normalizeUrlPath as normalizeUrlPath2, padEnd } from "@web-ts-toolkit/utils";
5632
+ import { forEach as forEach8, isPlainObject as isPlainObject18, isString as isString8, isUndefined as isUndefined2, normalizeUrlPath as normalizeUrlPath2, padEnd } from "@web-ts-toolkit/utils";
4456
5633
 
4457
5634
  // src/routers/router-mutation.ts
4458
5635
  var MODEL_BUILD_TIME_OPTION_KEYS = /* @__PURE__ */ new Set([
@@ -4581,6 +5758,7 @@ import JsonRouter4 from "@web-ts-toolkit/express-json-router";
4581
5758
 
4582
5759
  // src/validation/common.ts
4583
5760
  import { z as z2 } from "zod";
5761
+ import { isPlainObject as isPlainObject17 } from "@web-ts-toolkit/utils";
4584
5762
  var stringOrStringArray = z2.union([z2.string(), z2.array(z2.string())]);
4585
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);
4586
5764
  var queryBooleanString = z2.enum(["true", "false"]);
@@ -4641,7 +5819,8 @@ var subPopulateSchema = z2.union([
4641
5819
  select: projectionSchema.optional()
4642
5820
  }).passthrough()
4643
5821
  ]);
4644
- var includeItemSchema = z2.object({
5822
+ var legacyIncludeItemSchema = z2.object({
5823
+ mode: z2.enum(["legacy"]).optional(),
4645
5824
  model: z2.string().min(1),
4646
5825
  op: z2.enum(["list", "read", "count"]),
4647
5826
  path: z2.string().min(1),
@@ -4650,8 +5829,139 @@ var includeItemSchema = z2.object({
4650
5829
  foreignField: z2.string().min(1),
4651
5830
  args: z2.unknown().optional().describe("Forwarded to list/read includes. Count includes ignore pagination fields so counts remain exact."),
4652
5831
  options: z2.unknown().optional()
4653
- }).passthrough();
4654
- var includeSchema = z2.union([includeItemSchema, z2.array(includeItemSchema)]);
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
+ ]);
4655
5965
  var fieldsSchema = z2.array(z2.string().min(1));
4656
5966
  var taskSchema = z2.object({
4657
5967
  type: z2.string().min(1),
@@ -4768,8 +6078,9 @@ function defineRequestSchema(validator, options = {}) {
4768
6078
  };
4769
6079
  }
4770
6080
  function fromZod(schema) {
4771
- return (value) => {
4772
- const result = schema.safeParse(value);
6081
+ return async (value) => {
6082
+ const candidate = schema;
6083
+ const result = typeof candidate.safeParseAsync === "function" ? await candidate.safeParseAsync.call(schema, value) : schema.safeParse(value);
4773
6084
  if (result.success) {
4774
6085
  return {
4775
6086
  success: true,
@@ -4806,6 +6117,9 @@ function fromYup(schema) {
4806
6117
  data
4807
6118
  };
4808
6119
  } catch (error2) {
6120
+ if (!isYupValidationError(error2)) {
6121
+ throw error2;
6122
+ }
4809
6123
  return {
4810
6124
  success: false,
4811
6125
  issues: normalizeYupIssues(error2)
@@ -4830,8 +6144,32 @@ function fromJoi(schema) {
4830
6144
  }
4831
6145
  function fromAjv(validator) {
4832
6146
  return async (value) => {
4833
- const valid = await validator(value);
4834
- if (valid) {
6147
+ const outcome = validator(value);
6148
+ if (isThenable(outcome)) {
6149
+ try {
6150
+ const data = await outcome;
6151
+ if (data === false) {
6152
+ return {
6153
+ success: false,
6154
+ issues: normalizeAjvIssues(validator.errors ?? [])
6155
+ };
6156
+ }
6157
+ return {
6158
+ success: true,
6159
+ data: data === true ? value : data
6160
+ };
6161
+ } catch (error2) {
6162
+ if (!isAjvValidationError(error2)) {
6163
+ throw error2;
6164
+ }
6165
+ return {
6166
+ success: false,
6167
+ issues: normalizeAjvIssues(error2.errors ?? [])
6168
+ };
6169
+ }
6170
+ }
6171
+ const errorsSnapshot = validator.errors ? [...validator.errors] : [];
6172
+ if (outcome) {
4835
6173
  return {
4836
6174
  success: true,
4837
6175
  data: value
@@ -4839,7 +6177,7 @@ function fromAjv(validator) {
4839
6177
  }
4840
6178
  return {
4841
6179
  success: false,
4842
- issues: normalizeAjvIssues(validator.errors ?? [])
6180
+ issues: normalizeAjvIssues(errorsSnapshot)
4843
6181
  };
4844
6182
  };
4845
6183
  }
@@ -4860,6 +6198,25 @@ function fromValibot(schema, safeParse) {
4860
6198
  }
4861
6199
  function fromArkType(type) {
4862
6200
  return async (value) => {
6201
+ if (isStandardSchema(type)) {
6202
+ const result2 = await type["~standard"].validate(value);
6203
+ if (isArkTypeErrors(result2)) {
6204
+ return {
6205
+ success: false,
6206
+ issues: normalizeArkTypeIssues(result2)
6207
+ };
6208
+ }
6209
+ if (isStandardSchemaFailure(result2)) {
6210
+ return {
6211
+ success: false,
6212
+ issues: normalizeArkTypeStandardIssues(result2.issues)
6213
+ };
6214
+ }
6215
+ return {
6216
+ success: true,
6217
+ data: result2.value
6218
+ };
6219
+ }
4863
6220
  const result = await type(value);
4864
6221
  if (!isArkTypeErrors(result)) {
4865
6222
  return {
@@ -4912,6 +6269,9 @@ function fromVine(validator) {
4912
6269
  data: output
4913
6270
  };
4914
6271
  } catch (error2) {
6272
+ if (!isVineValidationError(error2)) {
6273
+ throw error2;
6274
+ }
4915
6275
  return {
4916
6276
  success: false,
4917
6277
  issues: normalizeVineError(error2)
@@ -4972,6 +6332,13 @@ function normalizeArkTypeIssues(issues) {
4972
6332
  }));
4973
6333
  return normalized.length ? normalized : [{ message: issues.summary ?? "Validation failed" }];
4974
6334
  }
6335
+ function normalizeArkTypeStandardIssues(issues) {
6336
+ const normalized = issues.map((issue) => ({
6337
+ message: issue.message ?? issue.problem ?? "Validation failed",
6338
+ path: issue.path?.flatMap((segment) => normalizePathSegment(segment))
6339
+ }));
6340
+ return normalized.length ? normalized : [{ message: "Validation failed" }];
6341
+ }
4975
6342
  function normalizeIoTsIssues(issues) {
4976
6343
  return issues.map((issue) => ({
4977
6344
  message: issue.message ?? "Validation failed",
@@ -5006,25 +6373,64 @@ function normalizeVineField(message) {
5006
6373
  }
5007
6374
  return path.length ? path : void 0;
5008
6375
  }
6376
+ function decodeAjvSegment(segment) {
6377
+ return segment.replace(/~1/g, "/").replace(/~0/g, "~");
6378
+ }
6379
+ function encodePointerSegment(segment) {
6380
+ return encodeURIComponent(segment.replace(/~/g, "~0").replace(/\//g, "~1"));
6381
+ }
5009
6382
  function parseAjvPath(issue) {
5010
- const path = issue.instancePath?.split("/").filter(Boolean).map((segment) => /^\d+$/.test(segment) ? Number(segment) : segment);
5011
- if (issue.params?.missingProperty) {
5012
- return (path ?? []).concat(issue.params.missingProperty);
6383
+ const raw = issue.instancePath;
6384
+ const base = raw == null || raw === "" ? [] : (raw.startsWith("/") ? raw.slice(1) : raw).split("/").map(decodeAjvSegment);
6385
+ const missing = issue.params?.missingProperty;
6386
+ if (typeof missing === "string") {
6387
+ return [...base, missing];
5013
6388
  }
5014
- return path;
6389
+ return base;
5015
6390
  }
5016
6391
  function parsePathString(path) {
5017
6392
  if (!path) return void 0;
5018
6393
  return path.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean).map((segment) => /^\d+$/.test(segment) ? Number(segment) : segment);
5019
6394
  }
5020
6395
  function isYupValidationError(error2) {
5021
- return typeof error2 === "object" && error2 !== null && "message" in error2;
6396
+ if (typeof error2 !== "object" || error2 === null || !("message" in error2)) {
6397
+ return false;
6398
+ }
6399
+ const candidate = error2;
6400
+ return candidate.name === "ValidationError" && typeof candidate.message === "string" && Array.isArray(candidate.errors) && Array.isArray(candidate.inner);
5022
6401
  }
5023
6402
  function isArkTypeErrors(value) {
5024
- return Array.isArray(value);
6403
+ if (typeof value !== "object" || value === null) {
6404
+ return false;
6405
+ }
6406
+ const record = value;
6407
+ return record.arkKind === "errors" || record[" arkKind"] === "errors";
6408
+ }
6409
+ function isThenable(value) {
6410
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
6411
+ }
6412
+ function isAjvValidationError(error2) {
6413
+ if (typeof error2 !== "object" || error2 === null || !("errors" in error2)) {
6414
+ return false;
6415
+ }
6416
+ const errors = error2.errors;
6417
+ return errors == null || Array.isArray(errors);
5025
6418
  }
5026
6419
  function isVineValidationError(error2) {
5027
- return typeof error2 === "object" && error2 !== null && "messages" in error2 && Array.isArray(error2.messages);
6420
+ if (typeof error2 !== "object" || error2 === null || !("messages" in error2)) {
6421
+ return false;
6422
+ }
6423
+ const candidate = error2;
6424
+ if (!Array.isArray(candidate.messages) || candidate.messages.length === 0) {
6425
+ return false;
6426
+ }
6427
+ const hasDiscriminator = candidate.code === "E_VALIDATION_ERROR" || candidate.name === "ValidationError";
6428
+ if (!hasDiscriminator) {
6429
+ return false;
6430
+ }
6431
+ return candidate.messages.every(
6432
+ (message) => typeof message === "object" && message !== null && typeof message.message === "string" && typeof message.field === "string"
6433
+ );
5028
6434
  }
5029
6435
  function formatIssue(issue, key, location = "pointer") {
5030
6436
  const path = (issue.path ?? []).map(String);
@@ -5041,7 +6447,7 @@ function formatIssue(issue, key, location = "pointer") {
5041
6447
  };
5042
6448
  }
5043
6449
  function buildPointer(path) {
5044
- return path.length === 0 ? "#" : `#/${path.join("/")}`;
6450
+ return path.length === 0 ? "#" : `#/${path.map(encodePointerSegment).join("/")}`;
5045
6451
  }
5046
6452
 
5047
6453
  // src/validation/model-router.ts
@@ -5092,7 +6498,7 @@ var listBodySchema = z3.object({
5092
6498
  }).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query"]));
5093
6499
  var countBodySchema = z3.object({
5094
6500
  filter: objectOrArraySchema.optional()
5095
- }).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query", "access", "options"]));
6501
+ }).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query", "access", "options", "include"]));
5096
6502
  var readFilterBodySchema = z3.object({
5097
6503
  filter: objectOrArraySchema.optional(),
5098
6504
  select: projectionSchema.optional(),
@@ -5542,24 +6948,26 @@ function setModelCollectionRoutes(context) {
5542
6948
  router.post(`/${options.mutationRouteSegment}`, async (req) => {
5543
6949
  await context.assertAllowed(req, "create");
5544
6950
  const { include_permissions } = parseQuery(requestSchemas.createQuery, req.query);
5545
- const body = await parseNestedBodyWithSchema(
6951
+ const intermediateBody = await parseNestedBodyWithSchema(
5546
6952
  advancedCreateBodySchema,
5547
6953
  req.body,
5548
6954
  "data",
5549
6955
  context.getRequestSchema("requestSchemas.advancedCreate.data")
5550
6956
  );
5551
- const { data, select, populate, tasks } = body;
5552
- const advancedOptions = body.options ?? {};
5553
- await parseBodyWithSchema(
6957
+ const { data: nestedData, select: nestedSelect, populate: nestedPopulate, tasks: nestedTasks } = intermediateBody;
6958
+ const nestedOptions = intermediateBody.options ?? {};
6959
+ const finalBody = await parseBodyWithSchema(
5554
6960
  advancedCreateBodySchema,
5555
- { data, select, populate, tasks, options: advancedOptions },
6961
+ { data: nestedData, select: nestedSelect, populate: nestedPopulate, tasks: nestedTasks, options: nestedOptions },
5556
6962
  context.getRequestSchema("requestSchemas.advancedCreate.default") ?? context.getRequestSchema("requestSchemas.advancedCreate")
5557
6963
  );
5558
- const { includePermissions, populateAccess } = advancedOptions;
6964
+ const { data: finalData, select: finalSelect, populate: finalPopulate, tasks: finalTasks } = finalBody;
6965
+ const finalOptions = finalBody.options ?? {};
6966
+ const { includePermissions, populateAccess } = finalOptions;
5559
6967
  const svc = context.getPublicService(req);
5560
6968
  const result = await svc._create(
5561
- data,
5562
- { select, populate, tasks },
6969
+ finalData,
6970
+ { select: finalSelect, populate: finalPopulate, tasks: finalTasks },
5563
6971
  {
5564
6972
  includePermissions: includePermissions ?? parseBooleanString(include_permissions),
5565
6973
  populateAccess
@@ -5758,25 +7166,33 @@ function setModelDocumentRoutes(context) {
5758
7166
  await context.assertAllowed(req, "update");
5759
7167
  const id = parsePathParam(req.params[options.idParam], options.idParam);
5760
7168
  const { returning_all, include_permissions } = parseQuery(requestSchemas.updateQuery, req.query);
5761
- const body = await parseNestedBodyWithSchema(
7169
+ const intermediateBody = await parseNestedBodyWithSchema(
5762
7170
  advancedUpdateBodySchema,
5763
7171
  req.body,
5764
7172
  "data",
5765
7173
  context.getRequestSchema("requestSchemas.advancedUpdate.data")
5766
7174
  );
5767
- const { data, select, populate, tasks } = body;
5768
- const advancedOptions = body.options ?? {};
5769
- await parseBodyWithSchema(
7175
+ const { data: nestedData, select: nestedSelect, populate: nestedPopulate, tasks: nestedTasks } = intermediateBody;
7176
+ const nestedOptions = intermediateBody.options ?? {};
7177
+ const finalBody = await parseBodyWithSchema(
5770
7178
  advancedUpdateBodySchema,
5771
- { data, select, populate, tasks, options: advancedOptions },
7179
+ {
7180
+ data: nestedData,
7181
+ select: nestedSelect,
7182
+ populate: nestedPopulate,
7183
+ tasks: nestedTasks,
7184
+ options: nestedOptions
7185
+ },
5772
7186
  context.getRequestSchema("requestSchemas.advancedUpdate.default") ?? context.getRequestSchema("requestSchemas.advancedUpdate")
5773
7187
  );
5774
- const { returningAll, includePermissions, populateAccess } = advancedOptions;
7188
+ const { data: finalData, select: finalSelect, populate: finalPopulate, tasks: finalTasks } = finalBody;
7189
+ const finalOptions = finalBody.options ?? {};
7190
+ const { returningAll, includePermissions, populateAccess } = finalOptions;
5775
7191
  const svc = context.getPublicService(req);
5776
7192
  const result = await svc._update(
5777
7193
  id,
5778
- data,
5779
- { select, populate, tasks },
7194
+ finalData,
7195
+ { select: finalSelect, populate: finalPopulate, tasks: finalTasks },
5780
7196
  {
5781
7197
  returningAll: returningAll ?? parseBooleanString(returning_all),
5782
7198
  includePermissions: includePermissions ?? parseBooleanString(include_permissions),
@@ -5826,23 +7242,31 @@ function setModelDocumentRoutes(context) {
5826
7242
  await context.assertAllowed(req, "upsert");
5827
7243
  const svc = context.getPublicService(req);
5828
7244
  const { returning_all, include_permissions } = parseQuery(requestSchemas.upsertQuery, req.query);
5829
- const body = await parseNestedBodyWithSchema(
7245
+ const intermediateBody = await parseNestedBodyWithSchema(
5830
7246
  advancedUpsertBodySchema,
5831
7247
  req.body,
5832
7248
  "data",
5833
7249
  context.getRequestSchema("requestSchemas.advancedUpsert.data")
5834
7250
  );
5835
- const { data, select, populate, tasks } = body;
5836
- const advancedOptions = body.options ?? {};
5837
- await parseBodyWithSchema(
7251
+ const { data: nestedData, select: nestedSelect, populate: nestedPopulate, tasks: nestedTasks } = intermediateBody;
7252
+ const nestedOptions = intermediateBody.options ?? {};
7253
+ const finalBody = await parseBodyWithSchema(
5838
7254
  advancedUpsertBodySchema,
5839
- { data, select, populate, tasks, options: advancedOptions },
7255
+ {
7256
+ data: nestedData,
7257
+ select: nestedSelect,
7258
+ populate: nestedPopulate,
7259
+ tasks: nestedTasks,
7260
+ options: nestedOptions
7261
+ },
5840
7262
  context.getRequestSchema("requestSchemas.advancedUpsert.default") ?? context.getRequestSchema("requestSchemas.advancedUpsert")
5841
7263
  );
5842
- const { returningAll, includePermissions, populateAccess } = advancedOptions;
7264
+ const { data: finalData, select: finalSelect, populate: finalPopulate, tasks: finalTasks } = finalBody;
7265
+ const finalOptions = finalBody.options ?? {};
7266
+ const { returningAll, includePermissions, populateAccess } = finalOptions;
5843
7267
  const result = await svc._upsert(
5844
- data,
5845
- { select, populate, tasks },
7268
+ finalData,
7269
+ { select: finalSelect, populate: finalPopulate, tasks: finalTasks },
5846
7270
  {
5847
7271
  returningAll: returningAll ?? parseBooleanString(returning_all),
5848
7272
  includePermissions: includePermissions ?? parseBooleanString(include_permissions),
@@ -5912,7 +7336,7 @@ function setModelDocumentRoutes(context) {
5912
7336
 
5913
7337
  // src/routers/model-router-subdocument-routes.ts
5914
7338
  function setModelSubDocumentRoutes(context) {
5915
- const subs = getModelSub(context.modelName);
7339
+ const subs = context.runtime.getModelSub(context.modelName);
5916
7340
  for (let x = 0; x < subs.length; x++) {
5917
7341
  const sub = subs[x];
5918
7342
  context.router.get(`/:${context.options.idParam}/${sub}`, async (req) => {
@@ -6137,7 +7561,9 @@ var ModelRouter = class _ModelRouter {
6137
7561
  "baseFilter"
6138
7562
  );
6139
7563
  /**
6140
- * The override filter definitions applied in every query transaction.
7564
+ * Trusted filter replacements applied before base-filter composition.
7565
+ * They may replace ordinary filters, but cannot replace an existing `false` denial.
7566
+ * Returning `false` denies the transaction.
6141
7567
  * @operation `list`, `read`, `update`, `delete`
6142
7568
  */
6143
7569
  this.overrideFilter = setOption.bind(this, "overrideFilter");
@@ -6297,6 +7723,7 @@ var ModelRouter = class _ModelRouter {
6297
7723
  modelName: this.modelName,
6298
7724
  router: this.router,
6299
7725
  options: this.options,
7726
+ runtime: this.runtime,
6300
7727
  getRequestSchema: this.getRequestSchema.bind(this),
6301
7728
  getPublicService: this.getPublicService.bind(this),
6302
7729
  assertAllowed: this.assertAllowed.bind(this),
@@ -6311,6 +7738,7 @@ var ModelRouter = class _ModelRouter {
6311
7738
  modelName: this.modelName,
6312
7739
  router: this.router,
6313
7740
  options: this.options,
7741
+ runtime: this.runtime,
6314
7742
  getRequestSchema: this.getRequestSchema.bind(this),
6315
7743
  getPublicService: this.getPublicService.bind(this),
6316
7744
  assertAllowed: this.assertAllowed.bind(this),
@@ -6325,6 +7753,7 @@ var ModelRouter = class _ModelRouter {
6325
7753
  modelName: this.modelName,
6326
7754
  router: this.router,
6327
7755
  options: this.options,
7756
+ runtime: this.runtime,
6328
7757
  getRequestSchema: this.getRequestSchema.bind(this),
6329
7758
  getPublicService: this.getPublicService.bind(this),
6330
7759
  assertAllowed: this.assertAllowed.bind(this),
@@ -6347,7 +7776,7 @@ var ModelRouter = class _ModelRouter {
6347
7776
  value
6348
7777
  );
6349
7778
  }
6350
- if (arguments.length === 1 && isPlainObject16(keyOrOptions)) {
7779
+ if (arguments.length === 1 && isPlainObject18(keyOrOptions)) {
6351
7780
  assertMutableRouterOptions("model", keyOrOptions);
6352
7781
  this.runtime.setModelOptions(this.modelName, keyOrOptions);
6353
7782
  }
@@ -6375,6 +7804,7 @@ import { isNumber as _isNumber, normalizeUrlPath as normalizeUrlPath3, orderBy a
6375
7804
  // src/core-data.ts
6376
7805
  var DataCore = class {
6377
7806
  constructor(req) {
7807
+ this.cachedPermissions = null;
6378
7808
  this.req = req;
6379
7809
  this.caches = {
6380
7810
  baseFilter: new Cache()
@@ -6468,13 +7898,15 @@ var DataCore = class {
6468
7898
  });
6469
7899
  }
6470
7900
  getPermissions() {
7901
+ if (this.cachedPermissions) return this.cachedPermissions;
6471
7902
  return getResolvedRequestPermissions(this.req);
6472
7903
  }
6473
7904
  async setPermissions() {
6474
7905
  await setResolvedRequestPermissions(this.req);
7906
+ this.cachedPermissions = getResolvedRequestPermissions(this.req);
6475
7907
  }
6476
7908
  async canActivate(routeGuard) {
6477
- return canActivateRequest(this.req, routeGuard);
7909
+ return evaluateRouteGuard(this.req, this.getGlobalPermissions(), routeGuard);
6478
7910
  }
6479
7911
  async isAllowed(dataName, access) {
6480
7912
  const operationAccess = getDataOption(dataName, `operationAccess.${access}`);
@@ -6490,6 +7922,7 @@ var DataCore = class {
6490
7922
  return this.getService(dataName);
6491
7923
  }
6492
7924
  getGlobalPermissions() {
7925
+ if (this.cachedPermissions) return this.cachedPermissions;
6493
7926
  return getGlobalPermissions(this.req);
6494
7927
  }
6495
7928
  };
@@ -6499,6 +7932,7 @@ var createSetDataCore = (runtime = defaultRuntime) => {
6499
7932
  await initializeAclRequest({
6500
7933
  req,
6501
7934
  flag: DATA_MIDDLEWARE,
7935
+ runtime,
6502
7936
  createCore: (request) => new DataCore(request),
6503
7937
  assignCore: (core) => {
6504
7938
  req.dacl = core;
@@ -6781,7 +8215,7 @@ var RootRouter = class {
6781
8215
 
6782
8216
  // src/routers/data-router.ts
6783
8217
  import JsonRouter7 from "@web-ts-toolkit/express-json-router";
6784
- import { isPlainObject as isPlainObject17, isString as isString9, isUndefined as isUndefined3, normalizeUrlPath as normalizeUrlPath4 } from "@web-ts-toolkit/utils";
8218
+ import { isPlainObject as isPlainObject19, isString as isString9, isUndefined as isUndefined3, normalizeUrlPath as normalizeUrlPath4 } from "@web-ts-toolkit/utils";
6785
8219
  var clientErrors4 = JsonRouter7.clientErrors;
6786
8220
  function setOption2(parentKey, optionKey, option) {
6787
8221
  const key = isUndefined3(option) ? parentKey : `${parentKey}.${optionKey}`;
@@ -6803,6 +8237,11 @@ var DataRouter = class {
6803
8237
  this,
6804
8238
  "baseFilter"
6805
8239
  );
8240
+ /**
8241
+ * Trusted filter replacements applied before base-filter composition.
8242
+ * They may replace ordinary filters, but cannot replace an existing `false` denial.
8243
+ * Returning `false` denies the transaction.
8244
+ */
6806
8245
  this.overrideFilter = setOption2.bind(this, "overrideFilter");
6807
8246
  this.decorate = setOption2.bind(
6808
8247
  this,
@@ -6977,7 +8416,7 @@ var DataRouter = class {
6977
8416
  assertMutableRouterOption("data", keyOrOptions);
6978
8417
  this.runtime.setDataOption(this.dataName, keyOrOptions, value);
6979
8418
  }
6980
- if (arguments.length === 1 && isPlainObject17(keyOrOptions)) {
8419
+ if (arguments.length === 1 && isPlainObject19(keyOrOptions)) {
6981
8420
  assertMutableRouterOptions("data", keyOrOptions);
6982
8421
  this.runtime.setDataOptions(this.dataName, keyOrOptions);
6983
8422
  }
@@ -7124,7 +8563,7 @@ function createRuntimeApi(runtime) {
7124
8563
  if (arguments.length === 2 && isString10(keyOrOptions)) {
7125
8564
  return runtime.setGlobalOption(keyOrOptions, value);
7126
8565
  }
7127
- if (arguments.length === 1 && isPlainObject18(keyOrOptions)) {
8566
+ if (arguments.length === 1 && isPlainObject20(keyOrOptions)) {
7128
8567
  return runtime.setGlobalOptions(keyOrOptions);
7129
8568
  }
7130
8569
  };