@web-ts-toolkit/access-router 0.44.0 → 0.46.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.js CHANGED
@@ -77,14 +77,14 @@ __export(index_exports, {
77
77
  setModelOptions: () => setModelOptions
78
78
  });
79
79
  module.exports = __toCommonJS(index_exports);
80
- var import_utils28 = require("@web-ts-toolkit/utils");
80
+ var import_utils32 = require("@web-ts-toolkit/utils");
81
81
 
82
82
  // src/middleware.ts
83
83
  var import_express_json_router2 = __toESM(require("@web-ts-toolkit/express-json-router"));
84
- var import_utils22 = require("@web-ts-toolkit/utils");
84
+ var import_utils25 = require("@web-ts-toolkit/utils");
85
85
 
86
86
  // src/core.ts
87
- var import_utils21 = require("@web-ts-toolkit/utils");
87
+ var import_utils24 = require("@web-ts-toolkit/utils");
88
88
 
89
89
  // src/runtime.ts
90
90
  var import_mongoose3 = __toESM(require("mongoose"));
@@ -122,8 +122,12 @@ var mapWithConcurrencyLimit = async (items, limit, iteratee) => {
122
122
  };
123
123
  var RequestConcurrencyScheduler = class {
124
124
  constructor(limit) {
125
+ this.activeWork = 0;
126
+ this.waiters = [];
125
127
  this.limit = Math.max(1, limit);
126
128
  }
129
+ // Each map bounds its own orchestration workers. Recursive maps never hold
130
+ // shared work permits while awaiting descendants (including at limit 1).
127
131
  async map(items, iteratee) {
128
132
  return mapWithConcurrencyLimit(items, this.limit, (item, index) => iteratee(item, index, true));
129
133
  }
@@ -131,6 +135,23 @@ var RequestConcurrencyScheduler = class {
131
135
  const [result] = await this.map([void 0], () => iteratee(true));
132
136
  return result;
133
137
  }
138
+ // Only leaf persistence operations belong here, not service/hook callbacks.
139
+ // All work calls on this scheduler share a FIFO admission ceiling.
140
+ async work(operation) {
141
+ if (this.activeWork >= this.limit) {
142
+ await new Promise((resolve) => this.waiters.push(resolve));
143
+ } else {
144
+ this.activeWork += 1;
145
+ }
146
+ try {
147
+ return await operation();
148
+ } finally {
149
+ const next = this.waiters.shift();
150
+ if (next)
151
+ next();
152
+ else this.activeWork -= 1;
153
+ }
154
+ }
134
155
  };
135
156
 
136
157
  // src/helpers/document.ts
@@ -1000,7 +1021,9 @@ var defaultRequestComplexity = {
1000
1021
  maxIncludeCount: 10,
1001
1022
  maxSubQueryCount: 10,
1002
1023
  maxBulkConcurrency: 10,
1003
- maxHookConcurrency: 10
1024
+ maxHookConcurrency: 10,
1025
+ maxCorrelatedQueries: 100,
1026
+ maxCorrelatedDepth: 5
1004
1027
  };
1005
1028
  var normalizePositiveInteger = (value, fallback) => {
1006
1029
  return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : fallback;
@@ -1020,6 +1043,14 @@ var resolveRequestComplexity = (options) => ({
1020
1043
  maxHookConcurrency: normalizePositiveInteger(
1021
1044
  options?.maxHookConcurrency,
1022
1045
  defaultRequestComplexity.maxHookConcurrency
1046
+ ),
1047
+ maxCorrelatedQueries: normalizePositiveInteger(
1048
+ options?.maxCorrelatedQueries,
1049
+ defaultRequestComplexity.maxCorrelatedQueries
1050
+ ),
1051
+ maxCorrelatedDepth: normalizePositiveInteger(
1052
+ options?.maxCorrelatedDepth,
1053
+ defaultRequestComplexity.maxCorrelatedDepth
1023
1054
  )
1024
1055
  });
1025
1056
  function validateRequestComplexity(value, options, scope = "request") {
@@ -1212,6 +1243,7 @@ var AccessRuntime = class {
1212
1243
  this.modelAtts = {};
1213
1244
  this.modelInstances = {};
1214
1245
  this.openApiRegistry = new OpenApiRegistry();
1246
+ this.openApiPathPrefix = "";
1215
1247
  this.allowGlobalModelLookup = options.allowGlobalModelLookup ?? false;
1216
1248
  ensureMongooseJsonSchemaInitialized();
1217
1249
  }
@@ -1240,7 +1272,24 @@ var AccessRuntime = class {
1240
1272
  return global;
1241
1273
  }
1242
1274
  registerOpenApiRoute(route) {
1243
- this.openApiRegistry.register(route);
1275
+ this.openApiRegistry.register(
1276
+ this.openApiPathPrefix ? { ...route, path: (0, import_utils11.normalizeUrlPath)(`${this.openApiPathPrefix}/${route.path}`) } : route
1277
+ );
1278
+ }
1279
+ /**
1280
+ * Compose OpenAPI paths while synchronously constructing mounted routers.
1281
+ * Prefixes apply before collision checks; prior descriptors and Express paths
1282
+ * are unchanged. Nested calls compose and the prior scope is restored even on
1283
+ * failure. The callback must finish registration synchronously (no async work).
1284
+ */
1285
+ withOpenApiPathPrefix(prefix, build) {
1286
+ const previous = this.openApiPathPrefix;
1287
+ this.openApiPathPrefix = (0, import_utils11.normalizeUrlPath)(`${previous}/${prefix}`);
1288
+ try {
1289
+ return build();
1290
+ } finally {
1291
+ this.openApiPathPrefix = previous;
1292
+ }
1244
1293
  }
1245
1294
  /**
1246
1295
  * Enable strict OpenAPI collision detection for the remainder of this
@@ -1605,7 +1654,484 @@ var getModelRef = (modelName, refPath) => {
1605
1654
  };
1606
1655
 
1607
1656
  // src/services/base.ts
1657
+ var import_utils13 = require("@web-ts-toolkit/utils");
1658
+
1659
+ // src/correlated-includes.ts
1608
1660
  var import_utils12 = require("@web-ts-toolkit/utils");
1661
+ var PARENT_MARKER_KEY = "$parent";
1662
+ var ESCAPE_WRAPPER_KEY = "$escape";
1663
+ var SUBQUERY_PAYLOAD_KEYS = /* @__PURE__ */ new Set(["$$sq", "$$date"]);
1664
+ var MARKER_FORBIDDEN_OPERATOR_KEYS = /* @__PURE__ */ new Set(["$text", "$where", "$comment"]);
1665
+ var LOGICAL_CLAUSE_KEYS = /* @__PURE__ */ new Set(["$and", "$or", "$nor"]);
1666
+ var DANGEROUS_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
1667
+ var FIELD_SEGMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_$]*$/;
1668
+ var ARRAY_INDEX_SEGMENT_PATTERN = /^(0|[1-9]\d*)$/;
1669
+ var ownKeys = (value) => Object.keys(value);
1670
+ function isParentRefMarker(value) {
1671
+ return (0, import_utils12.isPlainObject)(value) && ownKeys(value).length === 1 && ownKeys(value)[0] === PARENT_MARKER_KEY;
1672
+ }
1673
+ function isEscapedParentRef(value) {
1674
+ return (0, import_utils12.isPlainObject)(value) && ownKeys(value).length === 1 && ownKeys(value)[0] === ESCAPE_WRAPPER_KEY;
1675
+ }
1676
+ function hasMarkerKey(value) {
1677
+ return ownKeys(value).includes(PARENT_MARKER_KEY) || ownKeys(value).includes(ESCAPE_WRAPPER_KEY);
1678
+ }
1679
+ function isValidParentRefPath(path) {
1680
+ if (typeof path !== "string" || path.length === 0) return false;
1681
+ const segments = path.split(".");
1682
+ return segments.every(
1683
+ (segment) => (FIELD_SEGMENT_PATTERN.test(segment) || ARRAY_INDEX_SEGMENT_PATTERN.test(segment)) && !DANGEROUS_PATH_SEGMENTS.has(segment)
1684
+ );
1685
+ }
1686
+ function isValidCorrelatedOutputPath(path) {
1687
+ return typeof path === "string" && path.length > 0 && isValidFieldPath(path) && path !== "_id" && !path.startsWith("$");
1688
+ }
1689
+ function containsParentMarkerShape(value) {
1690
+ const seen = /* @__PURE__ */ new Set();
1691
+ const visit = (current) => {
1692
+ if (isParentRefMarker(current) || isEscapedParentRef(current)) return true;
1693
+ if ((0, import_utils12.isArray)(current)) {
1694
+ if (seen.has(current)) return false;
1695
+ seen.add(current);
1696
+ return current.some(visit);
1697
+ }
1698
+ if ((0, import_utils12.isPlainObject)(current)) {
1699
+ if (seen.has(current)) return false;
1700
+ seen.add(current);
1701
+ if (hasMarkerKey(current)) return true;
1702
+ return Object.values(current).some(visit);
1703
+ }
1704
+ return false;
1705
+ };
1706
+ return visit(value);
1707
+ }
1708
+ function scanTemplateValueNode(value, path, issues) {
1709
+ if (isEscapedParentRef(value)) {
1710
+ const inner = value.$escape;
1711
+ if (!isParentRefMarker(inner) || typeof inner.$parent !== "string" || (inner.$parent?.length ?? 0) === 0) {
1712
+ issues.push({
1713
+ message: 'Invalid $escape payload: expected { "$escape": { "$parent": "<field>" } }',
1714
+ path
1715
+ });
1716
+ }
1717
+ return;
1718
+ }
1719
+ if (isParentRefMarker(value)) {
1720
+ if (!isValidParentRefPath(value.$parent)) {
1721
+ issues.push({
1722
+ message: `Invalid $parent reference path at ${path.length === 0 ? "#" : path.join(".")}`,
1723
+ path
1724
+ });
1725
+ }
1726
+ return;
1727
+ }
1728
+ if ((0, import_utils12.isPlainObject)(value)) {
1729
+ if (hasMarkerKey(value)) {
1730
+ issues.push({
1731
+ message: 'Malformed $parent reference: objects carrying $parent must be exactly { "$parent": "<field>" }',
1732
+ path
1733
+ });
1734
+ return;
1735
+ }
1736
+ scanTemplateObjectEntries(value, path, issues);
1737
+ return;
1738
+ }
1739
+ if ((0, import_utils12.isArray)(value)) {
1740
+ value.forEach((item, index) => scanTemplateValueNode(item, [...path, index], issues));
1741
+ }
1742
+ }
1743
+ function scanTemplateObjectEntries(obj, basePath, issues) {
1744
+ for (const [key, child] of Object.entries(obj)) {
1745
+ const childPath = [...basePath, key];
1746
+ if (key === PARENT_MARKER_KEY) {
1747
+ issues.push({ message: "$parent is never a field name in correlated templates", path: childPath });
1748
+ continue;
1749
+ }
1750
+ if (SUBQUERY_PAYLOAD_KEYS.has(key)) {
1751
+ if (containsParentMarkerShape(child)) {
1752
+ issues.push({
1753
+ message: `Unsupported $parent reference inside ${key}: markers are not resolved in subquery payloads`,
1754
+ path: childPath
1755
+ });
1756
+ }
1757
+ continue;
1758
+ }
1759
+ if (MARKER_FORBIDDEN_OPERATOR_KEYS.has(key)) {
1760
+ if (containsParentMarkerShape(child)) {
1761
+ issues.push({
1762
+ message: `Unsupported $parent reference inside ${key}: markers are not resolved in ${key} values`,
1763
+ path: childPath
1764
+ });
1765
+ }
1766
+ continue;
1767
+ }
1768
+ if (key === "$elemMatch") {
1769
+ if (isParentRefMarker(child) || isEscapedParentRef(child)) {
1770
+ issues.push({
1771
+ message: "Unsupported $parent reference as a direct $elemMatch value",
1772
+ path: childPath
1773
+ });
1774
+ continue;
1775
+ }
1776
+ scanTemplateValueNode(child, childPath, issues);
1777
+ continue;
1778
+ }
1779
+ if (LOGICAL_CLAUSE_KEYS.has(key)) {
1780
+ if (!(0, import_utils12.isArray)(child)) {
1781
+ scanTemplateValueNode(child, childPath, issues);
1782
+ continue;
1783
+ }
1784
+ child.forEach((clause, index) => {
1785
+ const clausePath = [...childPath, index];
1786
+ if (isParentRefMarker(clause) || isEscapedParentRef(clause)) {
1787
+ issues.push({
1788
+ message: `Unsupported $parent reference as a direct ${key} clause: clauses must be filter objects`,
1789
+ path: clausePath
1790
+ });
1791
+ return;
1792
+ }
1793
+ if ((0, import_utils12.isPlainObject)(clause)) {
1794
+ scanTemplateObjectEntries(clause, clausePath, issues);
1795
+ }
1796
+ });
1797
+ continue;
1798
+ }
1799
+ scanTemplateValueNode(child, childPath, issues);
1800
+ }
1801
+ }
1802
+ function validateCorrelatedFilterTemplate(filter2) {
1803
+ const issues = [];
1804
+ if (!(0, import_utils12.isPlainObject)(filter2)) {
1805
+ issues.push({ message: "Correlated include filter must be an object", path: [] });
1806
+ return issues;
1807
+ }
1808
+ if (isParentRefMarker(filter2) || isEscapedParentRef(filter2)) {
1809
+ issues.push({ message: "Correlated include filter must not itself be a $parent reference", path: [] });
1810
+ return issues;
1811
+ }
1812
+ scanTemplateObjectEntries(filter2, [], issues);
1813
+ return issues;
1814
+ }
1815
+ function validateCorrelatedIdTemplate(id) {
1816
+ if (typeof id === "string") {
1817
+ return id.length > 0 ? [] : [{ message: "Correlated include id must be a non-empty string or $parent reference", path: [] }];
1818
+ }
1819
+ if (isEscapedParentRef(id)) {
1820
+ return [{ message: "Unsupported $escape as a correlated include id", path: [] }];
1821
+ }
1822
+ if (isParentRefMarker(id)) {
1823
+ return isValidParentRefPath(id.$parent) ? [] : [{ message: "Invalid $parent reference path in correlated include id", path: [] }];
1824
+ }
1825
+ return [{ message: "Correlated include id must be a string or $parent reference", path: [] }];
1826
+ }
1827
+ function findArgsMarkerIssue(value, where) {
1828
+ if (value !== void 0 && containsParentMarkerShape(value)) {
1829
+ return {
1830
+ message: `Unsupported $parent reference in ${where}: references are only supported in filter and id positions`,
1831
+ path: []
1832
+ };
1833
+ }
1834
+ return null;
1835
+ }
1836
+ function collectFilterReferencePaths(filter2) {
1837
+ const paths = [];
1838
+ const seen = /* @__PURE__ */ new Set();
1839
+ const visitValue = (value) => {
1840
+ if (isParentRefMarker(value)) {
1841
+ if (typeof value.$parent === "string") {
1842
+ paths.push(value.$parent);
1843
+ }
1844
+ return;
1845
+ }
1846
+ if (isEscapedParentRef(value)) return;
1847
+ if ((0, import_utils12.isArray)(value)) {
1848
+ if (seen.has(value)) return;
1849
+ seen.add(value);
1850
+ value.forEach(visitValue);
1851
+ return;
1852
+ }
1853
+ if ((0, import_utils12.isPlainObject)(value)) {
1854
+ if (seen.has(value)) return;
1855
+ seen.add(value);
1856
+ for (const [key, child] of Object.entries(value)) {
1857
+ if (SUBQUERY_PAYLOAD_KEYS.has(key) || MARKER_FORBIDDEN_OPERATOR_KEYS.has(key)) continue;
1858
+ visitValue(child);
1859
+ }
1860
+ }
1861
+ };
1862
+ visitValue(filter2);
1863
+ return paths;
1864
+ }
1865
+ function collectCorrelatedReferencePaths(include) {
1866
+ const paths = [];
1867
+ if (isParentRefMarker(include.id) && typeof include.id !== "string") {
1868
+ const ref = include.id;
1869
+ if (typeof ref.$parent === "string") paths.push(ref.$parent);
1870
+ } else if (typeof include.id === "string") {
1871
+ }
1872
+ const filter2 = include.filter;
1873
+ if (filter2 !== void 0) {
1874
+ paths.push(...collectFilterReferencePaths(filter2));
1875
+ }
1876
+ return [...new Set(paths)];
1877
+ }
1878
+ var CorrelatedReferenceError = class extends Error {
1879
+ constructor(message) {
1880
+ super(message);
1881
+ this.name = "CorrelatedReferenceError";
1882
+ }
1883
+ };
1884
+ var UNRESOLVABLE = /* @__PURE__ */ Symbol("correlated-include-unresolvable");
1885
+ function getOwnPathValue(parent, path) {
1886
+ const segments = path.split(".");
1887
+ let current = parent;
1888
+ for (const segment of segments) {
1889
+ if (DANGEROUS_PATH_SEGMENTS.has(segment)) {
1890
+ throw new CorrelatedReferenceError(`Invalid $parent reference path: ${path}`);
1891
+ }
1892
+ if (typeof current !== "object" || current === null) return void 0;
1893
+ if (!Object.prototype.hasOwnProperty.call(current, segment)) return void 0;
1894
+ current = current[segment];
1895
+ }
1896
+ return current;
1897
+ }
1898
+ function cloneSubstitutedValue(value) {
1899
+ if ((0, import_utils12.isPlainObject)(value) || (0, import_utils12.isArray)(value)) return (0, import_utils12.cloneDeep)(value);
1900
+ return value;
1901
+ }
1902
+ function literalParentRefObject(path) {
1903
+ const literal = {};
1904
+ Object.defineProperty(literal, PARENT_MARKER_KEY, {
1905
+ value: path,
1906
+ writable: true,
1907
+ enumerable: true,
1908
+ configurable: true
1909
+ });
1910
+ return literal;
1911
+ }
1912
+ function resolveObjectEntries(obj, parent) {
1913
+ const out = {};
1914
+ for (const [key, child] of Object.entries(obj)) {
1915
+ if (key === PARENT_MARKER_KEY) {
1916
+ throw new CorrelatedReferenceError("$parent is never a field name in correlated templates");
1917
+ }
1918
+ if (SUBQUERY_PAYLOAD_KEYS.has(key)) {
1919
+ if (containsParentMarkerShape(child)) {
1920
+ throw new CorrelatedReferenceError(`Unsupported $parent reference inside ${key}`);
1921
+ }
1922
+ out[key] = cloneSubstitutedValue(child);
1923
+ continue;
1924
+ }
1925
+ if (MARKER_FORBIDDEN_OPERATOR_KEYS.has(key)) {
1926
+ if (containsParentMarkerShape(child)) {
1927
+ throw new CorrelatedReferenceError(`Unsupported $parent reference inside ${key}`);
1928
+ }
1929
+ out[key] = cloneSubstitutedValue(child);
1930
+ continue;
1931
+ }
1932
+ if (key === "$elemMatch") {
1933
+ if (isParentRefMarker(child) || isEscapedParentRef(child)) {
1934
+ throw new CorrelatedReferenceError("Unsupported $parent reference as a direct $elemMatch value");
1935
+ }
1936
+ out[key] = resolveValueNode(child, parent, false);
1937
+ continue;
1938
+ }
1939
+ if (LOGICAL_CLAUSE_KEYS.has(key)) {
1940
+ if (!(0, import_utils12.isArray)(child)) {
1941
+ out[key] = resolveValueNode(child, parent, false);
1942
+ continue;
1943
+ }
1944
+ out[key] = child.map((clause) => {
1945
+ if (isParentRefMarker(clause) || isEscapedParentRef(clause)) {
1946
+ throw new CorrelatedReferenceError(`Unsupported $parent reference as a direct ${key} clause`);
1947
+ }
1948
+ if (!(0, import_utils12.isPlainObject)(clause)) return cloneSubstitutedValue(clause);
1949
+ return resolveObjectEntries(clause, parent);
1950
+ });
1951
+ continue;
1952
+ }
1953
+ if (key.startsWith("$")) {
1954
+ out[key] = resolveValueNode(child, parent, false);
1955
+ continue;
1956
+ }
1957
+ out[key] = resolveValueNode(child, parent, true);
1958
+ }
1959
+ return out;
1960
+ }
1961
+ function resolveValueNode(template, parent, bare) {
1962
+ if (isEscapedParentRef(template)) {
1963
+ const inner = template.$escape;
1964
+ if (!isParentRefMarker(inner) || typeof inner.$parent !== "string" || (inner.$parent?.length ?? 0) === 0) {
1965
+ throw new CorrelatedReferenceError('Invalid $escape payload: expected { "$escape": { "$parent": "<field>" } }');
1966
+ }
1967
+ const literal = literalParentRefObject(inner.$parent);
1968
+ return bare ? { $eq: literal } : literal;
1969
+ }
1970
+ if (isParentRefMarker(template)) {
1971
+ const path = template.$parent;
1972
+ if (!isValidParentRefPath(path)) {
1973
+ throw new CorrelatedReferenceError(`Invalid $parent reference path: ${String(path)}`);
1974
+ }
1975
+ const value = getOwnPathValue(parent, path);
1976
+ if (value === void 0 || value === null) throw UNRESOLVABLE;
1977
+ if (bare && (0, import_utils12.isPlainObject)(value)) return { $eq: (0, import_utils12.cloneDeep)(value) };
1978
+ return cloneSubstitutedValue(value);
1979
+ }
1980
+ if ((0, import_utils12.isPlainObject)(template)) {
1981
+ if (hasMarkerKey(template)) {
1982
+ throw new CorrelatedReferenceError(
1983
+ 'Malformed $parent reference: objects carrying $parent must be exactly { "$parent": "<field>" }'
1984
+ );
1985
+ }
1986
+ return resolveObjectEntries(template, parent);
1987
+ }
1988
+ if ((0, import_utils12.isArray)(template)) {
1989
+ return template.map((item) => resolveValueNode(item, parent, false));
1990
+ }
1991
+ return template;
1992
+ }
1993
+ function isUnresolvableSignal(error2) {
1994
+ return error2 === UNRESOLVABLE;
1995
+ }
1996
+ function resolveCorrelatedIdTemplate(id, parent) {
1997
+ try {
1998
+ if (typeof id === "string") {
1999
+ if (id.length === 0) throw new CorrelatedReferenceError("Correlated include id must be a non-empty string");
2000
+ return { status: "resolved", id };
2001
+ }
2002
+ if (isParentRefMarker(id)) {
2003
+ const path = id.$parent;
2004
+ if (!isValidParentRefPath(path)) {
2005
+ throw new CorrelatedReferenceError(`Invalid $parent reference path: ${String(path)}`);
2006
+ }
2007
+ const value = getOwnPathValue(parent, path);
2008
+ if (typeof value === "string") return { status: "resolved", id: value };
2009
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
2010
+ return { status: "resolved", id: String(value) };
2011
+ }
2012
+ throw UNRESOLVABLE;
2013
+ }
2014
+ throw new CorrelatedReferenceError("Correlated include id must be a string or $parent reference");
2015
+ } catch (error2) {
2016
+ if (isUnresolvableSignal(error2)) return { status: "unresolvable" };
2017
+ throw error2;
2018
+ }
2019
+ }
2020
+ function resolveCorrelatedFilterTemplate(filter2, parent) {
2021
+ try {
2022
+ if (!(0, import_utils12.isPlainObject)(filter2) || isParentRefMarker(filter2) || isEscapedParentRef(filter2)) {
2023
+ throw new CorrelatedReferenceError("Correlated include filter must be an object");
2024
+ }
2025
+ return { status: "resolved", filter: resolveObjectEntries(filter2, parent) };
2026
+ } catch (error2) {
2027
+ if (isUnresolvableSignal(error2)) return { status: "unresolvable" };
2028
+ throw error2;
2029
+ }
2030
+ }
2031
+ function validateExpandedCorrelatedOperands(expanded, limits) {
2032
+ return validateRequestComplexity(expanded, limits ?? null, "filter");
2033
+ }
2034
+ var MAX_NESTED_INCLUDE_DEPTH = 10;
2035
+ function validateCorrelatedArgsShape(args, op, depth) {
2036
+ if (args === void 0) return [];
2037
+ if (!(0, import_utils12.isPlainObject)(args)) return ["Correlated include args must be an object"];
2038
+ const record = args;
2039
+ const allowed = op === "list" ? /* @__PURE__ */ new Set(["select", "sort", "skip", "limit", "page", "pageSize", "include"]) : /* @__PURE__ */ new Set(["select", "sort", "include"]);
2040
+ for (const key of Object.keys(record)) {
2041
+ if (!allowed.has(key)) return [`Unsupported correlated include arg: ${key}`];
2042
+ }
2043
+ if (op === "count" && Object.keys(record).length > 0) return ["Count includes accept no args"];
2044
+ const selectIssue = findArgsMarkerIssue(record.select, "args.select");
2045
+ if (selectIssue) return [selectIssue.message];
2046
+ const sortIssue = findArgsMarkerIssue(record.sort, "args.sort");
2047
+ if (sortIssue) return [sortIssue.message];
2048
+ if (record.include !== void 0) {
2049
+ if (depth >= MAX_NESTED_INCLUDE_DEPTH) return ["Correlated include nesting exceeds the supported depth"];
2050
+ const nested = Array.isArray(record.include) ? record.include : [record.include];
2051
+ for (const entry of nested) {
2052
+ const errors = validateCorrelatedIncludeShape(entry, depth + 1);
2053
+ if (errors.length > 0) return errors;
2054
+ }
2055
+ }
2056
+ return [];
2057
+ }
2058
+ function validateCorrelatedIncludeShape(entry, depth = 0) {
2059
+ if (!(0, import_utils12.isPlainObject)(entry)) return ["Correlated include must be an object"];
2060
+ const record = entry;
2061
+ if (record.mode !== "correlated") return ['Correlated include mode must be "correlated"'];
2062
+ if (typeof record.model !== "string" || record.model.length === 0)
2063
+ return ["Correlated include model must be a non-empty string"];
2064
+ if (record.op !== "list" && record.op !== "read" && record.op !== "count") {
2065
+ return ['Correlated include op must be one of "list", "read", or "count"'];
2066
+ }
2067
+ if (!isValidCorrelatedOutputPath(record.path))
2068
+ return [`Invalid correlated include output path: ${String(record.path)}`];
2069
+ if ("localField" in record || "foreignField" in record) {
2070
+ return ["Correlated includes must not carry localField or foreignField"];
2071
+ }
2072
+ if (record.options !== void 0 && !((0, import_utils12.isPlainObject)(record.options) && Object.keys(record.options).length === 0)) {
2073
+ return ["Correlated include options must be absent or empty"];
2074
+ }
2075
+ const hasId = record.id !== void 0;
2076
+ const hasFilter = record.filter !== void 0;
2077
+ if (record.op === "read") {
2078
+ if (hasId === hasFilter) return ['Correlated read includes require exactly one of "id" or "filter"'];
2079
+ if (hasId) {
2080
+ const issues = validateCorrelatedIdTemplate(record.id);
2081
+ if (issues.length > 0) return [issues[0].message];
2082
+ } else {
2083
+ const issues = validateCorrelatedFilterTemplate(record.filter);
2084
+ if (issues.length > 0) return [issues[0].message];
2085
+ }
2086
+ } else {
2087
+ if (!hasFilter || hasId) return [`Correlated ${record.op} includes require a filter and no id`];
2088
+ const issues = validateCorrelatedFilterTemplate(record.filter);
2089
+ if (issues.length > 0) return [issues[0].message];
2090
+ }
2091
+ return validateCorrelatedArgsShape(record.args, record.op, depth);
2092
+ }
2093
+ function isCorrelatedInclude(value) {
2094
+ return (0, import_utils12.isPlainObject)(value) && value.mode === "correlated";
2095
+ }
2096
+
2097
+ // src/helpers/request-work.ts
2098
+ var states = /* @__PURE__ */ new WeakMap();
2099
+ function getRequestWorkState(req, runtime) {
2100
+ let owners = states.get(req);
2101
+ if (!owners) {
2102
+ owners = /* @__PURE__ */ new WeakMap();
2103
+ states.set(req, owners);
2104
+ }
2105
+ let state = owners.get(runtime);
2106
+ if (!state) {
2107
+ state = {
2108
+ totalQueries: 0,
2109
+ scheduler: new RequestConcurrencyScheduler(
2110
+ resolveRequestComplexity(runtime.getGlobalOption("requestComplexity")).maxBulkConcurrency
2111
+ )
2112
+ };
2113
+ owners.set(runtime, state);
2114
+ }
2115
+ return state;
2116
+ }
2117
+
2118
+ // src/services/base.ts
2119
+ function getCorrelatedTemplateDepth(includes) {
2120
+ let max = 0;
2121
+ const visit = (entries, depth) => {
2122
+ const list = (0, import_utils13.castArray)(entries).filter(Boolean);
2123
+ for (const entry of list) {
2124
+ if (!(0, import_utils13.isPlainObject)(entry)) continue;
2125
+ max = Math.max(max, depth);
2126
+ const nested = entry.args?.include;
2127
+ if (nested !== void 0) {
2128
+ visit(nested, depth + 1);
2129
+ }
2130
+ }
2131
+ };
2132
+ if (includes.length > 0) visit(includes, 1);
2133
+ return max;
2134
+ }
1609
2135
  var ClientRequestError = class extends Error {
1610
2136
  constructor(result) {
1611
2137
  super(String(result.code));
@@ -1620,11 +2146,11 @@ function validateClientFilter(filter2) {
1620
2146
  }
1621
2147
  const blockedOperators = /* @__PURE__ */ new Set(["$where", "$expr", "$function", "$accumulator"]);
1622
2148
  const visit = (value, path) => {
1623
- if ((0, import_utils12.isArray)(value)) {
2149
+ if ((0, import_utils13.isArray)(value)) {
1624
2150
  value.forEach((item, index) => visit(item, `${path}[${index}]`));
1625
2151
  return;
1626
2152
  }
1627
- if (!(0, import_utils12.isPlainObject)(value)) return;
2153
+ if (!(0, import_utils13.isPlainObject)(value)) return;
1628
2154
  Object.entries(value).forEach(([key, child]) => {
1629
2155
  const nextPath = path ? `${path}.${key}` : key;
1630
2156
  if (blockedOperators.has(key)) {
@@ -1639,6 +2165,7 @@ function validateClientFilter(filter2) {
1639
2165
  }
1640
2166
  var Base = class {
1641
2167
  constructor(req, modelName) {
2168
+ this.requestRuntime = getActiveRuntime() ?? defaultRuntime;
1642
2169
  this.req = req;
1643
2170
  this.modelName = modelName;
1644
2171
  }
@@ -1744,25 +2271,90 @@ var Base = class {
1744
2271
  return this.req.macl.getPublicService(modelName);
1745
2272
  }
1746
2273
  processInclude(include) {
1747
- const includes = (0, import_utils12.compact)((0, import_utils12.castArray)(include)).filter(({ model, op, path, localField, foreignField }) => {
1748
- return model && op && path && localField && foreignField;
1749
- });
2274
+ const items = (0, import_utils13.compact)((0, import_utils13.castArray)(include));
2275
+ const legacy = [];
2276
+ const correlated = [];
2277
+ const correlatedReferenceFields = [];
2278
+ for (const entry of items) {
2279
+ if (isCorrelatedInclude(entry)) {
2280
+ const shapeErrors = validateCorrelatedIncludeShape(entry);
2281
+ if (shapeErrors.length > 0) {
2282
+ this.throwClientRequestError("bad_request" /* BadRequest */, shapeErrors[0]);
2283
+ }
2284
+ correlated.push(entry);
2285
+ correlatedReferenceFields.push(...collectCorrelatedReferencePaths(entry));
2286
+ continue;
2287
+ }
2288
+ if (containsParentMarkerShape(entry)) {
2289
+ this.throwClientRequestError(
2290
+ "bad_request" /* BadRequest */,
2291
+ 'Legacy includes must not contain $parent markers; use a correlated include (mode: "correlated")'
2292
+ );
2293
+ }
2294
+ const candidate = entry;
2295
+ if (candidate.model && candidate.op && candidate.path && candidate.localField && candidate.foreignField) {
2296
+ legacy.push(entry);
2297
+ }
2298
+ }
2299
+ const seenCorrelatedPaths = /* @__PURE__ */ new Set();
2300
+ for (const entry of correlated) {
2301
+ if (seenCorrelatedPaths.has(entry.path)) {
2302
+ this.throwClientRequestError("bad_request" /* BadRequest */, `Duplicate correlated include output path: ${entry.path}`);
2303
+ }
2304
+ seenCorrelatedPaths.add(entry.path);
2305
+ }
2306
+ this.assertIncludeOutputPaths([...legacy, ...correlated]);
1750
2307
  let includeLocalFields = [];
1751
2308
  let includePaths = [];
1752
- (0, import_utils12.forEach)(includes, (inc) => {
2309
+ (0, import_utils13.forEach)(legacy, (inc) => {
1753
2310
  includeLocalFields.push(inc.localField);
1754
2311
  includePaths.push(inc.path);
1755
2312
  });
1756
- includeLocalFields = (0, import_utils12.uniq)((0, import_utils12.compact)(includeLocalFields));
1757
- includePaths = (0, import_utils12.uniq)((0, import_utils12.compact)(includePaths));
2313
+ includeLocalFields = (0, import_utils13.uniq)((0, import_utils13.compact)(includeLocalFields));
2314
+ includePaths = (0, import_utils13.uniq)((0, import_utils13.compact)(includePaths));
1758
2315
  return {
1759
- includes,
2316
+ includes: legacy,
2317
+ correlatedIncludes: correlated,
1760
2318
  includeLocalFields,
1761
- includePaths
2319
+ includePaths,
2320
+ correlatedReferenceFields: (0, import_utils13.uniq)((0, import_utils13.compact)(correlatedReferenceFields))
1762
2321
  };
1763
2322
  }
2323
+ validateIncludeOutputPath(path, modelName) {
2324
+ const permissionField = getModelOption(modelName, "documentPermissionField");
2325
+ if ((0, import_utils13.get)((0, import_utils13.set)({}, path, true), permissionField) !== void 0 || (0, import_utils13.get)((0, import_utils13.set)({}, permissionField, true), path) !== void 0) {
2326
+ this.throwClientRequestError(
2327
+ "bad_request" /* BadRequest */,
2328
+ `Include output path ${path} overlaps document permission field ${permissionField} on model ${modelName}`
2329
+ );
2330
+ }
2331
+ }
2332
+ assertIncludeOutputPaths(include) {
2333
+ const pending = [{ entries: include, modelName: this.modelName }];
2334
+ const visited = /* @__PURE__ */ new WeakMap();
2335
+ while (pending.length > 0) {
2336
+ const { entries, modelName } = pending.pop();
2337
+ if (modelName !== this.modelName && !getModelNames().includes(modelName)) {
2338
+ this.throwClientRequestError("bad_request" /* BadRequest */, `Model ${modelName} not found`);
2339
+ }
2340
+ for (const entry of entries) {
2341
+ if (!(0, import_utils13.isPlainObject)(entry)) continue;
2342
+ const models = visited.get(entry) ?? /* @__PURE__ */ new Set();
2343
+ if (models.has(modelName)) continue;
2344
+ models.add(modelName);
2345
+ visited.set(entry, models);
2346
+ if (typeof entry.path === "string" && entry.path) {
2347
+ this.validateIncludeOutputPath(entry.path, modelName);
2348
+ }
2349
+ if ((entry.op === "read" || entry.op === "list") && typeof entry.model === "string") {
2350
+ const nested = entry.args?.include;
2351
+ if (nested) pending.push({ entries: (0, import_utils13.compact)((0, import_utils13.castArray)(nested)), modelName: entry.model });
2352
+ }
2353
+ }
2354
+ }
2355
+ }
1764
2356
  getForeignKeyValues(value) {
1765
- return (0, import_utils12.flatten)((0, import_utils12.castArray)(value));
2357
+ return (0, import_utils13.flatten)((0, import_utils13.castArray)(value));
1766
2358
  }
1767
2359
  getForeignKey(value) {
1768
2360
  return String(value);
@@ -1770,7 +2362,7 @@ var Base = class {
1770
2362
  buildForeignKeyIndex(rows, foreignField) {
1771
2363
  const index = /* @__PURE__ */ new Map();
1772
2364
  for (const row of rows) {
1773
- for (const value of this.getForeignKeyValues((0, import_utils12.get)(row, foreignField))) {
2365
+ for (const value of this.getForeignKeyValues((0, import_utils13.get)(row, foreignField))) {
1774
2366
  const key = this.getForeignKey(value);
1775
2367
  const values = index.get(key);
1776
2368
  if (values) values.push(row);
@@ -1801,7 +2393,7 @@ var Base = class {
1801
2393
  return ids.size;
1802
2394
  }
1803
2395
  sanitizeIncludeArgs(args) {
1804
- if (!(0, import_utils12.isPlainObject)(args)) return {};
2396
+ if (!(0, import_utils13.isPlainObject)(args)) return {};
1805
2397
  const { overrides: _clientOverrides, ...trustedArgs } = args;
1806
2398
  return trustedArgs;
1807
2399
  }
@@ -1812,12 +2404,18 @@ var Base = class {
1812
2404
  }
1813
2405
  async includeDocs(docs, include) {
1814
2406
  if (!include) return docs;
1815
- const includes = (0, import_utils12.compact)((0, import_utils12.castArray)(include));
2407
+ const includes = (0, import_utils13.compact)((0, import_utils13.castArray)(include));
1816
2408
  if (includes.length === 0) return docs;
1817
- const isSingle = !(0, import_utils12.isArray)(docs);
2409
+ const isSingle = !(0, import_utils13.isArray)(docs);
1818
2410
  let docList = isSingle ? [docs] : docs;
1819
2411
  for (let x = 0; x < includes.length; x++) {
1820
2412
  const include2 = includes[x];
2413
+ if (isCorrelatedInclude(include2)) {
2414
+ this.throwClientRequestError(
2415
+ "bad_request" /* BadRequest */,
2416
+ "Correlated includes are not supported by the legacy include executor"
2417
+ );
2418
+ }
1821
2419
  this.assertIncludeForeignField(include2);
1822
2420
  switch (include2.op) {
1823
2421
  case "count":
@@ -1838,7 +2436,7 @@ var Base = class {
1838
2436
  const svc = await this.getAuthorizedTargetService(model, "read");
1839
2437
  for (let y = 0; y < docs.length; y++) {
1840
2438
  const doc = docs[y];
1841
- const localValue = (0, import_utils12.get)(doc, localField);
2439
+ const localValue = (0, import_utils13.get)(doc, localField);
1842
2440
  const filter2 = { ..._filters ?? {}, [foreignField]: { $in: this.getForeignKeyValues(localValue) } };
1843
2441
  const trustedOptions = {
1844
2442
  ...options,
@@ -1857,10 +2455,10 @@ var Base = class {
1857
2455
  const { model, op, path, localField, foreignField, filter: _filters, args = {}, options = {} } = include;
1858
2456
  const svc = await this.getAuthorizedTargetService(model, op);
1859
2457
  const includeLocalValues = [];
1860
- (0, import_utils12.forEach)(docs, (doc) => {
1861
- includeLocalValues.push(...this.getForeignKeyValues((0, import_utils12.get)(doc, localField)));
2458
+ (0, import_utils13.forEach)(docs, (doc) => {
2459
+ includeLocalValues.push(...this.getForeignKeyValues((0, import_utils13.get)(doc, localField)));
1862
2460
  });
1863
- const filter2 = { ..._filters ?? {}, [foreignField]: { $in: (0, import_utils12.flatten)(includeLocalValues) } };
2461
+ const filter2 = { ..._filters ?? {}, [foreignField]: { $in: (0, import_utils13.flatten)(includeLocalValues) } };
1864
2462
  const authorizedFilter = await svc.genFilter(op, filter2);
1865
2463
  const trustedArgs = {
1866
2464
  ...this.sanitizeIncludeArgs(args),
@@ -1879,7 +2477,7 @@ var Base = class {
1879
2477
  const index = this.buildForeignKeyIndex(trustedResult.data, foreignField);
1880
2478
  for (let y = 0; y < docs.length; y++) {
1881
2479
  const doc = docs[y];
1882
- const matches = this.getIndexedMatches(index, (0, import_utils12.get)(doc, localField));
2480
+ const matches = this.getIndexedMatches(index, (0, import_utils13.get)(doc, localField));
1883
2481
  setDocValue(doc, path, op === "list" ? matches : matches[0]);
1884
2482
  }
1885
2483
  return docs;
@@ -1887,15 +2485,357 @@ var Base = class {
1887
2485
  async includeDocsCount(docs, include) {
1888
2486
  const { model, path, localField, foreignField, filter: _filters } = include;
1889
2487
  const svc = await this.getAuthorizedTargetService(model, "count");
1890
- const localValues = docs.flatMap((doc) => this.getForeignKeyValues((0, import_utils12.get)(doc, localField)));
2488
+ const localValues = docs.flatMap((doc) => this.getForeignKeyValues((0, import_utils13.get)(doc, localField)));
1891
2489
  const result = await svc.countByFieldValues(foreignField, localValues, _filters ?? {}, "count");
2490
+ if (!result.success && result.code === "bad_request" /* BadRequest */) throw new ClientRequestError(result);
1892
2491
  if (!result.success) return docs;
1893
2492
  for (let y = 0; y < docs.length; y++) {
1894
2493
  const doc = docs[y];
1895
- setDocValue(doc, path, this.getIndexedCount(result.data, (0, import_utils12.get)(doc, localField)));
2494
+ setDocValue(doc, path, this.getIndexedCount(result.data, (0, import_utils13.get)(doc, localField)));
2495
+ }
2496
+ return docs;
2497
+ }
2498
+ getCorrelatedExecState() {
2499
+ return getRequestWorkState(this.req, this.requestRuntime);
2500
+ }
2501
+ claimCorrelatedQuerySlot() {
2502
+ const limits = this.getRequestComplexity();
2503
+ const state = this.getCorrelatedExecState();
2504
+ if (state.totalQueries >= limits.maxCorrelatedQueries) {
2505
+ this.throwClientRequestError(
2506
+ "bad_request" /* BadRequest */,
2507
+ `Correlated include query budget exceeded (max ${limits.maxCorrelatedQueries})`
2508
+ );
2509
+ }
2510
+ state.totalQueries += 1;
2511
+ }
2512
+ sanitizeCorrelatedArgs(include) {
2513
+ const args = include.args ?? {};
2514
+ if (include.op === "list") {
2515
+ const { select: select2, sort: sort2, skip, limit, page, pageSize, include: nestedInclude2 } = args;
2516
+ return { select: select2, sort: sort2, skip, limit, page, pageSize, nestedInclude: nestedInclude2 };
2517
+ }
2518
+ const { select, sort, include: nestedInclude } = args;
2519
+ return { select, sort, nestedInclude };
2520
+ }
2521
+ async includeCorrelatedDocs(docs, correlatedIncludes, snapshots, depth = 0) {
2522
+ if (correlatedIncludes.length === 0) return docs;
2523
+ const limits = this.getRequestComplexity();
2524
+ const templateDepth = getCorrelatedTemplateDepth(correlatedIncludes);
2525
+ if (depth + templateDepth > limits.maxCorrelatedDepth) {
2526
+ this.throwClientRequestError(
2527
+ "bad_request" /* BadRequest */,
2528
+ `Correlated include depth exceeded (max ${limits.maxCorrelatedDepth})`
2529
+ );
2530
+ }
2531
+ const state = this.getCorrelatedExecState();
2532
+ const resolvedSnapshots = snapshots ?? docs.map((doc) => (0, import_utils13.cloneDeep)(toObject(doc)));
2533
+ for (const include of correlatedIncludes) {
2534
+ switch (include.op) {
2535
+ case "read":
2536
+ await this.includeCorrelatedRead(docs, include, resolvedSnapshots, state, depth);
2537
+ break;
2538
+ case "list":
2539
+ await this.includeCorrelatedList(docs, include, resolvedSnapshots, state, depth);
2540
+ break;
2541
+ case "count":
2542
+ await this.includeCorrelatedCount(docs, include, resolvedSnapshots, state, depth);
2543
+ break;
2544
+ default:
2545
+ this.throwClientRequestError(
2546
+ "bad_request" /* BadRequest */,
2547
+ `Unsupported correlated include op: ${include.op}`
2548
+ );
2549
+ }
1896
2550
  }
1897
2551
  return docs;
1898
2552
  }
2553
+ async includeCorrelatedRead(docs, include, snapshots, state, depth) {
2554
+ const svc = await this.getAuthorizedTargetService(include.model, "read");
2555
+ const { select, sort, nestedInclude } = this.sanitizeCorrelatedArgs(include);
2556
+ const hasId = include.id !== void 0;
2557
+ if (hasId) {
2558
+ const idTemplate = include.id;
2559
+ const tasks2 = docs.map((_, index) => ({ index, snapshot: snapshots[index] }));
2560
+ await state.scheduler.map(tasks2, async ({ index, snapshot }) => {
2561
+ let resolution;
2562
+ try {
2563
+ resolution = resolveCorrelatedIdTemplate(idTemplate, snapshot);
2564
+ } catch (error2) {
2565
+ if (error2 instanceof CorrelatedReferenceError) {
2566
+ throw new ClientRequestError({
2567
+ success: false,
2568
+ kind: "error",
2569
+ code: "bad_request" /* BadRequest */,
2570
+ errors: [{ detail: error2.message }]
2571
+ });
2572
+ }
2573
+ throw error2;
2574
+ }
2575
+ if (resolution.status === "unresolvable") {
2576
+ setDocValue(docs[index], include.path, null);
2577
+ return;
2578
+ }
2579
+ const resolvedId = resolution.id;
2580
+ let idFilter;
2581
+ try {
2582
+ idFilter = await svc.genIDFilter(resolvedId);
2583
+ } catch (error2) {
2584
+ const clientResult = this.getClientRequestErrorResult(error2);
2585
+ if (clientResult) throw new ClientRequestError(clientResult);
2586
+ throw error2;
2587
+ }
2588
+ const authorized = await svc.genFilter("read", idFilter);
2589
+ if (authorized === false) {
2590
+ throw new ClientRequestError({ success: false, kind: "error", code: "forbidden" /* Forbidden */ });
2591
+ }
2592
+ this.claimCorrelatedQuerySlot();
2593
+ let result;
2594
+ try {
2595
+ result = await svc.findOne(
2596
+ {},
2597
+ {
2598
+ select,
2599
+ sort,
2600
+ include: nestedInclude,
2601
+ overrides: { filter: authorized }
2602
+ },
2603
+ { access: "read", lean: true, includePermissions: false }
2604
+ );
2605
+ } catch (error2) {
2606
+ const clientResult = this.getClientRequestErrorResult(error2);
2607
+ if (clientResult) throw new ClientRequestError(clientResult);
2608
+ throw error2;
2609
+ }
2610
+ if (result.success) {
2611
+ setDocValue(docs[index], include.path, result.data);
2612
+ } else if (result.code === "not_found" /* NotFound */) {
2613
+ setDocValue(docs[index], include.path, null);
2614
+ } else {
2615
+ throw new ClientRequestError(result);
2616
+ }
2617
+ });
2618
+ return;
2619
+ }
2620
+ const filterTemplate = include.filter;
2621
+ const templateErrors = this.validateClientFilter(filterTemplate);
2622
+ if (templateErrors.length > 0) {
2623
+ this.throwClientRequestError("bad_request" /* BadRequest */, templateErrors[0]);
2624
+ }
2625
+ let parsedTemplate;
2626
+ try {
2627
+ parsedTemplate = await this.parseClientData(
2628
+ filterTemplate,
2629
+ state.scheduler
2630
+ );
2631
+ } catch (error2) {
2632
+ const clientResult = this.getClientRequestErrorResult(error2);
2633
+ if (clientResult) throw new ClientRequestError(clientResult);
2634
+ throw error2;
2635
+ }
2636
+ const tasks = docs.map((_, index) => ({ index, snapshot: snapshots[index] }));
2637
+ await state.scheduler.map(tasks, async ({ index, snapshot }) => {
2638
+ let resolution;
2639
+ try {
2640
+ resolution = resolveCorrelatedFilterTemplate(parsedTemplate, snapshot);
2641
+ } catch (error2) {
2642
+ if (error2 instanceof CorrelatedReferenceError) {
2643
+ throw new ClientRequestError({
2644
+ success: false,
2645
+ kind: "error",
2646
+ code: "bad_request" /* BadRequest */,
2647
+ errors: [{ detail: error2.message }]
2648
+ });
2649
+ }
2650
+ throw error2;
2651
+ }
2652
+ if (resolution.status === "unresolvable") {
2653
+ setDocValue(docs[index], include.path, null);
2654
+ return;
2655
+ }
2656
+ const expanded = resolution.filter;
2657
+ const operandErrors = validateExpandedCorrelatedOperands(expanded, this.getRequestComplexity());
2658
+ if (operandErrors.length > 0) {
2659
+ throw new ClientRequestError({
2660
+ success: false,
2661
+ kind: "error",
2662
+ code: "bad_request" /* BadRequest */,
2663
+ errors: [{ detail: operandErrors[0].detail }]
2664
+ });
2665
+ }
2666
+ const authorized = await svc.genFilter("read", expanded);
2667
+ if (authorized === false) {
2668
+ throw new ClientRequestError({ success: false, kind: "error", code: "forbidden" /* Forbidden */ });
2669
+ }
2670
+ this.claimCorrelatedQuerySlot();
2671
+ let result;
2672
+ try {
2673
+ result = await svc.findOne(
2674
+ {},
2675
+ {
2676
+ select,
2677
+ sort,
2678
+ include: nestedInclude,
2679
+ overrides: { filter: authorized }
2680
+ },
2681
+ { access: "read", lean: true, includePermissions: false }
2682
+ );
2683
+ } catch (error2) {
2684
+ const clientResult = this.getClientRequestErrorResult(error2);
2685
+ if (clientResult) throw new ClientRequestError(clientResult);
2686
+ throw error2;
2687
+ }
2688
+ if (result.success) {
2689
+ setDocValue(docs[index], include.path, result.data);
2690
+ } else if (result.code === "not_found" /* NotFound */) {
2691
+ setDocValue(docs[index], include.path, null);
2692
+ } else {
2693
+ throw new ClientRequestError(result);
2694
+ }
2695
+ });
2696
+ void depth;
2697
+ }
2698
+ async includeCorrelatedList(docs, include, snapshots, state, depth) {
2699
+ const svc = await this.getAuthorizedTargetService(include.model, "list");
2700
+ const { select, sort, skip, limit, page, pageSize, nestedInclude } = this.sanitizeCorrelatedArgs(include);
2701
+ const filterTemplate = include.filter;
2702
+ const templateErrors = this.validateClientFilter(filterTemplate);
2703
+ if (templateErrors.length > 0) {
2704
+ this.throwClientRequestError("bad_request" /* BadRequest */, templateErrors[0]);
2705
+ }
2706
+ let parsedTemplate;
2707
+ try {
2708
+ parsedTemplate = await this.parseClientData(
2709
+ filterTemplate,
2710
+ state.scheduler
2711
+ );
2712
+ } catch (error2) {
2713
+ const clientResult = this.getClientRequestErrorResult(error2);
2714
+ if (clientResult) throw new ClientRequestError(clientResult);
2715
+ throw error2;
2716
+ }
2717
+ const tasks = docs.map((_, index) => ({ index, snapshot: snapshots[index] }));
2718
+ await state.scheduler.map(tasks, async ({ index, snapshot }) => {
2719
+ let resolution;
2720
+ try {
2721
+ resolution = resolveCorrelatedFilterTemplate(parsedTemplate, snapshot);
2722
+ } catch (error2) {
2723
+ if (error2 instanceof CorrelatedReferenceError) {
2724
+ throw new ClientRequestError({
2725
+ success: false,
2726
+ kind: "error",
2727
+ code: "bad_request" /* BadRequest */,
2728
+ errors: [{ detail: error2.message }]
2729
+ });
2730
+ }
2731
+ throw error2;
2732
+ }
2733
+ if (resolution.status === "unresolvable") {
2734
+ setDocValue(docs[index], include.path, []);
2735
+ return;
2736
+ }
2737
+ const expanded = resolution.filter;
2738
+ const operandErrors = validateExpandedCorrelatedOperands(expanded, this.getRequestComplexity());
2739
+ if (operandErrors.length > 0) {
2740
+ throw new ClientRequestError({
2741
+ success: false,
2742
+ kind: "error",
2743
+ code: "bad_request" /* BadRequest */,
2744
+ errors: [{ detail: operandErrors[0].detail }]
2745
+ });
2746
+ }
2747
+ const authorized = await svc.genFilter("list", expanded);
2748
+ if (authorized === false) {
2749
+ throw new ClientRequestError({ success: false, kind: "error", code: "forbidden" /* Forbidden */ });
2750
+ }
2751
+ this.claimCorrelatedQuerySlot();
2752
+ let result;
2753
+ try {
2754
+ result = await svc.find(
2755
+ {},
2756
+ {
2757
+ select,
2758
+ sort,
2759
+ skip,
2760
+ limit,
2761
+ page,
2762
+ pageSize,
2763
+ include: nestedInclude,
2764
+ overrides: { filter: authorized }
2765
+ },
2766
+ { includeCount: false, lean: true, includePermissions: false }
2767
+ );
2768
+ } catch (error2) {
2769
+ const clientResult = this.getClientRequestErrorResult(error2);
2770
+ if (clientResult) throw new ClientRequestError(clientResult);
2771
+ throw error2;
2772
+ }
2773
+ if (result.success) {
2774
+ setDocValue(docs[index], include.path, result.data);
2775
+ } else {
2776
+ throw new ClientRequestError(result);
2777
+ }
2778
+ });
2779
+ void depth;
2780
+ }
2781
+ async includeCorrelatedCount(docs, include, snapshots, state, depth) {
2782
+ const svc = await this.getAuthorizedTargetService(include.model, "count");
2783
+ const filterTemplate = include.filter;
2784
+ const templateErrors = this.validateClientFilter(filterTemplate);
2785
+ if (templateErrors.length > 0) {
2786
+ this.throwClientRequestError("bad_request" /* BadRequest */, templateErrors[0]);
2787
+ }
2788
+ const tasks = docs.map((_, index) => ({ index, snapshot: snapshots[index] }));
2789
+ await state.scheduler.map(tasks, async ({ index, snapshot }) => {
2790
+ let resolution;
2791
+ try {
2792
+ resolution = resolveCorrelatedFilterTemplate(filterTemplate, snapshot);
2793
+ } catch (error2) {
2794
+ if (error2 instanceof CorrelatedReferenceError) {
2795
+ throw new ClientRequestError({
2796
+ success: false,
2797
+ kind: "error",
2798
+ code: "bad_request" /* BadRequest */,
2799
+ errors: [{ detail: error2.message }]
2800
+ });
2801
+ }
2802
+ throw error2;
2803
+ }
2804
+ if (resolution.status === "unresolvable") {
2805
+ setDocValue(docs[index], include.path, 0);
2806
+ return;
2807
+ }
2808
+ const expanded = resolution.filter;
2809
+ const operandErrors = validateExpandedCorrelatedOperands(expanded, this.getRequestComplexity());
2810
+ if (operandErrors.length > 0) {
2811
+ throw new ClientRequestError({
2812
+ success: false,
2813
+ kind: "error",
2814
+ code: "bad_request" /* BadRequest */,
2815
+ errors: [{ detail: operandErrors[0].detail }]
2816
+ });
2817
+ }
2818
+ const authorized = await svc.genFilter("count", expanded);
2819
+ if (authorized === false) {
2820
+ throw new ClientRequestError({ success: false, kind: "error", code: "forbidden" /* Forbidden */ });
2821
+ }
2822
+ this.claimCorrelatedQuerySlot();
2823
+ let result;
2824
+ try {
2825
+ result = await svc.countTrusted(authorized);
2826
+ } catch (error2) {
2827
+ const clientResult = this.getClientRequestErrorResult(error2);
2828
+ if (clientResult) throw new ClientRequestError(clientResult);
2829
+ throw error2;
2830
+ }
2831
+ if (result.success) {
2832
+ setDocValue(docs[index], include.path, result.data);
2833
+ } else {
2834
+ throw new ClientRequestError(result);
2835
+ }
2836
+ });
2837
+ void depth;
2838
+ }
1899
2839
  async parseClientData(filter2, scheduler = new RequestConcurrencyScheduler(this.getRequestComplexity().maxBulkConcurrency), scheduled = false) {
1900
2840
  const result = await iterateQuery(
1901
2841
  filter2,
@@ -1938,10 +2878,10 @@ var Base = class {
1938
2878
  }
1939
2879
  let ret = result.data;
1940
2880
  if (sqOptions.path) {
1941
- ret = (0, import_utils12.isArray)(ret) ? (0, import_utils12.flatten)(ret.map((v) => (0, import_utils12.get)(v, sqOptions.path))) : (0, import_utils12.get)(ret, sqOptions.path);
2881
+ ret = (0, import_utils13.isArray)(ret) ? (0, import_utils13.flatten)(ret.map((v) => (0, import_utils13.get)(v, sqOptions.path))) : (0, import_utils13.get)(ret, sqOptions.path);
1942
2882
  }
1943
2883
  if (sqOptions.compact) {
1944
- ret = (0, import_utils12.compact)((0, import_utils12.castArray)(ret));
2884
+ ret = (0, import_utils13.compact)((0, import_utils13.castArray)(ret));
1945
2885
  }
1946
2886
  return ret;
1947
2887
  }
@@ -1953,10 +2893,30 @@ var Base = class {
1953
2893
  };
1954
2894
 
1955
2895
  // src/services/service.ts
1956
- var import_utils14 = require("@web-ts-toolkit/utils");
2896
+ var import_utils17 = require("@web-ts-toolkit/utils");
1957
2897
  var import_just_diff = require("just-diff");
1958
2898
 
1959
2899
  // src/model.ts
2900
+ var import_utils14 = require("@web-ts-toolkit/utils");
2901
+ function admitModelPersistence(adapter, scheduler) {
2902
+ return {
2903
+ modelName: adapter.modelName,
2904
+ mongooseModel: adapter.mongooseModel,
2905
+ new: () => adapter.new(),
2906
+ // Mongoose create(array) otherwise starts all document saves at once. Keep
2907
+ // the adapter's array input/output contract, with one permit per item; all
2908
+ // items are submitted even if one rejects, as with unordered create(array).
2909
+ create: (data) => Array.isArray(data) ? Promise.all(data.map((item) => scheduler.work(async () => (await adapter.create([item]))[0]))) : scheduler.work(() => adapter.create(data)),
2910
+ find: (props) => scheduler.work(() => adapter.find(props)),
2911
+ findOne: (props) => scheduler.work(() => adapter.findOne(props)),
2912
+ exists: (filter2) => scheduler.work(() => adapter.exists(filter2)),
2913
+ countDocuments: (filter2) => scheduler.work(() => adapter.countDocuments(filter2)),
2914
+ distinct: (field, conditions) => scheduler.work(() => adapter.distinct(field, conditions)),
2915
+ aggregate: (pipeline) => scheduler.work(() => adapter.aggregate(pipeline)),
2916
+ // Casting builds no persistence work and must not acquire a recursive permit.
2917
+ ...adapter.castFilter ? { castFilter: (filter2) => adapter.castFilter(filter2) } : {}
2918
+ };
2919
+ }
1960
2920
  var Model = class {
1961
2921
  constructor(modelName, runtime) {
1962
2922
  this.modelName = modelName;
@@ -2009,6 +2969,11 @@ var Model = class {
2009
2969
  countDocuments(filter2 = {}) {
2010
2970
  return this.mongooseModel.countDocuments(filter2);
2011
2971
  }
2972
+ /** Cast a copy using this model's query schema/setters, without executing query middleware or persistence. */
2973
+ castFilter(filter2) {
2974
+ const query = this.mongooseModel.countDocuments((0, import_utils14.cloneDeep)(filter2));
2975
+ return query.cast(this.mongooseModel);
2976
+ }
2012
2977
  // see https://mongoosejs.com/docs/api.html#model_Model.distinct
2013
2978
  distinct(field, conditions = {}) {
2014
2979
  return this.mongooseModel.distinct(field, conditions);
@@ -2019,6 +2984,39 @@ var Model = class {
2019
2984
  };
2020
2985
  var model_default = Model;
2021
2986
 
2987
+ // src/helpers/apply-update.ts
2988
+ var import_utils15 = require("@web-ts-toolkit/utils");
2989
+ function applyUpdate(target, data, allowedFields) {
2990
+ const wholeFields = /* @__PURE__ */ new Set();
2991
+ const partialContainers = /* @__PURE__ */ new Set();
2992
+ const collectBoundary = (shape, prefix = "") => {
2993
+ for (const [key, value] of Object.entries(shape)) {
2994
+ const path = prefix ? `${prefix}.${key}` : key;
2995
+ if (value === true) wholeFields.add(path);
2996
+ else {
2997
+ partialContainers.add(path);
2998
+ collectBoundary(value, path);
2999
+ }
3000
+ }
3001
+ };
3002
+ for (const field of allowedFields) {
3003
+ collectBoundary((0, import_utils15.set)({}, field, true));
3004
+ }
3005
+ const apply = (values, prefix = "") => {
3006
+ for (const [key, value] of Object.entries(values)) {
3007
+ const path = prefix ? `${prefix}.${key}` : key;
3008
+ if (partialContainers.has(path) && !wholeFields.has(path) && ((0, import_utils15.isPlainObject)(value) || Array.isArray(value))) {
3009
+ apply(value, path);
3010
+ } else if (isDocument2(target)) {
3011
+ target.set(path, value);
3012
+ } else {
3013
+ (0, import_utils15.set)(target, path, value);
3014
+ }
3015
+ }
3016
+ };
3017
+ apply(data ?? {});
3018
+ }
3019
+
2022
3020
  // src/logger-helpers.ts
2023
3021
  var SENSITIVE_FILTER_KEYS = /* @__PURE__ */ new Set([
2024
3022
  "password",
@@ -2197,28 +3195,52 @@ var error = (error2) => {
2197
3195
  };
2198
3196
 
2199
3197
  // src/services/model-subdocument-service.ts
2200
- var import_utils13 = require("@web-ts-toolkit/utils");
3198
+ var import_utils16 = require("@web-ts-toolkit/utils");
3199
+ async function visibleMutationRows(service, isAllowed, parentId, sub, rows, response) {
3200
+ const accesses = ["read", `subs.${sub}.read`];
3201
+ if (response === "create") accesses.push(`subs.${sub}.list`);
3202
+ const allowed = await Promise.all(accesses.map(isAllowed));
3203
+ if (allowed.some((value) => !value)) return [];
3204
+ const parentFilter = await service.genFilter("read", { _id: parentId });
3205
+ if (parentFilter === false) return [];
3206
+ const parent = await service.findRawParentDoc({
3207
+ filter: { $and: [{ _id: parentId }, parentFilter] },
3208
+ select: "_id",
3209
+ populate: [],
3210
+ lean: true
3211
+ });
3212
+ if (!parent) return [];
3213
+ const targetFilter = response === "create" ? {} : response === "single" ? { _id: rows[0]?._id } : { _id: { $in: rows.map((row) => row._id) } };
3214
+ const [readFilter, listFilter, select] = await Promise.all([
3215
+ service.genFilter(`subs.${sub}.read`, targetFilter),
3216
+ response === "create" ? service.genFilter(`subs.${sub}.list`) : Promise.resolve({}),
3217
+ service.genQuerySelect("read", null, false, [sub, "sub"])
3218
+ ]);
3219
+ if (readFilter === false || listFilter === false) return [];
3220
+ const visible = filterCollection(filterCollection(rows, listFilter), readFilter);
3221
+ return select ? visible.map((row) => (0, import_utils16.pick)(toObject(row), select.concat("_id"))) : visible;
3222
+ }
2201
3223
  async function listSub(service, id, sub, options) {
2202
3224
  const { filter: ft, select } = options ?? {};
2203
3225
  const filterErrors = validateClientFilter(ft);
2204
3226
  if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
2205
3227
  const parentDoc = await getParentDoc(service, id, sub, null, { access: "read" });
2206
3228
  if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
2207
- let result = (0, import_utils13.get)(parentDoc, sub);
3229
+ let result = (0, import_utils16.get)(parentDoc, sub);
2208
3230
  const [subFilter, subSelect] = await Promise.all([
2209
3231
  service.genFilter(`subs.${sub}.list`, ft),
2210
3232
  service.genQuerySelect("list", select, false, [sub, "sub"])
2211
3233
  ]);
2212
3234
  if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
2213
3235
  result = filterCollection(result, subFilter);
2214
- if (subSelect) result = result.map((v) => (0, import_utils13.pick)(toObject(v), subSelect.concat("_id")));
3236
+ if (subSelect) result = result.map((v) => (0, import_utils16.pick)(toObject(v), subSelect.concat("_id")));
2215
3237
  return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length };
2216
3238
  }
2217
3239
  async function readSub(service, id, sub, subId, options) {
2218
3240
  const { select, populate } = options ?? {};
2219
3241
  const parentDoc = await getParentDoc(service, id, sub, { populate }, { access: "read" });
2220
3242
  if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
2221
- const result = (0, import_utils13.get)(parentDoc, sub);
3243
+ const result = (0, import_utils16.get)(parentDoc, sub);
2222
3244
  const [subFilter, subSelect] = await Promise.all([
2223
3245
  service.genFilter(`subs.${sub}.read`, { _id: subId }),
2224
3246
  service.genQuerySelect("read", select, false, [sub, "sub"])
@@ -2226,28 +3248,27 @@ async function readSub(service, id, sub, subId, options) {
2226
3248
  if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
2227
3249
  let subdoc = findElement(result, subFilter);
2228
3250
  if (!subdoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
2229
- if (subSelect) subdoc = (0, import_utils13.pick)(toObject(subdoc), subSelect.concat(["_id"]));
3251
+ if (subSelect) subdoc = (0, import_utils16.pick)(toObject(subdoc), subSelect.concat(["_id"]));
2230
3252
  return { success: true, kind: "single", code: "success" /* Success */, data: subdoc };
2231
3253
  }
2232
- async function updateSub(service, id, sub, subId, data) {
3254
+ async function updateSub(service, persist, isAllowed, id, sub, subId, data) {
2233
3255
  const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
2234
3256
  if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
2235
- const result = (0, import_utils13.get)(parentDoc, sub);
2236
- const [subFilter, subReadSelect, subUpdateSelect] = await Promise.all([
3257
+ const result = (0, import_utils16.get)(parentDoc, sub);
3258
+ const [subFilter, subUpdateSelect] = await Promise.all([
2237
3259
  service.genFilter(`subs.${sub}.update`, { _id: subId }),
2238
- service.genQuerySelect("read", null, false, [sub, "sub"]),
2239
3260
  service.genQuerySelect("update", null, false, [sub, "sub"])
2240
3261
  ]);
2241
3262
  if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
2242
- let subdoc = findElement(result, subFilter);
3263
+ const subdoc = findElement(result, subFilter);
2243
3264
  if (!subdoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
2244
- const allowedData = (0, import_utils13.pick)(data, subUpdateSelect);
2245
- Object.assign(subdoc, allowedData);
2246
- await parentDoc.save();
2247
- if (subReadSelect) subdoc = (0, import_utils13.pick)(toObject(subdoc), subReadSelect.concat(["_id"]));
2248
- return { success: true, kind: "single", code: "success" /* Success */, data: subdoc };
3265
+ const allowedData = (0, import_utils16.pick)(data, subUpdateSelect);
3266
+ applyUpdate(subdoc, allowedData, subUpdateSelect);
3267
+ await persist(() => parentDoc.save());
3268
+ const visible = await visibleMutationRows(service, isAllowed, parentDoc._id, sub, [subdoc], "single");
3269
+ return { success: true, kind: "single", code: "success" /* Success */, data: visible[0] ?? null };
2249
3270
  }
2250
- async function bulkUpdateSub(service, id, sub, data) {
3271
+ async function bulkUpdateSub(service, persist, isAllowed, id, sub, data) {
2251
3272
  const { maxBulkItems } = service.getRequestComplexity();
2252
3273
  if (data.length > maxBulkItems) {
2253
3274
  return {
@@ -2259,25 +3280,24 @@ async function bulkUpdateSub(service, id, sub, data) {
2259
3280
  }
2260
3281
  const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
2261
3282
  if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
2262
- let result = (0, import_utils13.get)(parentDoc, sub);
2263
- const [subFilter, subReadSelect, subUpdateSelect] = await Promise.all([
3283
+ let result = (0, import_utils16.get)(parentDoc, sub);
3284
+ const [subFilter, subUpdateSelect] = await Promise.all([
2264
3285
  service.genFilter(`subs.${sub}.update`, { _id: { $in: data.map((v) => v._id) } }),
2265
- service.genQuerySelect("read", null, false, [sub, "sub"]),
2266
3286
  service.genQuerySelect("update", null, false, [sub, "sub"])
2267
3287
  ]);
2268
3288
  if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
2269
- result = filterCollection(result, subFilter);
2270
- (0, import_utils13.forEach)(result, (subdoc) => {
3289
+ result = filterCollection(result, subFilter).filter((subdoc) => findElementById(data, subdoc._id));
3290
+ (0, import_utils16.forEach)(result, (subdoc) => {
2271
3291
  const tdata = findElementById(data, subdoc._id);
2272
3292
  if (!tdata) return;
2273
- const allowedData = (0, import_utils13.pick)(tdata, subUpdateSelect);
2274
- Object.assign(subdoc, allowedData);
3293
+ const allowedData = (0, import_utils16.pick)(tdata, subUpdateSelect);
3294
+ applyUpdate(subdoc, allowedData, subUpdateSelect);
2275
3295
  });
2276
- await parentDoc.save();
2277
- if (subReadSelect) result = result.map((v) => (0, import_utils13.pick)(toObject(v), subReadSelect.concat(["_id"])));
3296
+ await persist(() => parentDoc.save());
3297
+ result = await visibleMutationRows(service, isAllowed, parentDoc._id, sub, result, "bulk");
2278
3298
  return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length };
2279
3299
  }
2280
- async function createSub(service, id, sub, data, options) {
3300
+ async function createSub(service, persist, isAllowed, id, sub, data, options) {
2281
3301
  const { addFirst } = options ?? {};
2282
3302
  const { maxBulkItems } = service.getRequestComplexity();
2283
3303
  if (Array.isArray(data) && data.length > maxBulkItems) {
@@ -2290,31 +3310,28 @@ async function createSub(service, id, sub, data, options) {
2290
3310
  }
2291
3311
  const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
2292
3312
  if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
2293
- let result = (0, import_utils13.get)(parentDoc, sub);
2294
- const [subCreateSelect, subReadSelect] = await Promise.all([
2295
- service.genQuerySelect("create", null, false, [sub, "sub"]),
2296
- service.genQuerySelect("read", null, false, [sub, "sub"])
2297
- ]);
2298
- const allowedData = Array.isArray(data) ? data.map((row) => (0, import_utils13.pick)(row, subCreateSelect)) : (0, import_utils13.pick)(data, subCreateSelect);
3313
+ let result = (0, import_utils16.get)(parentDoc, sub);
3314
+ const subCreateSelect = await service.genQuerySelect("create", null, false, [sub, "sub"]);
3315
+ const allowedData = Array.isArray(data) ? data.map((row) => (0, import_utils16.pick)(row, subCreateSelect)) : (0, import_utils16.pick)(data, subCreateSelect);
2299
3316
  if (Array.isArray(allowedData)) {
2300
3317
  addFirst === true ? result.unshift(...allowedData) : result.push(...allowedData);
2301
3318
  } else {
2302
3319
  addFirst === true ? result.unshift(allowedData) : result.push(allowedData);
2303
3320
  }
2304
- await parentDoc.save();
2305
- if (subReadSelect) result = result.map((v) => (0, import_utils13.pick)(toObject(v), subReadSelect.concat(["_id"])));
3321
+ await persist(() => parentDoc.save());
3322
+ result = await visibleMutationRows(service, isAllowed, parentDoc._id, sub, result, "create");
2306
3323
  return { success: true, kind: "list", code: "created" /* Created */, data: result, count: result.length };
2307
3324
  }
2308
- async function deleteSub(service, id, sub, subId) {
3325
+ async function deleteSub(service, persist, id, sub, subId) {
2309
3326
  const parentDoc = await getParentDoc(service, id, sub, null, { access: "update" });
2310
3327
  if (!parentDoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
2311
- const result = (0, import_utils13.get)(parentDoc, sub);
3328
+ const result = (0, import_utils16.get)(parentDoc, sub);
2312
3329
  const subFilter = await service.genFilter(`subs.${sub}.delete`, { _id: subId });
2313
3330
  if (subFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */ };
2314
3331
  const subdoc = findElement(result, subFilter);
2315
3332
  if (!subdoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
2316
3333
  await ("deleteOne" in subdoc ? subdoc.deleteOne?.() : subdoc.remove?.());
2317
- await parentDoc.save();
3334
+ await persist(() => parentDoc.save());
2318
3335
  return { success: true, kind: "single", code: "success" /* Success */, data: subdoc._id };
2319
3336
  }
2320
3337
  async function getParentDoc(service, id, sub, args, options) {
@@ -2449,7 +3466,39 @@ var assertModelDocument = (value, modelName, hookName) => {
2449
3466
  }
2450
3467
  throw new Error(`${hookName} hook for model=${modelName} must return a Mongoose document instance`);
2451
3468
  };
3469
+ var unsetDocPath = (doc, path) => {
3470
+ const segments = path.split(".");
3471
+ let current = doc;
3472
+ const unwrap = (value) => value != null && typeof value === "object" && "_doc" in value ? value._doc : value;
3473
+ current = unwrap(current);
3474
+ for (let i = 0; i < segments.length - 1; i++) {
3475
+ if (current == null || typeof current !== "object") return;
3476
+ if (!Object.prototype.hasOwnProperty.call(current, segments[i])) return;
3477
+ current = unwrap(current[segments[i]]);
3478
+ }
3479
+ if (current != null && typeof current === "object") {
3480
+ delete current[segments[segments.length - 1]];
3481
+ }
3482
+ };
3483
+ var shouldKeepCorrelatedRef = (ref, keep) => {
3484
+ if (keep.has(ref)) return true;
3485
+ const top = ref.split(".")[0];
3486
+ if (keep.has(top)) return true;
3487
+ for (const kept of keep) {
3488
+ if (kept === ref || kept.startsWith(`${ref}.`) || ref.startsWith(`${kept}.`)) return true;
3489
+ }
3490
+ return false;
3491
+ };
2452
3492
  var Service = class extends Base {
3493
+ constructor(req, modelName) {
3494
+ super(req, modelName);
3495
+ this.persist = (operation) => this.getCorrelatedExecState().scheduler.work(operation);
3496
+ this.model = admitModelPersistence(this.createModelAdapter(modelName), this.getCorrelatedExecState().scheduler);
3497
+ this.options = this.getModelRouterOptions(modelName);
3498
+ this.defaults = this.options.defaults || {};
3499
+ this.baseFields = ["_id"];
3500
+ this.baseFieldsExt = this.baseFields.concat(this.options.documentPermissionField);
3501
+ }
2453
3502
  findRawParentDoc(args) {
2454
3503
  return this.model.findOne({
2455
3504
  ...args,
@@ -2487,14 +3536,6 @@ var Service = class extends Base {
2487
3536
  ...extra ?? {}
2488
3537
  });
2489
3538
  }
2490
- constructor(req, modelName) {
2491
- super(req, modelName);
2492
- this.model = this.createModelAdapter(modelName);
2493
- this.options = this.getModelRouterOptions(modelName);
2494
- this.defaults = this.options.defaults || {};
2495
- this.baseFields = ["_id"];
2496
- this.baseFieldsExt = this.baseFields.concat(this.options.documentPermissionField);
2497
- }
2498
3539
  async findOne(filter2, args, options) {
2499
3540
  const filterErrors = this.validateClientFilter(filter2);
2500
3541
  if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
@@ -2502,21 +3543,24 @@ var Service = class extends Base {
2502
3543
  const { skim, includePermissions, access, populateAccess, lean } = this.resolveFindOneOptions(options);
2503
3544
  const { filter: overrideFilter, select: overrideSelect, populate: overridePopulate } = overrides ?? {};
2504
3545
  let parsedFilter;
3546
+ let processedInclude;
2505
3547
  try {
2506
- parsedFilter = await this.parseClientData(filter2);
3548
+ processedInclude = this.processInclude(include);
3549
+ parsedFilter = overrideFilter === false ? false : await this.parseClientData(filter2);
2507
3550
  } catch (error2) {
2508
3551
  const result = this.getClientRequestErrorResult(error2);
2509
3552
  if (result) return result;
2510
3553
  throw error2;
2511
3554
  }
2512
3555
  let [_filter, _select, _populate, allowedSortFields] = await Promise.all([
2513
- overrideFilter || this.genFilter(access, parsedFilter),
3556
+ overrideFilter ?? this.genFilter(access, parsedFilter),
2514
3557
  overrideSelect || this.genQuerySelect(access, select),
2515
3558
  overridePopulate || this.genPopulate(populateAccess || access, populate),
2516
3559
  this.genAllowedFields({}, access, this.baseFieldsExt)
2517
3560
  ]);
2518
- const { includes, includeLocalFields, includePaths } = this.processInclude(include);
2519
- const finalSelect = normalizeSelect(_select).concat(includeLocalFields);
3561
+ const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } = processedInclude;
3562
+ const correlatedOutputPaths = correlatedIncludes.map((entry) => entry.path);
3563
+ const finalSelect = normalizeSelect(_select).concat(includeLocalFields, correlatedReferenceFields);
2520
3564
  const query = {
2521
3565
  filter: _filter,
2522
3566
  select: finalSelect,
@@ -2549,8 +3593,14 @@ var Service = class extends Base {
2549
3593
  originalDocumentSnapshot: toObject(doc),
2550
3594
  resolvedQuery: query
2551
3595
  };
3596
+ const correlatedSnapshots = [(0, import_utils17.cloneDeep)(toObject(doc))];
2552
3597
  try {
2553
3598
  doc = await this.includeDocs(doc, includes);
3599
+ if (correlatedIncludes.length > 0) {
3600
+ const docList = [doc];
3601
+ await this.includeCorrelatedDocs(docList, correlatedIncludes, correlatedSnapshots);
3602
+ doc = docList[0];
3603
+ }
2554
3604
  } catch (error2) {
2555
3605
  const result = this.getClientRequestErrorResult(error2);
2556
3606
  if (result) {
@@ -2568,8 +3618,20 @@ var Service = class extends Base {
2568
3618
  doc = await this.trimOutputFields(
2569
3619
  doc,
2570
3620
  access,
2571
- this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
3621
+ this.baseFieldsExt.concat(includePaths, correlatedOutputPaths, normalizeSelect(overrideSelect))
2572
3622
  );
3623
+ if (correlatedReferenceFields.length > 0) {
3624
+ const keep = /* @__PURE__ */ new Set([
3625
+ ...normalizeSelect(_select),
3626
+ ...normalizeSelect(overrideSelect),
3627
+ ...this.baseFieldsExt,
3628
+ ...includePaths,
3629
+ ...correlatedOutputPaths
3630
+ ]);
3631
+ for (const ref of correlatedReferenceFields) {
3632
+ if (!shouldKeepCorrelatedRef(ref, keep)) unsetDocPath(doc, ref);
3633
+ }
3634
+ }
2573
3635
  if (!includePermissions) doc = this.addEmptyPermissions(doc);
2574
3636
  this.completeOp("findOne", startedAt, "success" /* Success */, _filter);
2575
3637
  return { success: true, kind: "single", code: "success" /* Success */, data: doc, query, context };
@@ -2578,7 +3640,8 @@ var Service = class extends Base {
2578
3640
  const { select, populate, include, overrides } = this.resolveFindByIdArgs(args);
2579
3641
  const { skim, includePermissions, access, populateAccess, lean } = this.resolveFindByIdOptions(options);
2580
3642
  const { select: overrideSelect, populate: overridePopulate, idFilter: overrideIdFilter } = overrides ?? {};
2581
- const filter2 = overrideIdFilter || await this.genIDFilter(id);
3643
+ const filter2 = overrideIdFilter ?? await this.genIDFilter(id);
3644
+ if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query: { filter: filter2 } };
2582
3645
  return this.findOne(
2583
3646
  filter2,
2584
3647
  {
@@ -2600,26 +3663,29 @@ var Service = class extends Base {
2600
3663
  const { skim, includePermissions, includeCount, populateAccess, lean } = this.resolveFindOptions(options);
2601
3664
  const { filter: overrideFilter, select: overrideSelect, populate: overridePopulate } = overrides ?? {};
2602
3665
  let parsedFilter;
3666
+ let processedInclude;
2603
3667
  try {
2604
- parsedFilter = await this.parseClientData(filter2);
3668
+ processedInclude = this.processInclude(include);
3669
+ parsedFilter = overrideFilter === false ? false : await this.parseClientData(filter2);
2605
3670
  } catch (error2) {
2606
3671
  const result = this.getClientRequestErrorResult(error2);
2607
3672
  if (result) return result;
2608
3673
  throw error2;
2609
3674
  }
2610
3675
  const [_filter, _select, _populate, pagination, allowedSortFields] = await Promise.all([
2611
- overrideFilter || this.genFilter("list", parsedFilter),
3676
+ overrideFilter ?? this.genFilter("list", parsedFilter),
2612
3677
  overrideSelect || this.genQuerySelect("list", select),
2613
3678
  overridePopulate || this.genPopulate(populateAccess, populate),
2614
3679
  genPagination({ skip, limit, page, pageSize }, this.options.listHardLimit),
2615
3680
  this.genAllowedFields({}, "list", this.baseFieldsExt)
2616
3681
  ]);
2617
3682
  const finalSelect = normalizeSelect(_select);
2618
- const filteredPopulate = (0, import_utils14.isArray)(finalSelect) && (0, import_utils14.isArray)(_populate) ? _populate.filter((p) => finalSelect.includes(p.path.split(".")[0])) : _populate;
2619
- const { includes, includeLocalFields, includePaths } = this.processInclude(include);
3683
+ const filteredPopulate = (0, import_utils17.isArray)(finalSelect) && (0, import_utils17.isArray)(_populate) ? _populate.filter((p) => finalSelect.includes(p.path.split(".")[0])) : _populate;
3684
+ const { includes, correlatedIncludes, includeLocalFields, includePaths, correlatedReferenceFields } = processedInclude;
3685
+ const correlatedOutputPaths = correlatedIncludes.map((entry) => entry.path);
2620
3686
  const query = {
2621
3687
  filter: _filter,
2622
- select: finalSelect.concat(includeLocalFields),
3688
+ select: finalSelect.concat(includeLocalFields, correlatedReferenceFields),
2623
3689
  populate: filteredPopulate,
2624
3690
  sort,
2625
3691
  ...pagination
@@ -2628,7 +3694,7 @@ var Service = class extends Base {
2628
3694
  sort,
2629
3695
  skip: pagination.skip,
2630
3696
  limit: pagination.limit,
2631
- selectCount: finalSelect.concat(includeLocalFields).length,
3697
+ selectCount: finalSelect.concat(includeLocalFields, correlatedReferenceFields).length,
2632
3698
  populateCount: Array.isArray(filteredPopulate) ? filteredPopulate.length : filteredPopulate ? 1 : 0
2633
3699
  });
2634
3700
  if (_filter === false) {
@@ -2652,9 +3718,13 @@ var Service = class extends Base {
2652
3718
  originalDocumentSnapshot: toObject(doc),
2653
3719
  resolvedQuery: query
2654
3720
  }));
2655
- const _decorate = (0, import_utils14.isFunction)(decorate) ? decorate : (v) => v;
3721
+ const _decorate = (0, import_utils17.isFunction)(decorate) ? decorate : (v) => v;
3722
+ const correlatedSnapshots = docs.map((doc) => (0, import_utils17.cloneDeep)(toObject(doc)));
2656
3723
  try {
2657
3724
  docs = await this.includeDocs(docs, includes);
3725
+ if (correlatedIncludes.length > 0) {
3726
+ docs = await this.includeCorrelatedDocs(docs, correlatedIncludes, correlatedSnapshots);
3727
+ }
2658
3728
  } catch (error2) {
2659
3729
  const result = this.getClientRequestErrorResult(error2);
2660
3730
  if (result) {
@@ -2676,8 +3746,20 @@ var Service = class extends Base {
2676
3746
  doc = await this.trimOutputFields(
2677
3747
  doc,
2678
3748
  "list",
2679
- this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
3749
+ this.baseFieldsExt.concat(includePaths, correlatedOutputPaths, normalizeSelect(overrideSelect))
2680
3750
  );
3751
+ if (correlatedReferenceFields.length > 0) {
3752
+ const keep = /* @__PURE__ */ new Set([
3753
+ ...normalizeSelect(_select),
3754
+ ...normalizeSelect(overrideSelect),
3755
+ ...this.baseFieldsExt,
3756
+ ...includePaths,
3757
+ ...correlatedOutputPaths
3758
+ ]);
3759
+ for (const ref of correlatedReferenceFields) {
3760
+ if (!shouldKeepCorrelatedRef(ref, keep)) unsetDocPath(doc, ref);
3761
+ }
3762
+ }
2681
3763
  doc = await _decorate(doc, contexts[i]);
2682
3764
  if (!includePermissions) doc = this.addEmptyPermissions(doc);
2683
3765
  return doc;
@@ -2753,16 +3835,16 @@ var Service = class extends Base {
2753
3835
  resolvedQuery: resolvedPopulate.length > 0 ? { populate: resolvedPopulate } : {}
2754
3836
  };
2755
3837
  const allowedFields = await this.genAllowedFields(item, "create");
2756
- const allowedData = (0, import_utils14.pick)(item, allowedFields);
3838
+ const allowedData = (0, import_utils17.pick)(item, allowedFields);
2757
3839
  context.allowedFields = allowedFields;
2758
3840
  context.allowedData = allowedData;
2759
3841
  const validated = await this.validate(allowedData, "create", context);
2760
- if ((0, import_utils14.isBoolean)(validated)) {
3842
+ if ((0, import_utils17.isBoolean)(validated)) {
2761
3843
  if (!validated) {
2762
3844
  validationErrors.push({ index, errors: [] });
2763
3845
  return void 0;
2764
3846
  }
2765
- } else if ((0, import_utils14.isArray)(validated)) {
3847
+ } else if ((0, import_utils17.isArray)(validated)) {
2766
3848
  if (validated.length > 0) {
2767
3849
  validationErrors.push({
2768
3850
  index,
@@ -2792,7 +3874,7 @@ var Service = class extends Base {
2792
3874
  contexts[index].preparedData = preparedData;
2793
3875
  return preparedData;
2794
3876
  });
2795
- const _decorate = (0, import_utils14.isFunction)(decorate) ? decorate : (v) => v;
3877
+ const _decorate = (0, import_utils17.isFunction)(decorate) ? decorate : (v) => v;
2796
3878
  const createdDocs = await this.model.create(items);
2797
3879
  const docs = await mapWithConcurrencyLimit(createdDocs, maxBulkConcurrency, async (doc, index) => {
2798
3880
  contexts[index].currentDocument = doc;
@@ -2809,7 +3891,7 @@ var Service = class extends Base {
2809
3891
  }
2810
3892
  if (includeDocPermissions) doc = await this.addDocPermissions(doc, "create", contexts[index]);
2811
3893
  if (includePermissions) doc = await this.addFieldPermissions(doc, "read", contexts[index]);
2812
- if (resolvedPopulate.length > 0) await populateDoc(doc, resolvedPopulate);
3894
+ if (resolvedPopulate.length > 0) await this.persist(() => populateDoc(doc, resolvedPopulate));
2813
3895
  doc = await this.trimOutputFields(doc, "read", this.baseFieldsExt);
2814
3896
  let outputDoc = await _decorate(doc, contexts[index]);
2815
3897
  if (!includePermissions) outputDoc = this.addEmptyPermissions(outputDoc);
@@ -2865,6 +3947,13 @@ var Service = class extends Base {
2865
3947
  data: doc
2866
3948
  };
2867
3949
  }
3950
+ /**
3951
+ * Update at authorized policy paths: admitted leaves preserve omitted siblings;
3952
+ * whole-authorized objects/arrays replace. Shared by updateById and existing-row
3953
+ * upsert. Validation precedes trusted prepare output (not re-filtered); partial
3954
+ * prepare containers preserve omissions. Transform retains explicit document-setter
3955
+ * replacement authority. Send nested client JSON, not literal dotted update keys.
3956
+ */
2868
3957
  async updateOne(filter2, data, args, options, decorate) {
2869
3958
  const filterErrors = this.validateClientFilter(filter2);
2870
3959
  if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
@@ -2872,7 +3961,7 @@ var Service = class extends Base {
2872
3961
  const { skim, includePermissions, populateAccess } = this.resolveUpdateOneOptions(options);
2873
3962
  const { filter: overrideFilter, populate: overridePopulate } = overrides ?? {};
2874
3963
  const [_filter, _populate] = await Promise.all([
2875
- overrideFilter || this.genFilter("update", filter2),
3964
+ overrideFilter ?? this.genFilter("update", filter2),
2876
3965
  overridePopulate || this.genPopulate(populateAccess, populate)
2877
3966
  ]);
2878
3967
  const query = { filter: _filter, populate: _populate };
@@ -2910,16 +3999,16 @@ var Service = class extends Base {
2910
3999
  context.docPermissions = this.getDocPermissions(doc);
2911
4000
  context.currentDocument = doc;
2912
4001
  const allowedFields = await this.genAllowedFields(doc, "update");
2913
- const allowedData = (0, import_utils14.pick)(data, allowedFields);
4002
+ const allowedData = (0, import_utils17.pick)(data, allowedFields);
2914
4003
  context.allowedFields = allowedFields;
2915
4004
  context.allowedData = allowedData;
2916
4005
  const validated = await this.validate(allowedData, "update", context);
2917
- if ((0, import_utils14.isBoolean)(validated)) {
4006
+ if ((0, import_utils17.isBoolean)(validated)) {
2918
4007
  if (!validated) {
2919
4008
  this.completeOp("updateOne", startedAt, "bad_request" /* BadRequest */, _filter);
2920
4009
  return { success: false, kind: "error", code: "bad_request" /* BadRequest */ };
2921
4010
  }
2922
- } else if ((0, import_utils14.isArray)(validated)) {
4011
+ } else if ((0, import_utils17.isArray)(validated)) {
2923
4012
  if (validated.length > 0) {
2924
4013
  this.completeOp("updateOne", startedAt, "bad_request" /* BadRequest */, _filter);
2925
4014
  return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: validated };
@@ -2927,18 +4016,18 @@ var Service = class extends Base {
2927
4016
  }
2928
4017
  const prepared = await this.prepare(allowedData, "update", context);
2929
4018
  context.preparedData = prepared;
2930
- Object.assign(doc, prepared);
4019
+ applyUpdate(doc, prepared, allowedFields);
2931
4020
  context.modifiedPaths = doc.modifiedPaths();
2932
4021
  doc = assertModelDocument(await this.transform(doc, "update", context), this.modelName, "transform");
2933
4022
  context.currentDocument = doc;
2934
- doc = await doc.save();
4023
+ doc = await this.persist(() => doc.save());
2935
4024
  const diffExcludeFields = [this.options.documentPermissionField, "__v"];
2936
4025
  this.asServiceHookContext(context).diff = (d) => {
2937
4026
  context.changes = (0, import_just_diff.diff)(
2938
- (0, import_utils14.omit)(context.originalDocumentSnapshot, diffExcludeFields),
2939
- (0, import_utils14.omit)(d.toObject({ virtuals: false }), diffExcludeFields)
4027
+ (0, import_utils17.omit)(context.originalDocumentSnapshot, diffExcludeFields),
4028
+ (0, import_utils17.omit)(d.toObject({ virtuals: false }), diffExcludeFields)
2940
4029
  ) || [];
2941
- context.modifiedPaths = (0, import_utils14.uniq)(context.changes.map((di) => di.path.length > 0 ? String(di.path[0]) : ""));
4030
+ context.modifiedPaths = (0, import_utils17.uniq)(context.changes.map((di) => di.path.length > 0 ? String(di.path[0]) : ""));
2942
4031
  };
2943
4032
  doc = assertModelDocument(await this.afterPersist(doc, "update", context), this.modelName, "afterPersist");
2944
4033
  context.currentDocument = doc;
@@ -2951,10 +4040,10 @@ var Service = class extends Base {
2951
4040
  }
2952
4041
  if (includeDocPermissions) doc = await this.addDocPermissions(doc, "update", context);
2953
4042
  if (includePermissions) doc = await this.addFieldPermissions(doc, "update", context);
2954
- if (_populate) await populateDoc(doc, _populate);
4043
+ if (_populate) await this.persist(() => populateDoc(doc, _populate));
2955
4044
  doc = await this.trimOutputFields(doc, "read", this.baseFieldsExt);
2956
4045
  let outputDoc = doc;
2957
- if ((0, import_utils14.isFunction)(decorate)) outputDoc = await decorate(outputDoc, context);
4046
+ if ((0, import_utils17.isFunction)(decorate)) outputDoc = await decorate(outputDoc, context);
2958
4047
  if (!includePermissions) outputDoc = this.addEmptyPermissions(outputDoc);
2959
4048
  this.completeOp("updateOne", startedAt, "success" /* Success */, _filter);
2960
4049
  return { success: true, kind: "single", code: "success" /* Success */, data: outputDoc, input: prepared };
@@ -2963,7 +4052,8 @@ var Service = class extends Base {
2963
4052
  const { populate, overrides } = this.resolveUpdateByIdArgs(args);
2964
4053
  const { skim, includePermissions, populateAccess } = this.resolveUpdateByIdOptions(options);
2965
4054
  const { populate: overridePopulate, idFilter: overrideIdFilter } = overrides;
2966
- const filter2 = overrideIdFilter || await this.genIDFilter(id);
4055
+ const filter2 = overrideIdFilter ?? await this.genIDFilter(id);
4056
+ if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query: { filter: filter2 } };
2967
4057
  return this.updateOne(
2968
4058
  filter2,
2969
4059
  data,
@@ -2983,7 +4073,7 @@ var Service = class extends Base {
2983
4073
  const { populate, overrides } = this.resolveUpsertArgs(args);
2984
4074
  const { skim, includePermissions, populateAccess } = this.resolveUpsertOptions(options);
2985
4075
  const { filter: overrideFilter, populate: overridePopulate } = overrides ?? {};
2986
- const _filter = await (overrideFilter || this.genFilter("update", filter2));
4076
+ const _filter = await (overrideFilter ?? this.genFilter("update", filter2));
2987
4077
  const query = { filter: _filter };
2988
4078
  const startedAt = this.beginOp("upsert", _filter);
2989
4079
  if (_filter === false) {
@@ -3043,7 +4133,9 @@ var Service = class extends Base {
3043
4133
  resolvedQuery: query
3044
4134
  };
3045
4135
  await this.beforeDelete(doc, context);
3046
- await ("deleteOne" in doc ? doc.deleteOne() : doc.remove());
4136
+ await this.persist(
4137
+ () => "deleteOne" in doc ? doc.deleteOne() : doc.remove()
4138
+ );
3047
4139
  context.finalDocumentSnapshot = toObject(doc);
3048
4140
  await this.afterDelete(doc, context);
3049
4141
  this.completeOp("delete", startedAt, "success" /* Success */, filter2);
@@ -3054,6 +4146,7 @@ var Service = class extends Base {
3054
4146
  if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
3055
4147
  const { access, includeId } = this.resolveExistsOptions(options);
3056
4148
  filter2 = await this.genFilter(access, filter2);
4149
+ if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query: { filter: filter2 } };
3057
4150
  const result = await this.model.exists(filter2);
3058
4151
  return {
3059
4152
  success: true,
@@ -3106,6 +4199,30 @@ var Service = class extends Base {
3106
4199
  if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
3107
4200
  return { success: true, kind: "single", code: "success" /* Success */, data: await this.model.countDocuments(filter2), query };
3108
4201
  }
4202
+ /**
4203
+ * Trusted count for correlated includes (ACI-03): the caller already
4204
+ * resolved parent references, revalidated expanded operands, and applied
4205
+ * explicit `count` access via `genFilter`. This skips client-filter
4206
+ * validation and subquery parsing so substituted parent data stays inert.
4207
+ */
4208
+ async countTrusted(authorizedFilter) {
4209
+ const query = { filter: authorizedFilter };
4210
+ if (authorizedFilter === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
4211
+ return {
4212
+ success: true,
4213
+ kind: "single",
4214
+ code: "success" /* Success */,
4215
+ data: await this.model.countDocuments(authorizedFilter),
4216
+ query
4217
+ };
4218
+ }
4219
+ /**
4220
+ * Group target document IDs by original parent-key aliases using one aggregate.
4221
+ * Resolves the requested row policy (legacy count includes pass `count`), casts
4222
+ * authorized filters/foreign operands with the target schema, and deduplicates IDs
4223
+ * independently of pagination. Cast failures return BadRequest; false policy returns
4224
+ * Forbidden. Casting does not execute countDocuments middleware/plugins.
4225
+ */
3109
4226
  async countByFieldValues(foreignField, values, filter2 = {}, access = "list") {
3110
4227
  if (!isValidFieldPath(foreignField)) {
3111
4228
  return {
@@ -3117,14 +4234,32 @@ var Service = class extends Base {
3117
4234
  }
3118
4235
  const filterErrors = this.validateClientFilter(filter2);
3119
4236
  if (filterErrors.length > 0) return { success: false, kind: "error", code: "bad_request" /* BadRequest */, errors: filterErrors };
3120
- const uniqueValues = (0, import_utils14.uniqBy)(values, (value) => String(value));
4237
+ const uniqueValues = (0, import_utils17.uniqBy)(values, (value) => String(value));
3121
4238
  filter2 = await this.genFilter(access, {
3122
4239
  ...filter2,
3123
4240
  [foreignField]: { $in: uniqueValues }
3124
4241
  });
3125
4242
  const query = { filter: filter2 };
3126
4243
  if (filter2 === false) return { success: false, kind: "error", code: "forbidden" /* Forbidden */, query };
3127
- const matchFilter = filter2;
4244
+ let matchFilter = filter2;
4245
+ let castValues = uniqueValues;
4246
+ try {
4247
+ if (this.model.castFilter) {
4248
+ matchFilter = this.model.castFilter(matchFilter);
4249
+ const foreignFilter = this.model.castFilter({ [foreignField]: { $in: uniqueValues } });
4250
+ castValues = foreignFilter[foreignField]?.$in;
4251
+ if (!Array.isArray(castValues) || castValues.length !== uniqueValues.length) {
4252
+ throw new Error("Foreign field query casting must retain the join operands");
4253
+ }
4254
+ }
4255
+ } catch {
4256
+ return {
4257
+ success: false,
4258
+ kind: "error",
4259
+ code: "bad_request" /* BadRequest */,
4260
+ errors: [{ detail: "Invalid grouped include count filter or foreign-key value" }]
4261
+ };
4262
+ }
3128
4263
  const rows = await this.model.aggregate([
3129
4264
  { $match: matchFilter },
3130
4265
  {
@@ -3135,12 +4270,20 @@ var Service = class extends Base {
3135
4270
  }
3136
4271
  },
3137
4272
  { $unwind: "$foreignValues" },
3138
- { $match: { foreignValues: { $in: uniqueValues } } },
4273
+ { $match: { foreignValues: { $in: castValues } } },
3139
4274
  { $group: { _id: "$foreignValues", documentIds: { $addToSet: "$_id" } } }
3140
4275
  ]);
3141
- const counts = /* @__PURE__ */ new Map();
4276
+ const grouped = /* @__PURE__ */ new Map();
3142
4277
  for (const row of rows) {
3143
- counts.set(String(row._id), new Set(row.documentIds.map((id) => String(id))));
4278
+ const key = String(row._id);
4279
+ const ids = grouped.get(key) ?? /* @__PURE__ */ new Set();
4280
+ for (const id of row.documentIds) ids.add(String(id));
4281
+ grouped.set(key, ids);
4282
+ }
4283
+ const counts = /* @__PURE__ */ new Map();
4284
+ for (let i = 0; i < uniqueValues.length; i++) {
4285
+ const ids = grouped.get(String(castValues[i]));
4286
+ if (ids) counts.set(String(uniqueValues[i]), ids);
3144
4287
  }
3145
4288
  return { success: true, kind: "single", code: "success" /* Success */, data: counts, query };
3146
4289
  }
@@ -3193,7 +4336,7 @@ var Service = class extends Base {
3193
4336
  return resolveExistsOptions(this, options);
3194
4337
  }
3195
4338
  async getFieldPermissionAccess(ids) {
3196
- const uniqueIds = (0, import_utils14.compact)((0, import_utils14.uniqBy)(ids, (id) => String(id)).map((id) => String(id)));
4339
+ const uniqueIds = (0, import_utils17.compact)((0, import_utils17.uniqBy)(ids, (id) => String(id)).map((id) => String(id)));
3197
4340
  if (uniqueIds.length === 0) {
3198
4341
  return {
3199
4342
  readIds: /* @__PURE__ */ new Set(),
@@ -3219,17 +4362,60 @@ var Service = class extends Base {
3219
4362
  async readSub(id, sub, subId, options) {
3220
4363
  return readSub(this, id, sub, subId, options);
3221
4364
  }
4365
+ /**
4366
+ * Apply authorized policy-path updates, preserving protected siblings, then save.
4367
+ * Output requires post-save parent read and subdocument read operation/row access;
4368
+ * hidden output is successful `data: null` (direct/root-entry 200), not a failed write.
4369
+ * Visible output uses read fields plus `_id`. Do not retry solely for null output.
4370
+ */
3222
4371
  async updateSub(id, sub, subId, data) {
3223
- return updateSub(this, id, sub, subId, data);
4372
+ return updateSub(
4373
+ this,
4374
+ this.persist,
4375
+ (access) => this.req.macl.isAllowed(this.modelName, access),
4376
+ id,
4377
+ sub,
4378
+ subId,
4379
+ data
4380
+ );
3224
4381
  }
4382
+ /**
4383
+ * Save authorized targeted updates, then return readable targets in stored order.
4384
+ * Requires parent/subdocument read access for output, not subdocument list access;
4385
+ * uses read fields plus `_id`. Hidden output succeeds with `data: [], count: 0`
4386
+ * (direct/root-entry 200); count measures visible rows, not all updated rows.
4387
+ */
3225
4388
  async bulkUpdateSub(id, sub, data) {
3226
- return bulkUpdateSub(this, id, sub, (0, import_utils14.castArray)(data));
4389
+ return bulkUpdateSub(
4390
+ this,
4391
+ this.persist,
4392
+ (access) => this.req.macl.isAllowed(this.modelName, access),
4393
+ id,
4394
+ sub,
4395
+ (0, import_utils17.castArray)(data)
4396
+ );
3227
4397
  }
4398
+ /**
4399
+ * Append/prepend admitted rows, then return the visible full array (including when
4400
+ * input is empty). Output requires post-save parent read and subdocument list AND
4401
+ * read guards/row filters; fields use read projection plus `_id`.
4402
+ * Hidden output succeeds with code `created`, `data: [], count: 0`
4403
+ * (direct/root-entry 201). Visible count/order follow filtered stored rows.
4404
+ * Empty output does not mean the write failed; addFirst is a service option.
4405
+ */
3228
4406
  async createSub(id, sub, data, options) {
3229
- return createSub(this, id, sub, data, options);
4407
+ return createSub(
4408
+ this,
4409
+ this.persist,
4410
+ (access) => this.req.macl.isAllowed(this.modelName, access),
4411
+ id,
4412
+ sub,
4413
+ data,
4414
+ options
4415
+ );
3230
4416
  }
3231
4417
  async deleteSub(id, sub, subId) {
3232
- return deleteSub(this, id, sub, subId);
4418
+ return deleteSub(this, this.persist, id, sub, subId);
3233
4419
  }
3234
4420
  async getParentDoc(id, sub, args, options) {
3235
4421
  return getParentDoc(this, id, sub, args, options);
@@ -3237,7 +4423,7 @@ var Service = class extends Base {
3237
4423
  };
3238
4424
 
3239
4425
  // src/services/public-service.ts
3240
- var import_utils15 = require("@web-ts-toolkit/utils");
4426
+ var import_utils18 = require("@web-ts-toolkit/utils");
3241
4427
  var PublicService = class extends Service {
3242
4428
  async _list(filter2, args, options) {
3243
4429
  const { select, populate, include, sort, skip, limit, page, pageSize, tasks } = this.resolvePublicListArgs(args);
@@ -3276,7 +4462,7 @@ var PublicService = class extends Service {
3276
4462
  let d = toObject(doc);
3277
4463
  d = await this.decorate(d, "create", context);
3278
4464
  d = this.runTasks(d, tasks);
3279
- if (select) d = (0, import_utils15.pick)(d, [...normalizeSelect(select), ...this.baseFieldsExt]);
4465
+ if (select) d = (0, import_utils18.pick)(d, [...normalizeSelect(select), ...this.baseFieldsExt]);
3280
4466
  return d;
3281
4467
  }
3282
4468
  );
@@ -3303,6 +4489,7 @@ var PublicService = class extends Service {
3303
4489
  },
3304
4490
  { skim, includePermissions, access, populateAccess, lean }
3305
4491
  );
4492
+ if (!result.success && (result.code === "bad_request" /* BadRequest */ || result.code === "forbidden" /* Forbidden */)) return result;
3306
4493
  if (tryList && (!result.success || !result.data)) {
3307
4494
  const listAllowed = await this.req.macl.isAllowed(this.modelName, "list");
3308
4495
  if (!listAllowed) {
@@ -3343,6 +4530,7 @@ var PublicService = class extends Service {
3343
4530
  },
3344
4531
  { skim, includePermissions, access, populateAccess, lean }
3345
4532
  );
4533
+ if (!result.success && (result.code === "bad_request" /* BadRequest */ || result.code === "forbidden" /* Forbidden */)) return result;
3346
4534
  if (tryList && (!result.success || !result.data)) {
3347
4535
  const listAllowed = await this.req.macl.isAllowed(this.modelName, "list");
3348
4536
  if (!listAllowed) {
@@ -3381,8 +4569,8 @@ var PublicService = class extends Service {
3381
4569
  let d = toObject(doc);
3382
4570
  d = await this.decorate(d, "update", context);
3383
4571
  d = this.runTasks(d, tasks);
3384
- if (select) d = (0, import_utils15.pick)(d, [...normalizeSelect(select), ...this.baseFieldsExt]);
3385
- else if (!returningAll) d = (0, import_utils15.pick)(d, [...Object.keys(data), "_id"]);
4572
+ if (select) d = (0, import_utils18.pick)(d, [...normalizeSelect(select), ...this.baseFieldsExt]);
4573
+ else if (!returningAll) d = (0, import_utils18.pick)(d, [...Object.keys(data), "_id"]);
3386
4574
  return d;
3387
4575
  }
3388
4576
  );
@@ -3391,6 +4579,11 @@ var PublicService = class extends Service {
3391
4579
  }
3392
4580
  return result;
3393
4581
  }
4582
+ /**
4583
+ * With an _id, check update-policy existence then update: terminal denial returns
4584
+ * Forbidden, an allowed miss remains Unauthorized, and neither creates a row.
4585
+ * Without an _id, use the create path.
4586
+ */
3394
4587
  async _upsert(data, args, options) {
3395
4588
  const idKey = this.getIdentifier();
3396
4589
  if (idKey !== "_id") {
@@ -3504,7 +4697,7 @@ var PublicService = class extends Service {
3504
4697
  };
3505
4698
 
3506
4699
  // src/services/data-service.ts
3507
- var import_utils16 = require("@web-ts-toolkit/utils");
4700
+ var import_utils19 = require("@web-ts-toolkit/utils");
3508
4701
  var DataService = class {
3509
4702
  constructor(req, dataName) {
3510
4703
  this.req = req;
@@ -3526,7 +4719,7 @@ var DataService = class {
3526
4719
  let doc = await findElement(this.data, _filter);
3527
4720
  if (!doc) return { success: false, kind: "error", code: "not_found" /* NotFound */, query };
3528
4721
  doc = await this.trimOutputFields(doc, access);
3529
- if (_select.length > 0) doc = (0, import_utils16.pick)(doc, _select);
4722
+ if (_select.length > 0) doc = (0, import_utils19.pick)(doc, _select);
3530
4723
  return { success: true, kind: "single", code: "success" /* Success */, data: doc, query };
3531
4724
  }
3532
4725
  async findById(id, args, options) {
@@ -3566,13 +4759,13 @@ var DataService = class {
3566
4759
  const totalCount = docs.length;
3567
4760
  if (sort) {
3568
4761
  const normalizedSort = normalizeSortForOrderBy(sort);
3569
- docs = (0, import_utils16.orderBy)(docs, normalizedSort.fields, normalizedSort.orders);
4762
+ docs = (0, import_utils19.orderBy)(docs, normalizedSort.fields, normalizedSort.orders);
3570
4763
  }
3571
4764
  const pagedDocs = docs.slice(query.skip, query.limit && query.skip + query.limit);
3572
4765
  const { maxHookConcurrency } = this.getRequestComplexity();
3573
4766
  const trimmed = await mapWithConcurrencyLimit(pagedDocs, maxHookConcurrency, async (doc) => {
3574
4767
  doc = await this.trimOutputFields(doc, "list");
3575
- if (_select.length > 0) doc = (0, import_utils16.pick)(doc, _select);
4768
+ if (_select.length > 0) doc = (0, import_utils19.pick)(doc, _select);
3576
4769
  return doc;
3577
4770
  });
3578
4771
  if (!hasExplicitLimit && trimmed.length === totalCount) {
@@ -3621,30 +4814,65 @@ var DataService = class {
3621
4814
  };
3622
4815
 
3623
4816
  // src/processors.ts
3624
- var import_utils17 = require("@web-ts-toolkit/utils");
4817
+ var import_utils20 = require("@web-ts-toolkit/utils");
3625
4818
  var UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
3626
- function failUnsafeSegment(path) {
4819
+ var DEFAULT_MAX_SLICE = 1e3;
4820
+ var DEFAULT_MASK_REPLACEMENT = "***";
4821
+ function failUnsafeSegment(path, fn = "copyAndDepopulate") {
3627
4822
  throw new Error(
3628
- `copyAndDepopulate: refusing path '${path}' because it contains an unsafe segment (__proto__, prototype, or constructor).`
4823
+ `${fn}: refusing path '${path}' because it contains an unsafe segment (__proto__, prototype, or constructor).`
3629
4824
  );
3630
4825
  }
3631
4826
  function failMissingId(path) {
3632
4827
  throw new Error(`copyAndDepopulate: path '${path}' resolved to a record missing the configured id field.`);
3633
4828
  }
3634
- function assertSafePath(path) {
4829
+ function failInvalidSlice(value, field, fn) {
4830
+ throw new Error(`${fn}: '${field}' must be a non-negative safe integer, received ${String(value)}.`);
4831
+ }
4832
+ function assertSafePath(path, fn = "copyAndDepopulate") {
3635
4833
  const segs = path.split(".");
3636
4834
  for (const seg of segs) {
3637
4835
  if (seg.length === 0) continue;
3638
4836
  if (UNSAFE_PATH_KEYS.has(seg)) {
3639
- failUnsafeSegment(path);
4837
+ failUnsafeSegment(path, fn);
3640
4838
  }
3641
4839
  }
3642
4840
  }
4841
+ function assertNonNegativeSafeInteger(value, field, fn) {
4842
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
4843
+ failInvalidSlice(value, field, fn);
4844
+ }
4845
+ }
4846
+ function resolveLeafParents(obj, src) {
4847
+ const segs = src.split(".");
4848
+ let targets = [obj];
4849
+ for (let ind = 0; ind < segs.length - 1; ind += 1) {
4850
+ const seg = segs[ind];
4851
+ targets = targets.reduce((ret, target) => {
4852
+ if (!(0, import_utils20.isPlainObject)(target)) return ret;
4853
+ const targetObject = target;
4854
+ const next = targetObject[seg];
4855
+ if ((0, import_utils20.isArray)(next)) {
4856
+ for (const item of next) {
4857
+ ret.push(item);
4858
+ }
4859
+ } else if (next !== null && next !== void 0) ret.push(next);
4860
+ return ret;
4861
+ }, []);
4862
+ }
4863
+ const leaf = segs[segs.length - 1];
4864
+ const pairs = [];
4865
+ (0, import_utils20.forEach)(targets, (target) => {
4866
+ if (!(0, import_utils20.isPlainObject)(target)) return;
4867
+ pairs.push({ parent: target, leaf });
4868
+ });
4869
+ return pairs;
4870
+ }
3643
4871
  var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
3644
4872
  const mutable = options.mutable !== false;
3645
4873
  const idField = options.idField ?? "_id";
3646
- const obj = mutable ? docObject : (0, import_utils17.cloneDeep)(docObject);
3647
- (0, import_utils17.forEach)(Array.isArray(operations) ? operations : [], (op) => {
4874
+ const obj = mutable ? docObject : (0, import_utils20.cloneDeep)(docObject);
4875
+ (0, import_utils20.forEach)(Array.isArray(operations) ? operations : [], (op) => {
3648
4876
  if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
3649
4877
  const src = op.src;
3650
4878
  const dest = op.dest;
@@ -3653,19 +4881,19 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
3653
4881
  assertSafePath(dest);
3654
4882
  let targets = [obj];
3655
4883
  const segs = src.split(".");
3656
- (0, import_utils17.forEach)(segs, (seg, ind) => {
4884
+ (0, import_utils20.forEach)(segs, (seg, ind) => {
3657
4885
  if (segs.length === ind + 1) {
3658
- (0, import_utils17.forEach)(targets, (target) => {
3659
- if (!(0, import_utils17.isPlainObject)(target)) return;
4886
+ (0, import_utils20.forEach)(targets, (target) => {
4887
+ if (!(0, import_utils20.isPlainObject)(target)) return;
3660
4888
  const targetObject = target;
3661
4889
  const leaf = targetObject[seg];
3662
4890
  const sourceForDest = leaf;
3663
4891
  let depopulated;
3664
- if ((0, import_utils17.isArray)(leaf)) {
4892
+ if ((0, import_utils20.isArray)(leaf)) {
3665
4893
  if (leaf.length === 0) {
3666
4894
  depopulated = [];
3667
- } else if (leaf.every((item) => (0, import_utils17.isPlainObject)(item))) {
3668
- depopulated = (0, import_utils17.map)(leaf, idField).map((id, i) => {
4895
+ } else if (leaf.every((item) => (0, import_utils20.isPlainObject)(item))) {
4896
+ depopulated = (0, import_utils20.map)(leaf, idField).map((id, i) => {
3669
4897
  if (id === void 0) {
3670
4898
  failMissingId(`${src}[${i}]`);
3671
4899
  }
@@ -3674,8 +4902,8 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
3674
4902
  } else {
3675
4903
  return;
3676
4904
  }
3677
- } else if ((0, import_utils17.isPlainObject)(leaf)) {
3678
- const id = (0, import_utils17.get)(leaf, idField);
4905
+ } else if ((0, import_utils20.isPlainObject)(leaf)) {
4906
+ const id = (0, import_utils20.get)(leaf, idField);
3679
4907
  if (id === void 0) {
3680
4908
  failMissingId(src);
3681
4909
  }
@@ -3683,15 +4911,15 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
3683
4911
  } else {
3684
4912
  return;
3685
4913
  }
3686
- (0, import_utils17.set)(targetObject, dest, sourceForDest);
3687
- (0, import_utils17.set)(targetObject, seg, depopulated);
4914
+ (0, import_utils20.set)(targetObject, dest, sourceForDest);
4915
+ (0, import_utils20.set)(targetObject, seg, depopulated);
3688
4916
  });
3689
4917
  } else {
3690
4918
  targets = targets.reduce((ret, target) => {
3691
- if (!(0, import_utils17.isPlainObject)(target)) return ret;
4919
+ if (!(0, import_utils20.isPlainObject)(target)) return ret;
3692
4920
  const targetObject = target;
3693
4921
  const next = targetObject[seg];
3694
- if ((0, import_utils17.isArray)(next)) {
4922
+ if ((0, import_utils20.isArray)(next)) {
3695
4923
  for (const item of next) {
3696
4924
  ret.push(item);
3697
4925
  }
@@ -3703,6 +4931,125 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
3703
4931
  });
3704
4932
  return obj;
3705
4933
  };
4934
+ var copyPaths = (docObject, operations, options = { mutable: true }) => {
4935
+ const mutable = options.mutable !== false;
4936
+ const obj = mutable ? docObject : (0, import_utils20.cloneDeep)(docObject);
4937
+ (0, import_utils20.forEach)(Array.isArray(operations) ? operations : [], (op) => {
4938
+ if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
4939
+ const src = op.src;
4940
+ const dest = op.dest;
4941
+ if (src.length === 0 || dest.length === 0) return;
4942
+ assertSafePath(src, "copyPaths");
4943
+ assertSafePath(dest, "copyPaths");
4944
+ if (src === dest) return;
4945
+ (0, import_utils20.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
4946
+ if (!(0, import_utils20.hasOwn)(parent, leaf)) return;
4947
+ (0, import_utils20.set)(parent, dest, (0, import_utils20.cloneDeep)(parent[leaf]));
4948
+ });
4949
+ });
4950
+ return obj;
4951
+ };
4952
+ var movePaths = (docObject, operations, options = { mutable: true }) => {
4953
+ const mutable = options.mutable !== false;
4954
+ const obj = mutable ? docObject : (0, import_utils20.cloneDeep)(docObject);
4955
+ (0, import_utils20.forEach)(Array.isArray(operations) ? operations : [], (op) => {
4956
+ if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
4957
+ const src = op.src;
4958
+ const dest = op.dest;
4959
+ if (src.length === 0 || dest.length === 0) return;
4960
+ assertSafePath(src, "movePaths");
4961
+ assertSafePath(dest, "movePaths");
4962
+ if (src === dest) return;
4963
+ (0, import_utils20.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
4964
+ if (!(0, import_utils20.hasOwn)(parent, leaf)) return;
4965
+ const value = (0, import_utils20.cloneDeep)(parent[leaf]);
4966
+ (0, import_utils20.set)(parent, dest, value);
4967
+ if (dest === leaf || dest.startsWith(`${leaf}.`)) return;
4968
+ delete parent[leaf];
4969
+ });
4970
+ });
4971
+ return obj;
4972
+ };
4973
+ var sliceArrays = (docObject, operations, options = { mutable: true, maxSlice: DEFAULT_MAX_SLICE }) => {
4974
+ const mutable = options.mutable !== false;
4975
+ const maxSlice = options.maxSlice ?? DEFAULT_MAX_SLICE;
4976
+ assertNonNegativeSafeInteger(maxSlice, "maxSlice", "sliceArrays");
4977
+ const obj = mutable ? docObject : (0, import_utils20.cloneDeep)(docObject);
4978
+ (0, import_utils20.forEach)(Array.isArray(operations) ? operations : [], (op) => {
4979
+ if (!op || typeof op.src !== "string") return;
4980
+ const src = op.src;
4981
+ if (src.length === 0) return;
4982
+ assertSafePath(src, "sliceArrays");
4983
+ const rawDest = typeof op.dest === "undefined" ? void 0 : op.dest;
4984
+ if (typeof rawDest !== "undefined") {
4985
+ if (typeof rawDest !== "string" || rawDest.length === 0) return;
4986
+ assertSafePath(rawDest, "sliceArrays");
4987
+ }
4988
+ const skip = op.skip ?? 0;
4989
+ assertNonNegativeSafeInteger(skip, "skip", "sliceArrays");
4990
+ let limit;
4991
+ if (typeof op.limit !== "undefined") {
4992
+ assertNonNegativeSafeInteger(op.limit, "limit", "sliceArrays");
4993
+ limit = Math.min(op.limit, maxSlice);
4994
+ }
4995
+ (0, import_utils20.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
4996
+ const leafValue = parent[leaf];
4997
+ if (!(0, import_utils20.isArray)(leafValue)) return;
4998
+ const end = typeof limit === "undefined" ? void 0 : skip + limit;
4999
+ const window = leafValue.slice(skip, end);
5000
+ if (typeof rawDest === "undefined") {
5001
+ (0, import_utils20.set)(parent, leaf, window);
5002
+ } else {
5003
+ (0, import_utils20.set)(parent, rawDest, window);
5004
+ }
5005
+ });
5006
+ });
5007
+ return obj;
5008
+ };
5009
+ var countPaths = (docObject, operations, options = { mutable: true }) => {
5010
+ const mutable = options.mutable !== false;
5011
+ const obj = mutable ? docObject : (0, import_utils20.cloneDeep)(docObject);
5012
+ (0, import_utils20.forEach)(Array.isArray(operations) ? operations : [], (op) => {
5013
+ if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
5014
+ const src = op.src;
5015
+ const dest = op.dest;
5016
+ if (src.length === 0 || dest.length === 0) return;
5017
+ assertSafePath(src, "countPaths");
5018
+ assertSafePath(dest, "countPaths");
5019
+ (0, import_utils20.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
5020
+ if (!(0, import_utils20.hasOwn)(parent, leaf)) return;
5021
+ const leafValue = parent[leaf];
5022
+ let count;
5023
+ if ((0, import_utils20.isArray)(leafValue)) {
5024
+ count = leafValue.length;
5025
+ } else if ((0, import_utils20.isPlainObject)(leafValue)) {
5026
+ count = Object.keys(leafValue).length;
5027
+ } else if (leafValue === null || typeof leafValue === "undefined") {
5028
+ count = 0;
5029
+ } else {
5030
+ count = 1;
5031
+ }
5032
+ (0, import_utils20.set)(parent, dest, count);
5033
+ });
5034
+ });
5035
+ return obj;
5036
+ };
5037
+ var maskPaths = (docObject, operations, options = { mutable: true }) => {
5038
+ const mutable = options.mutable !== false;
5039
+ const obj = mutable ? docObject : (0, import_utils20.cloneDeep)(docObject);
5040
+ (0, import_utils20.forEach)(Array.isArray(operations) ? operations : [], (op) => {
5041
+ if (!op || typeof op.src !== "string") return;
5042
+ const src = op.src;
5043
+ if (src.length === 0) return;
5044
+ assertSafePath(src, "maskPaths");
5045
+ const replacement = Object.prototype.hasOwnProperty.call(op, "replacement") ? op.replacement : DEFAULT_MASK_REPLACEMENT;
5046
+ (0, import_utils20.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
5047
+ if (!(0, import_utils20.hasOwn)(parent, leaf)) return;
5048
+ (0, import_utils20.set)(parent, leaf, replacement);
5049
+ });
5050
+ });
5051
+ return obj;
5052
+ };
3706
5053
 
3707
5054
  // src/symbols.ts
3708
5055
  var MIDDLEWARE = /* @__PURE__ */ Symbol.for("@web-ts-toolkit/access-router/middleware");
@@ -3742,10 +5089,10 @@ var Cache = class {
3742
5089
  };
3743
5090
 
3744
5091
  // src/core-shared.ts
3745
- var import_utils19 = require("@web-ts-toolkit/utils");
5092
+ var import_utils22 = require("@web-ts-toolkit/utils");
3746
5093
 
3747
5094
  // src/permission.ts
3748
- var import_utils18 = require("@web-ts-toolkit/utils");
5095
+ var import_utils21 = require("@web-ts-toolkit/utils");
3749
5096
  var Permission = class {
3750
5097
  constructor(permissions) {
3751
5098
  this.permissions = { ...permissions };
@@ -3761,7 +5108,7 @@ var Permission = class {
3761
5108
  return [permissionOrPermissions].concat(permissions);
3762
5109
  }
3763
5110
  hasKey(permission) {
3764
- return (0, import_utils18.hasOwn)(this.permissions, permission);
5111
+ return (0, import_utils21.hasOwn)(this.permissions, permission);
3765
5112
  }
3766
5113
  has(permission) {
3767
5114
  return this.permissions[permission] === true;
@@ -3781,10 +5128,10 @@ var permission_default = Permission;
3781
5128
 
3782
5129
  // src/core-shared.ts
3783
5130
  function isAndFilter(filter2) {
3784
- return !!filter2 && (0, import_utils19.isPlainObject)(filter2) && Object.keys(filter2).length === 1 && (0, import_utils19.isArray)(filter2.$and);
5131
+ return !!filter2 && (0, import_utils22.isPlainObject)(filter2) && Object.keys(filter2).length === 1 && (0, import_utils22.isArray)(filter2.$and);
3785
5132
  }
3786
5133
  function isMergeableClause(filter2) {
3787
- return !!filter2 && (0, import_utils19.isPlainObject)(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
5134
+ return !!filter2 && (0, import_utils22.isPlainObject)(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
3788
5135
  }
3789
5136
  function optimizeAndFilter(clausesInput) {
3790
5137
  const clauses = [];
@@ -3799,10 +5146,10 @@ function optimizeAndFilter(clausesInput) {
3799
5146
  }
3800
5147
  }
3801
5148
  const dedupeKey = (value) => {
3802
- if ((0, import_utils19.isArray)(value)) {
5149
+ if ((0, import_utils22.isArray)(value)) {
3803
5150
  return `[${value.map(dedupeKey).join(",")}]`;
3804
5151
  }
3805
- if ((0, import_utils19.isPlainObject)(value)) {
5152
+ if ((0, import_utils22.isPlainObject)(value)) {
3806
5153
  return `{${Object.keys(value).sort().map((key) => `${key}:${dedupeKey(value[key])}`).join(",")}}`;
3807
5154
  }
3808
5155
  return JSON.stringify(value);
@@ -3826,7 +5173,7 @@ function optimizeAndFilter(clausesInput) {
3826
5173
  }
3827
5174
  let canMerge = true;
3828
5175
  for (const [key, value] of Object.entries(clause)) {
3829
- if ((0, import_utils19.hasOwn)(mergedClause, key) && !(0, import_utils19.isEqual)(mergedClause[key], value)) {
5176
+ if ((0, import_utils22.hasOwn)(mergedClause, key) && !(0, import_utils22.isEqual)(mergedClause[key], value)) {
3830
5177
  canMerge = false;
3831
5178
  break;
3832
5179
  }
@@ -3837,24 +5184,24 @@ function optimizeAndFilter(clausesInput) {
3837
5184
  }
3838
5185
  Object.assign(mergedClause, clause);
3839
5186
  }
3840
- const finalClauses = (0, import_utils19.isEmpty)(mergedClause) ? remainingClauses : [mergedClause, ...remainingClauses];
5187
+ const finalClauses = (0, import_utils22.isEmpty)(mergedClause) ? remainingClauses : [mergedClause, ...remainingClauses];
3841
5188
  if (finalClauses.length === 0) return null;
3842
5189
  if (finalClauses.length === 1) return finalClauses[0];
3843
5190
  return { $and: finalClauses };
3844
5191
  }
3845
5192
  function normalizeFilter(filter2) {
3846
5193
  if (filter2 === false) return false;
3847
- if (!filter2 || !(0, import_utils19.isPlainObject)(filter2) || (0, import_utils19.isEmpty)(filter2)) return null;
5194
+ if (!filter2 || !(0, import_utils22.isPlainObject)(filter2) || (0, import_utils22.isEmpty)(filter2)) return null;
3848
5195
  if (!isAndFilter(filter2)) {
3849
5196
  return filter2;
3850
5197
  }
3851
5198
  return optimizeAndFilter(filter2.$and);
3852
5199
  }
3853
5200
  async function resolveIdentifierFilter(req, idField, resolveIdFilter, id) {
3854
- if ((0, import_utils19.isFunction)(resolveIdFilter)) {
5201
+ if ((0, import_utils22.isFunction)(resolveIdFilter)) {
3855
5202
  return await resolveIdFilter.call(req, id);
3856
5203
  }
3857
- if ((0, import_utils19.isString)(idField)) {
5204
+ if ((0, import_utils22.isString)(idField)) {
3858
5205
  return { [idField]: id };
3859
5206
  }
3860
5207
  return { _id: id };
@@ -3870,12 +5217,12 @@ async function resolveAccessFilter({
3870
5217
  }) {
3871
5218
  let nextFilter = normalizeFilter(filter2);
3872
5219
  const overrideFilterFn = getOption(`overrideFilter.${access}`, null);
3873
- if (nextFilter !== false && (0, import_utils19.isFunction)(overrideFilterFn)) {
5220
+ if (nextFilter !== false && (0, import_utils22.isFunction)(overrideFilterFn)) {
3874
5221
  nextFilter = normalizeFilter(await overrideFilterFn.call(req, nextFilter, permissions));
3875
5222
  }
3876
5223
  if (nextFilter === false) return false;
3877
5224
  const baseFilterFn = getOption(`baseFilter.${access}`, null);
3878
- if (!(0, import_utils19.isFunction)(baseFilterFn)) return nextFilter ?? {};
5225
+ if (!(0, import_utils22.isFunction)(baseFilterFn)) return nextFilter ?? {};
3879
5226
  const baseFilter = normalizeFilter(
3880
5227
  cache.has(cacheKey) ? cache.get(cacheKey) : await baseFilterFn.call(req, permissions)
3881
5228
  );
@@ -3909,34 +5256,34 @@ async function setRequestPermissions(req) {
3909
5256
  if (owner === activeRuntime) return;
3910
5257
  if (hasFieldValue && owner === void 0) return;
3911
5258
  const globalPermissions = getGlobalOption("globalPermissions");
3912
- if (!(0, import_utils19.isFunction)(globalPermissions)) return;
5259
+ if (!(0, import_utils22.isFunction)(globalPermissions)) return;
3913
5260
  const permissions = await globalPermissions.call(req, req);
3914
- if ((0, import_utils19.isPlainObject)(permissions)) req[requestPermissionField] = permissions;
3915
- else if ((0, import_utils19.isArray)(permissions)) req[requestPermissionField] = (0, import_utils19.arrayToRecord)(permissions);
3916
- else if ((0, import_utils19.isString)(permissions)) req[requestPermissionField] = { [permissions]: true };
5261
+ if ((0, import_utils22.isPlainObject)(permissions)) req[requestPermissionField] = permissions;
5262
+ else if ((0, import_utils22.isArray)(permissions)) req[requestPermissionField] = (0, import_utils22.arrayToRecord)(permissions);
5263
+ else if ((0, import_utils22.isString)(permissions)) req[requestPermissionField] = { [permissions]: true };
3917
5264
  owners.set(requestPermissionField, activeRuntime);
3918
5265
  }
3919
5266
  async function evaluateRouteGuard(req, permissions, routeGuard) {
3920
5267
  const phas = (key) => permissions.has(key);
3921
5268
  const { stringHandler, arrayHandler } = createValidator(phas);
3922
- if ((0, import_utils19.isBoolean)(routeGuard)) {
5269
+ if ((0, import_utils22.isBoolean)(routeGuard)) {
3923
5270
  return routeGuard === true;
3924
5271
  }
3925
- if ((0, import_utils19.isString)(routeGuard)) {
5272
+ if ((0, import_utils22.isString)(routeGuard)) {
3926
5273
  return stringHandler(routeGuard);
3927
5274
  }
3928
- if ((0, import_utils19.isArray)(routeGuard)) {
5275
+ if ((0, import_utils22.isArray)(routeGuard)) {
3929
5276
  return arrayHandler(routeGuard);
3930
5277
  }
3931
- if ((0, import_utils19.isFunction)(routeGuard)) {
5278
+ if ((0, import_utils22.isFunction)(routeGuard)) {
3932
5279
  return routeGuard.call(req, permissions);
3933
5280
  }
3934
5281
  return false;
3935
5282
  }
3936
5283
  async function callHookChain(req, hook, doc, permissions, context) {
3937
- const hooks = (0, import_utils19.castArray)(hook);
5284
+ const hooks = (0, import_utils22.castArray)(hook);
3938
5285
  for (let x = 0; x < hooks.length; x++) {
3939
- if ((0, import_utils19.isFunction)(hooks[x])) {
5286
+ if ((0, import_utils22.isFunction)(hooks[x])) {
3940
5287
  doc = await hooks[x].call(req, doc, permissions, context);
3941
5288
  }
3942
5289
  }
@@ -3959,13 +5306,13 @@ async function collectSchemaFields({
3959
5306
  if (baseFields.includes(key)) continue;
3960
5307
  const val = permissionSchema[key];
3961
5308
  const value = val && val[access] || val;
3962
- if ((0, import_utils19.isBoolean)(value)) {
5309
+ if ((0, import_utils22.isBoolean)(value)) {
3963
5310
  if (value) fields.push(key);
3964
- } else if ((0, import_utils19.isString)(value)) {
5311
+ } else if ((0, import_utils22.isString)(value)) {
3965
5312
  if (stringHandler(value)) fields.push(key);
3966
- } else if ((0, import_utils19.isArray)(value)) {
5313
+ } else if ((0, import_utils22.isArray)(value)) {
3967
5314
  if (arrayHandler(value)) fields.push(key);
3968
- } else if ((0, import_utils19.isFunction)(value)) {
5315
+ } else if ((0, import_utils22.isFunction)(value)) {
3969
5316
  if (await value.call(req, ...functionArgs)) fields.push(key);
3970
5317
  }
3971
5318
  }
@@ -4051,7 +5398,7 @@ async function runDecorateAllHook({
4051
5398
  }
4052
5399
 
4053
5400
  // src/acl/select-resolution.ts
4054
- var import_utils20 = require("@web-ts-toolkit/utils");
5401
+ var import_utils23 = require("@web-ts-toolkit/utils");
4055
5402
  async function collectAllowedFieldsForRequest({
4056
5403
  req,
4057
5404
  permissionSchema,
@@ -4119,15 +5466,15 @@ async function resolveSelectForRequest({
4119
5466
  const excludeall = normalizedSelect.every((v) => v.startsWith("-"));
4120
5467
  if (excludeall) {
4121
5468
  normalizedSelect = normalizedSelect.map((v) => v.substring(1));
4122
- fields = (0, import_utils20.difference)(fields, normalizedSelect);
5469
+ fields = (0, import_utils23.difference)(fields, normalizedSelect);
4123
5470
  if (excludeid) fields.push("-_id");
4124
5471
  } else {
4125
- fields = (0, import_utils20.intersection)(normalizedSelect, fields.concat(excludeid ? "-_id" : "_id"));
5472
+ fields = (0, import_utils23.intersection)(normalizedSelect, fields.concat(excludeid ? "-_id" : "_id"));
4126
5473
  }
4127
5474
  }
4128
5475
  return fields.concat(alwaysSelectFields);
4129
5476
  }
4130
- return (0, import_utils20.intersection)(normalizedSelect, fields);
5477
+ return (0, import_utils23.intersection)(normalizedSelect, fields);
4131
5478
  }
4132
5479
 
4133
5480
  // src/core.ts
@@ -4142,10 +5489,10 @@ var Core = class {
4142
5489
  getIdentifier(modelName) {
4143
5490
  const resolveIdFilter = getModelOption(modelName, "resolveIdFilter");
4144
5491
  const idField = getModelOption(modelName, "idField");
4145
- if ((0, import_utils21.isFunction)(resolveIdFilter)) {
5492
+ if ((0, import_utils24.isFunction)(resolveIdFilter)) {
4146
5493
  return null;
4147
5494
  }
4148
- if ((0, import_utils21.isString)(idField)) {
5495
+ if ((0, import_utils24.isString)(idField)) {
4149
5496
  return idField;
4150
5497
  }
4151
5498
  return "_id";
@@ -4223,11 +5570,11 @@ var Core = class {
4223
5570
  async genPopulate(modelName, access = "read", _populate = null, subPaths = []) {
4224
5571
  if (!_populate) return [];
4225
5572
  let populate = Array.isArray(_populate) ? _populate : [_populate];
4226
- populate = (0, import_utils21.compact)(
5573
+ populate = (0, import_utils24.compact)(
4227
5574
  await Promise.all(
4228
5575
  populate.map(async (p) => {
4229
- const populateAccess = !(0, import_utils21.isString)(p) && p.access ? p.access : access;
4230
- const ret = (0, import_utils21.isString)(p) ? { path: p } : {
5576
+ const populateAccess = !(0, import_utils24.isString)(p) && p.access ? p.access : access;
5577
+ const ret = (0, import_utils24.isString)(p) ? { path: p } : {
4231
5578
  path: p.path,
4232
5579
  select: normalizeSelect(p.select)
4233
5580
  };
@@ -4265,10 +5612,10 @@ var Core = class {
4265
5612
  }
4266
5613
  async validate(modelName, allowedData, access, context) {
4267
5614
  const validate = getModelOption(modelName, `validate.${access}`, null);
4268
- if ((0, import_utils21.isFunction)(validate)) {
5615
+ if ((0, import_utils24.isFunction)(validate)) {
4269
5616
  const permissions = this.getGlobalPermissions();
4270
5617
  return validate.call(this.req, allowedData, permissions, context);
4271
- } else if ((0, import_utils21.isBoolean)(validate) || (0, import_utils21.isArray)(validate)) {
5618
+ } else if ((0, import_utils24.isBoolean)(validate) || (0, import_utils24.isArray)(validate)) {
4272
5619
  return validate;
4273
5620
  } else {
4274
5621
  return true;
@@ -4293,7 +5640,7 @@ var Core = class {
4293
5640
  const changeOptions = getModelOption(modelName, `onChange`, {});
4294
5641
  for (let x = 0; x < context.modifiedPaths.length; x++) {
4295
5642
  const mpath = context.modifiedPaths[x];
4296
- if ((0, import_utils21.isFunction)(changeOptions[mpath])) {
5643
+ if ((0, import_utils24.isFunction)(changeOptions[mpath])) {
4297
5644
  await changeOptions[mpath].call(
4298
5645
  this.req,
4299
5646
  context.originalDocumentSnapshot[mpath],
@@ -4317,7 +5664,7 @@ var Core = class {
4317
5664
  async genDocPermissions(modelName, doc, access, context) {
4318
5665
  const docPermissionsFn = getModelOption(modelName, `docPermissions.${access}`, null);
4319
5666
  let docPermissions = {};
4320
- if ((0, import_utils21.isFunction)(docPermissionsFn)) {
5667
+ if ((0, import_utils24.isFunction)(docPermissionsFn)) {
4321
5668
  const permissions = this.getGlobalPermissions();
4322
5669
  try {
4323
5670
  docPermissions = await docPermissionsFn.call(this.req, doc, permissions, context);
@@ -4368,7 +5715,7 @@ var Core = class {
4368
5715
  readExists ? this.genAllowedFields(modelName, doc, "read") : [],
4369
5716
  updateExists ? this.genAllowedFields(modelName, doc, "update") : []
4370
5717
  ]);
4371
- const viewObj = (0, import_utils21.reduce)(
5718
+ const viewObj = (0, import_utils24.reduce)(
4372
5719
  views,
4373
5720
  (ret, view) => {
4374
5721
  ret[view] = true;
@@ -4376,7 +5723,7 @@ var Core = class {
4376
5723
  },
4377
5724
  {}
4378
5725
  );
4379
- const editObj = (0, import_utils21.reduce)(
5726
+ const editObj = (0, import_utils24.reduce)(
4380
5727
  edits,
4381
5728
  (ret, view) => {
4382
5729
  ret[view] = true;
@@ -4400,9 +5747,9 @@ var Core = class {
4400
5747
  return runDecorateAllHook({ req: this.req, hook: decorateAll, docs, permissions, context });
4401
5748
  }
4402
5749
  runTasks(modelName, docObject, task) {
4403
- const tasks = (0, import_utils21.compact)((0, import_utils21.castArray)(task));
5750
+ const tasks = (0, import_utils24.compact)((0, import_utils24.castArray)(task));
4404
5751
  if (tasks.length === 0) return docObject;
4405
- (0, import_utils21.forEach)(tasks, (task2) => {
5752
+ (0, import_utils24.forEach)(tasks, (task2) => {
4406
5753
  const { type, args, options } = task2;
4407
5754
  switch (type) {
4408
5755
  case "COPY_AND_DEPOPULATE":
@@ -4412,6 +5759,41 @@ var Core = class {
4412
5759
  options
4413
5760
  );
4414
5761
  break;
5762
+ case "COPY":
5763
+ docObject = copyPaths(
5764
+ docObject,
5765
+ args,
5766
+ options
5767
+ );
5768
+ break;
5769
+ case "MOVE":
5770
+ docObject = movePaths(
5771
+ docObject,
5772
+ args,
5773
+ options
5774
+ );
5775
+ break;
5776
+ case "SLICE":
5777
+ docObject = sliceArrays(
5778
+ docObject,
5779
+ args,
5780
+ options
5781
+ );
5782
+ break;
5783
+ case "COUNT":
5784
+ docObject = countPaths(
5785
+ docObject,
5786
+ args,
5787
+ options
5788
+ );
5789
+ break;
5790
+ case "MASK":
5791
+ docObject = maskPaths(
5792
+ docObject,
5793
+ args,
5794
+ options
5795
+ );
5796
+ break;
4415
5797
  }
4416
5798
  });
4417
5799
  return docObject;
@@ -4435,9 +5817,9 @@ var Core = class {
4435
5817
  }
4436
5818
  const [, field, op] = keys2;
4437
5819
  const subOption = getExactModelOption(modelName, `operationAccess.${access}`);
4438
- if ((0, import_utils21.isUndefined)(subOption)) {
5820
+ if ((0, import_utils24.isUndefined)(subOption)) {
4439
5821
  const subFieldOption = getExactModelOption(modelName, `operationAccess.subs.${field}`);
4440
- if ((0, import_utils21.isUndefined)(subFieldOption)) {
5822
+ if ((0, import_utils24.isUndefined)(subFieldOption)) {
4441
5823
  const opOption = getModelOption(modelName, `operationAccess.${op}`);
4442
5824
  return this.canActivate(opOption);
4443
5825
  }
@@ -4489,25 +5871,25 @@ function guard(condition) {
4489
5871
  const permissions = req[PERMISSIONS];
4490
5872
  let cond = condition;
4491
5873
  let phas = (key) => permissions.has(key);
4492
- if ((0, import_utils22.isPlainObject)(condition)) {
5874
+ if ((0, import_utils25.isPlainObject)(condition)) {
4493
5875
  const { modelName, id, condition: _cond } = condition;
4494
5876
  const svc = req.macl.getPublicService(modelName);
4495
5877
  const select = getModelOption(modelName, `alwaysSelectFields.read`, void 0);
4496
- let _id = (0, import_utils22.isString)(id) ? id : null;
4497
- if ((0, import_utils22.isPlainObject)(id)) {
5878
+ let _id = (0, import_utils25.isString)(id) ? id : null;
5879
+ if ((0, import_utils25.isPlainObject)(id)) {
4498
5880
  const { type = "param", key } = id;
4499
5881
  if (type === "param") {
4500
5882
  const paramValue = req.params[key];
4501
- _id = (0, import_utils22.isArray)(paramValue) ? paramValue[0] ?? null : paramValue ?? null;
5883
+ _id = (0, import_utils25.isArray)(paramValue) ? paramValue[0] ?? null : paramValue ?? null;
4502
5884
  } else if (type === "query") {
4503
5885
  const _qval = req.query[key];
4504
- if ((0, import_utils22.isArray)(_qval)) _id = _qval.length > 0 ? _qval[0] : null;
5886
+ if ((0, import_utils25.isArray)(_qval)) _id = _qval.length > 0 ? _qval[0] : null;
4505
5887
  else _id = _qval;
4506
5888
  } else {
4507
5889
  _id = null;
4508
5890
  }
4509
5891
  }
4510
- if (!(0, import_utils22.isString)(_id) || !_id) {
5892
+ if (!(0, import_utils25.isString)(_id) || !_id) {
4511
5893
  return next(new import_express_json_router2.default.clientErrors.BadRequestError());
4512
5894
  }
4513
5895
  const result = await svc._read(_id, { select });
@@ -4519,11 +5901,11 @@ function guard(condition) {
4519
5901
  cond = _cond;
4520
5902
  }
4521
5903
  const { stringHandler, arrayHandler } = createValidator(phas);
4522
- if ((0, import_utils22.isString)(cond)) {
5904
+ if ((0, import_utils25.isString)(cond)) {
4523
5905
  if (stringHandler(cond)) return next();
4524
- } else if ((0, import_utils22.isArray)(cond)) {
5906
+ } else if ((0, import_utils25.isArray)(cond)) {
4525
5907
  if (arrayHandler(cond)) return next();
4526
- } else if ((0, import_utils22.isFunction)(cond)) {
5908
+ } else if ((0, import_utils25.isFunction)(cond)) {
4527
5909
  const result = await cond.call(req, permissions);
4528
5910
  if (!!result) return next();
4529
5911
  }
@@ -4536,7 +5918,7 @@ var import_express2 = __toESM(require("express"));
4536
5918
 
4537
5919
  // src/routers/model-router.ts
4538
5920
  var import_express_json_router5 = __toESM(require("@web-ts-toolkit/express-json-router"));
4539
- var import_utils25 = require("@web-ts-toolkit/utils");
5921
+ var import_utils29 = require("@web-ts-toolkit/utils");
4540
5922
 
4541
5923
  // src/routers/router-mutation.ts
4542
5924
  var MODEL_BUILD_TIME_OPTION_KEYS = /* @__PURE__ */ new Set([
@@ -4665,6 +6047,7 @@ var import_express_json_router4 = __toESM(require("@web-ts-toolkit/express-json-
4665
6047
 
4666
6048
  // src/validation/common.ts
4667
6049
  var import_zod2 = require("zod");
6050
+ var import_utils26 = require("@web-ts-toolkit/utils");
4668
6051
  var stringOrStringArray = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.array(import_zod2.z.string())]);
4669
6052
  var safeIntegerString = (min, message) => import_zod2.z.string().regex(/^\d+$/, message).refine((value) => Number.isSafeInteger(Number(value)), "Expected a safe integer").refine((value) => Number(value) >= min, message);
4670
6053
  var queryBooleanString = import_zod2.z.enum(["true", "false"]);
@@ -4725,7 +6108,8 @@ var subPopulateSchema = import_zod2.z.union([
4725
6108
  select: projectionSchema.optional()
4726
6109
  }).passthrough()
4727
6110
  ]);
4728
- var includeItemSchema = import_zod2.z.object({
6111
+ var legacyIncludeItemSchema = import_zod2.z.object({
6112
+ mode: import_zod2.z.enum(["legacy"]).optional(),
4729
6113
  model: import_zod2.z.string().min(1),
4730
6114
  op: import_zod2.z.enum(["list", "read", "count"]),
4731
6115
  path: import_zod2.z.string().min(1),
@@ -4734,8 +6118,139 @@ var includeItemSchema = import_zod2.z.object({
4734
6118
  foreignField: import_zod2.z.string().min(1),
4735
6119
  args: import_zod2.z.unknown().optional().describe("Forwarded to list/read includes. Count includes ignore pagination fields so counts remain exact."),
4736
6120
  options: import_zod2.z.unknown().optional()
4737
- }).passthrough();
4738
- var includeSchema = import_zod2.z.union([includeItemSchema, import_zod2.z.array(includeItemSchema)]);
6121
+ }).passthrough().superRefine((entry, ctx) => {
6122
+ if (entry.filter !== void 0 && containsParentMarkerShape(entry.filter)) {
6123
+ ctx.addIssue({
6124
+ code: import_zod2.z.ZodIssueCode.custom,
6125
+ message: 'Legacy includes must not contain $parent markers; use a correlated include (mode: "correlated")',
6126
+ path: ["filter"]
6127
+ });
6128
+ }
6129
+ });
6130
+ var parentRefSchema = import_zod2.z.object({ $parent: import_zod2.z.string().min(1) }).strict().superRefine((value, ctx) => {
6131
+ if (!isValidParentRefPath(value.$parent)) {
6132
+ ctx.addIssue({
6133
+ code: import_zod2.z.ZodIssueCode.custom,
6134
+ message: `Invalid $parent reference path: ${value.$parent}`,
6135
+ path: ["$parent"]
6136
+ });
6137
+ }
6138
+ });
6139
+ var correlatedIdSchema = import_zod2.z.union([import_zod2.z.string().min(1), parentRefSchema]);
6140
+ var correlatedFilterSchema = import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).superRefine((filter2, ctx) => {
6141
+ for (const issue of validateCorrelatedFilterTemplate(filter2)) {
6142
+ ctx.addIssue({
6143
+ code: import_zod2.z.ZodIssueCode.custom,
6144
+ message: issue.message,
6145
+ path: issue.path.map(String)
6146
+ });
6147
+ }
6148
+ });
6149
+ var correlatedEmptyOptionsSchema = import_zod2.z.unknown().refine(
6150
+ (value) => value === void 0 || (0, import_utils26.isPlainObject)(value) && Object.keys(value).length === 0,
6151
+ "Correlated include options must be absent or empty"
6152
+ ).optional();
6153
+ var correlatedPathSchema = import_zod2.z.string().min(1).superRefine((path, ctx) => {
6154
+ if (!isValidCorrelatedOutputPath(path)) {
6155
+ ctx.addIssue({
6156
+ code: import_zod2.z.ZodIssueCode.custom,
6157
+ message: `Invalid correlated include output path: ${path}`,
6158
+ path: []
6159
+ });
6160
+ }
6161
+ });
6162
+ var checkArgsMarkers = (select, sort, ctx) => {
6163
+ const selectIssue = findArgsMarkerIssue(select, "args.select");
6164
+ if (selectIssue) {
6165
+ ctx.addIssue({ code: import_zod2.z.ZodIssueCode.custom, message: selectIssue.message, path: ["select"] });
6166
+ }
6167
+ const sortIssue = findArgsMarkerIssue(sort, "args.sort");
6168
+ if (sortIssue) {
6169
+ ctx.addIssue({ code: import_zod2.z.ZodIssueCode.custom, message: sortIssue.message, path: ["sort"] });
6170
+ }
6171
+ };
6172
+ var correlatedReadArgsSchema = import_zod2.z.object({
6173
+ select: projectionSchema.optional(),
6174
+ sort: sortSchema.optional(),
6175
+ include: import_zod2.z.lazy(() => correlatedNestedIncludeSchema).optional()
6176
+ }).strict().superRefine((args, ctx) => checkArgsMarkers(args.select, args.sort, ctx));
6177
+ var correlatedListArgsSchema = import_zod2.z.object({
6178
+ select: projectionSchema.optional(),
6179
+ sort: sortSchema.optional(),
6180
+ skip: import_zod2.z.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
6181
+ limit: import_zod2.z.union([positiveIntegerSchema, positiveIntegerString]).optional(),
6182
+ page: import_zod2.z.union([nonNegativeIntegerSchema, nonNegativeIntegerString]).optional(),
6183
+ pageSize: import_zod2.z.union([positiveIntegerSchema, positiveIntegerString]).optional(),
6184
+ include: import_zod2.z.lazy(() => correlatedNestedIncludeSchema).optional()
6185
+ }).strict().superRefine((args, ctx) => checkArgsMarkers(args.select, args.sort, ctx));
6186
+ var correlatedCountArgsSchema = import_zod2.z.unknown().refine(
6187
+ (value) => value === void 0 || (0, import_utils26.isPlainObject)(value) && Object.keys(value).length === 0,
6188
+ "Count includes accept no args"
6189
+ ).optional();
6190
+ var correlatedReadByIdSchema = import_zod2.z.object({
6191
+ mode: import_zod2.z.literal("correlated"),
6192
+ model: import_zod2.z.string().min(1),
6193
+ op: import_zod2.z.literal("read"),
6194
+ path: correlatedPathSchema,
6195
+ id: correlatedIdSchema,
6196
+ args: correlatedReadArgsSchema.optional(),
6197
+ options: correlatedEmptyOptionsSchema
6198
+ }).strict();
6199
+ var correlatedReadByFilterSchema = import_zod2.z.object({
6200
+ mode: import_zod2.z.literal("correlated"),
6201
+ model: import_zod2.z.string().min(1),
6202
+ op: import_zod2.z.literal("read"),
6203
+ path: correlatedPathSchema,
6204
+ filter: correlatedFilterSchema,
6205
+ args: correlatedReadArgsSchema.optional(),
6206
+ options: correlatedEmptyOptionsSchema
6207
+ }).strict();
6208
+ var correlatedListSchema = import_zod2.z.object({
6209
+ mode: import_zod2.z.literal("correlated"),
6210
+ model: import_zod2.z.string().min(1),
6211
+ op: import_zod2.z.literal("list"),
6212
+ path: correlatedPathSchema,
6213
+ filter: correlatedFilterSchema,
6214
+ args: correlatedListArgsSchema.optional(),
6215
+ options: correlatedEmptyOptionsSchema
6216
+ }).strict();
6217
+ var correlatedCountSchema = import_zod2.z.object({
6218
+ mode: import_zod2.z.literal("correlated"),
6219
+ model: import_zod2.z.string().min(1),
6220
+ op: import_zod2.z.literal("count"),
6221
+ path: correlatedPathSchema,
6222
+ filter: correlatedFilterSchema,
6223
+ args: correlatedCountArgsSchema,
6224
+ options: correlatedEmptyOptionsSchema
6225
+ }).strict();
6226
+ var includeItemSchema = import_zod2.z.union([
6227
+ legacyIncludeItemSchema,
6228
+ correlatedReadByIdSchema,
6229
+ correlatedReadByFilterSchema,
6230
+ correlatedListSchema,
6231
+ correlatedCountSchema
6232
+ ]);
6233
+ var correlatedNestedIncludeSchema = import_zod2.z.union([includeItemSchema, import_zod2.z.array(includeItemSchema)]);
6234
+ var rejectDuplicateCorrelatedPaths = (items, ctx) => {
6235
+ const seen = /* @__PURE__ */ new Set();
6236
+ items.forEach((item, index) => {
6237
+ if ((0, import_utils26.isPlainObject)(item) && item.mode === "correlated" && typeof item.path === "string") {
6238
+ if (seen.has(item.path)) {
6239
+ ctx.addIssue({
6240
+ code: import_zod2.z.ZodIssueCode.custom,
6241
+ message: `Duplicate correlated include output path: ${item.path}`,
6242
+ path: [index, "path"]
6243
+ });
6244
+ } else {
6245
+ seen.add(item.path);
6246
+ }
6247
+ }
6248
+ });
6249
+ };
6250
+ var includeSchema = import_zod2.z.union([
6251
+ includeItemSchema,
6252
+ import_zod2.z.array(includeItemSchema).superRefine(rejectDuplicateCorrelatedPaths)
6253
+ ]);
4739
6254
  var fieldsSchema = import_zod2.z.array(import_zod2.z.string().min(1));
4740
6255
  var taskSchema = import_zod2.z.object({
4741
6256
  type: import_zod2.z.string().min(1),
@@ -4918,19 +6433,12 @@ function fromJoi(schema) {
4918
6433
  }
4919
6434
  function fromAjv(validator) {
4920
6435
  return async (value) => {
4921
- const outcome = validator(value);
4922
- if (isThenable(outcome)) {
6436
+ if (validator.$async === true) {
4923
6437
  try {
4924
- const data = await outcome;
4925
- if (data === false) {
4926
- return {
4927
- success: false,
4928
- issues: normalizeAjvIssues(validator.errors ?? [])
4929
- };
4930
- }
6438
+ const data = await validator(value);
4931
6439
  return {
4932
6440
  success: true,
4933
- data: data === true ? value : data
6441
+ data
4934
6442
  };
4935
6443
  } catch (error2) {
4936
6444
  if (!isAjvValidationError(error2)) {
@@ -4938,11 +6446,21 @@ function fromAjv(validator) {
4938
6446
  }
4939
6447
  return {
4940
6448
  success: false,
4941
- issues: normalizeAjvIssues(error2.errors ?? [])
6449
+ issues: normalizeAjvIssues(error2.errors)
4942
6450
  };
4943
6451
  }
4944
6452
  }
4945
- const errorsSnapshot = validator.errors ? [...validator.errors] : [];
6453
+ const outcome = validator(value);
6454
+ if (isThenable(outcome)) {
6455
+ void Promise.resolve(outcome).catch(() => {
6456
+ });
6457
+ throw new TypeError(
6458
+ "fromAjv: promise/thenable validators must declare $async: true and resolve with validated data"
6459
+ );
6460
+ }
6461
+ if (typeof outcome !== "boolean") {
6462
+ throw new TypeError("fromAjv: synchronous validators must return a boolean verdict");
6463
+ }
4946
6464
  if (outcome) {
4947
6465
  return {
4948
6466
  success: true,
@@ -4951,7 +6469,9 @@ function fromAjv(validator) {
4951
6469
  }
4952
6470
  return {
4953
6471
  success: false,
4954
- issues: normalizeAjvIssues(errorsSnapshot)
6472
+ // Normalize immediately, copying message/path before another call can
6473
+ // replace the errors array or mutate its entries.
6474
+ issues: normalizeAjvIssues(validator.errors ?? [])
4955
6475
  };
4956
6476
  };
4957
6477
  }
@@ -5181,14 +6701,14 @@ function isArkTypeErrors(value) {
5181
6701
  return record.arkKind === "errors" || record[" arkKind"] === "errors";
5182
6702
  }
5183
6703
  function isThenable(value) {
5184
- return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
6704
+ return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
5185
6705
  }
5186
6706
  function isAjvValidationError(error2) {
5187
6707
  if (typeof error2 !== "object" || error2 === null || !("errors" in error2)) {
5188
6708
  return false;
5189
6709
  }
5190
- const errors = error2.errors;
5191
- return errors == null || Array.isArray(errors);
6710
+ const candidate = error2;
6711
+ return candidate.ajv === true && candidate.validation === true && Array.isArray(candidate.errors);
5192
6712
  }
5193
6713
  function isVineValidationError(error2) {
5194
6714
  if (typeof error2 !== "object" || error2 === null || !("messages" in error2)) {
@@ -5272,7 +6792,7 @@ var listBodySchema = import_zod3.z.object({
5272
6792
  }).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query"]));
5273
6793
  var countBodySchema = import_zod3.z.object({
5274
6794
  filter: objectOrArraySchema.optional()
5275
- }).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query", "access", "options"]));
6795
+ }).passthrough().superRefine((body, ctx) => rejectKeys(body, ctx, ["query", "access", "options", "include"]));
5276
6796
  var readFilterBodySchema = import_zod3.z.object({
5277
6797
  filter: objectOrArraySchema.optional(),
5278
6798
  select: projectionSchema.optional(),
@@ -5627,7 +7147,7 @@ var rootDataQueryEntrySchema = import_zod5.z.union([
5627
7147
  var rootQuerySchema = import_zod5.z.array(import_zod5.z.union([rootModelQueryEntrySchema, rootDataQueryEntrySchema]));
5628
7148
 
5629
7149
  // src/routers/shared.ts
5630
- var import_utils23 = require("@web-ts-toolkit/utils");
7150
+ var import_utils27 = require("@web-ts-toolkit/utils");
5631
7151
 
5632
7152
  // src/routers/model-router-collection-routes.ts
5633
7153
  function setModelCollectionRoutes(context) {
@@ -5645,14 +7165,14 @@ function setModelCollectionRoutes(context) {
5645
7165
  await context.assertAllowed(req, "list");
5646
7166
  const { skip, limit, page, page_size, skim, include_permissions, include_count, include_extra_headers } = parseQuery(requestSchemas.listQuery, req.query);
5647
7167
  const svc = context.getPublicService(req);
5648
- const includeCount = (0, import_utils23.parseBooleanString)(include_count);
5649
- const includeExtraHeaders = (0, import_utils23.parseBooleanString)(include_extra_headers);
7168
+ const includeCount = (0, import_utils27.parseBooleanString)(include_count);
7169
+ const includeExtraHeaders = (0, import_utils27.parseBooleanString)(include_extra_headers);
5650
7170
  const result = await svc._list(
5651
7171
  {},
5652
7172
  { skip, limit, page, pageSize: page_size },
5653
7173
  {
5654
- skim: (0, import_utils23.parseBooleanString)(skim),
5655
- includePermissions: (0, import_utils23.parseBooleanString)(include_permissions),
7174
+ skim: (0, import_utils27.parseBooleanString)(skim),
7175
+ includePermissions: (0, import_utils27.parseBooleanString)(include_permissions),
5656
7176
  includeCount
5657
7177
  }
5658
7178
  );
@@ -5707,7 +7227,7 @@ function setModelCollectionRoutes(context) {
5707
7227
  context.getRequestSchema("requestSchemas.create")
5708
7228
  );
5709
7229
  const svc = context.getPublicService(req);
5710
- const result = await svc._create(data, {}, { includePermissions: (0, import_utils23.parseBooleanString)(include_permissions) });
7230
+ const result = await svc._create(data, {}, { includePermissions: (0, import_utils27.parseBooleanString)(include_permissions) });
5711
7231
  handleResultError(result);
5712
7232
  return formatModelCreatedResponse(result);
5713
7233
  });
@@ -5743,7 +7263,7 @@ function setModelCollectionRoutes(context) {
5743
7263
  finalData,
5744
7264
  { select: finalSelect, populate: finalPopulate, tasks: finalTasks },
5745
7265
  {
5746
- includePermissions: includePermissions ?? (0, import_utils23.parseBooleanString)(include_permissions),
7266
+ includePermissions: includePermissions ?? (0, import_utils27.parseBooleanString)(include_permissions),
5747
7267
  populateAccess
5748
7268
  }
5749
7269
  );
@@ -5835,8 +7355,8 @@ function setModelDocumentRoutes(context) {
5835
7355
  id,
5836
7356
  {},
5837
7357
  {
5838
- includePermissions: (0, import_utils23.parseBooleanString)(include_permissions),
5839
- tryList: (0, import_utils23.parseBooleanString)(try_list)
7358
+ includePermissions: (0, import_utils27.parseBooleanString)(include_permissions),
7359
+ tryList: (0, import_utils27.parseBooleanString)(try_list)
5840
7360
  }
5841
7361
  );
5842
7362
  handleResultError(result);
@@ -5921,8 +7441,8 @@ function setModelDocumentRoutes(context) {
5921
7441
  data,
5922
7442
  {},
5923
7443
  {
5924
- returningAll: (0, import_utils23.parseBooleanString)(returning_all),
5925
- includePermissions: (0, import_utils23.parseBooleanString)(include_permissions)
7444
+ returningAll: (0, import_utils27.parseBooleanString)(returning_all),
7445
+ includePermissions: (0, import_utils27.parseBooleanString)(include_permissions)
5926
7446
  }
5927
7447
  );
5928
7448
  handleResultError(result);
@@ -5968,8 +7488,8 @@ function setModelDocumentRoutes(context) {
5968
7488
  finalData,
5969
7489
  { select: finalSelect, populate: finalPopulate, tasks: finalTasks },
5970
7490
  {
5971
- returningAll: returningAll ?? (0, import_utils23.parseBooleanString)(returning_all),
5972
- includePermissions: includePermissions ?? (0, import_utils23.parseBooleanString)(include_permissions),
7491
+ returningAll: returningAll ?? (0, import_utils27.parseBooleanString)(returning_all),
7492
+ includePermissions: includePermissions ?? (0, import_utils27.parseBooleanString)(include_permissions),
5973
7493
  populateAccess
5974
7494
  }
5975
7495
  );
@@ -5997,8 +7517,8 @@ function setModelDocumentRoutes(context) {
5997
7517
  body,
5998
7518
  {},
5999
7519
  {
6000
- returningAll: (0, import_utils23.parseBooleanString)(returning_all),
6001
- includePermissions: (0, import_utils23.parseBooleanString)(include_permissions)
7520
+ returningAll: (0, import_utils27.parseBooleanString)(returning_all),
7521
+ includePermissions: (0, import_utils27.parseBooleanString)(include_permissions)
6002
7522
  }
6003
7523
  );
6004
7524
  handleResultError(result);
@@ -6042,8 +7562,8 @@ function setModelDocumentRoutes(context) {
6042
7562
  finalData,
6043
7563
  { select: finalSelect, populate: finalPopulate, tasks: finalTasks },
6044
7564
  {
6045
- returningAll: returningAll ?? (0, import_utils23.parseBooleanString)(returning_all),
6046
- includePermissions: includePermissions ?? (0, import_utils23.parseBooleanString)(include_permissions),
7565
+ returningAll: returningAll ?? (0, import_utils27.parseBooleanString)(returning_all),
7566
+ includePermissions: includePermissions ?? (0, import_utils27.parseBooleanString)(include_permissions),
6047
7567
  populateAccess
6048
7568
  }
6049
7569
  );
@@ -6281,11 +7801,11 @@ function setModelSubDocumentRoutes(context) {
6281
7801
  }
6282
7802
 
6283
7803
  // src/openapi/route-registration.ts
6284
- var import_utils24 = require("@web-ts-toolkit/utils");
7804
+ var import_utils28 = require("@web-ts-toolkit/utils");
6285
7805
  function registerOpenApiRoute(runtime, basePath, defaultTag, route) {
6286
7806
  runtime.registerOpenApiRoute({
6287
7807
  ...route,
6288
- path: (0, import_utils24.normalizeUrlPath)(basePath + route.path),
7808
+ path: (0, import_utils28.normalizeUrlPath)(basePath + route.path),
6289
7809
  tags: route.tags ?? [defaultTag]
6290
7810
  });
6291
7811
  }
@@ -6293,8 +7813,8 @@ function registerOpenApiRoute(runtime, basePath, defaultTag, route) {
6293
7813
  // src/routers/model-router.ts
6294
7814
  var clientErrors2 = import_express_json_router5.default.clientErrors;
6295
7815
  function setOption(parentKey, optionKey, option) {
6296
- const key = (0, import_utils25.isUndefined)(option) ? parentKey : `${parentKey}.${optionKey}`;
6297
- const value = (0, import_utils25.isUndefined)(option) ? optionKey : option;
7816
+ const key = (0, import_utils29.isUndefined)(option) ? parentKey : `${parentKey}.${optionKey}`;
7817
+ const value = (0, import_utils29.isUndefined)(option) ? optionKey : option;
6298
7818
  assertMutableRouterOption("model", key);
6299
7819
  this.runtime.setModelOption(this.modelName, key, value);
6300
7820
  return this;
@@ -6456,7 +7976,7 @@ var ModelRouter = class _ModelRouter {
6456
7976
  this.runtime = runtime;
6457
7977
  this.runtime.setModelOptions(modelName, initialOptions);
6458
7978
  this.options = this.runtime.getModelOptions(modelName);
6459
- this.fullBasePath = (0, import_utils25.normalizeUrlPath)(this.options.parentPath + this.options.basePath);
7979
+ this.fullBasePath = (0, import_utils29.normalizeUrlPath)(this.options.parentPath + this.options.basePath);
6460
7980
  this.modelName = modelName;
6461
7981
  this.router = new import_express_json_router5.default(this.options.basePath, createSetCore(this.runtime), accessRouterResponseHandler);
6462
7982
  this.model = new model_default(modelName, this.runtime);
@@ -6536,13 +8056,13 @@ var ModelRouter = class _ModelRouter {
6536
8056
  }
6537
8057
  logEndpoints() {
6538
8058
  runWithRuntime(this.runtime, () => {
6539
- (0, import_utils25.forEach)(this.router.getEndpoints(), ({ method, path }) => {
6540
- logInfoMessage(`${(0, import_utils25.padEnd)(method, 6)} ${(0, import_utils25.normalizeUrlPath)(this.options.parentPath + path)}`);
8059
+ (0, import_utils29.forEach)(this.router.getEndpoints(), ({ method, path }) => {
8060
+ logInfoMessage(`${(0, import_utils29.padEnd)(method, 6)} ${(0, import_utils29.normalizeUrlPath)(this.options.parentPath + path)}`);
6541
8061
  });
6542
8062
  });
6543
8063
  }
6544
8064
  set(keyOrOptions, value) {
6545
- if (arguments.length === 2 && (0, import_utils25.isString)(keyOrOptions)) {
8065
+ if (arguments.length === 2 && (0, import_utils29.isString)(keyOrOptions)) {
6546
8066
  assertMutableRouterOption("model", keyOrOptions);
6547
8067
  this.runtime.setModelOption(
6548
8068
  this.modelName,
@@ -6550,7 +8070,7 @@ var ModelRouter = class _ModelRouter {
6550
8070
  value
6551
8071
  );
6552
8072
  }
6553
- if (arguments.length === 1 && (0, import_utils25.isPlainObject)(keyOrOptions)) {
8073
+ if (arguments.length === 1 && (0, import_utils29.isPlainObject)(keyOrOptions)) {
6554
8074
  assertMutableRouterOptions("model", keyOrOptions);
6555
8075
  this.runtime.setModelOptions(this.modelName, keyOrOptions);
6556
8076
  }
@@ -6573,7 +8093,7 @@ var ModelRouter = class _ModelRouter {
6573
8093
 
6574
8094
  // src/routers/root-router.ts
6575
8095
  var import_express_json_router6 = __toESM(require("@web-ts-toolkit/express-json-router"));
6576
- var import_utils26 = require("@web-ts-toolkit/utils");
8096
+ var import_utils30 = require("@web-ts-toolkit/utils");
6577
8097
 
6578
8098
  // src/core-data.ts
6579
8099
  var DataCore = class {
@@ -6839,7 +8359,7 @@ var RootRouter = class {
6839
8359
  this.setRoutes();
6840
8360
  }
6841
8361
  registerOpenApiRoute(route) {
6842
- registerOpenApiRoute(this.runtime, (0, import_utils26.normalizeUrlPath)(this.basename), "root", route);
8362
+ registerOpenApiRoute(this.runtime, (0, import_utils30.normalizeUrlPath)(this.basename), "root", route);
6843
8363
  }
6844
8364
  wrapResult(index, item, result) {
6845
8365
  return {
@@ -6900,7 +8420,7 @@ var RootRouter = class {
6900
8420
  groupItemsByOrder(items) {
6901
8421
  const groups = /* @__PURE__ */ new Map();
6902
8422
  items.forEach((item, index) => {
6903
- const order = (0, import_utils26.isNumber)(item.order) ? item.order : 0;
8423
+ const order = (0, import_utils30.isNumber)(item.order) ? item.order : 0;
6904
8424
  const group = groups.get(order);
6905
8425
  if (group) {
6906
8426
  group.push({ index, item });
@@ -6971,7 +8491,7 @@ var RootRouter = class {
6971
8491
  const arrResult = await this.runGroup(req, groupedItems[x]);
6972
8492
  results.push(...arrResult);
6973
8493
  }
6974
- return (0, import_utils26.orderBy)(results, ["index"], ["asc"]);
8494
+ return (0, import_utils30.orderBy)(results, ["index"], ["asc"]);
6975
8495
  });
6976
8496
  this.registerOpenApiRoute({
6977
8497
  method: "post",
@@ -6989,11 +8509,11 @@ var RootRouter = class {
6989
8509
 
6990
8510
  // src/routers/data-router.ts
6991
8511
  var import_express_json_router7 = __toESM(require("@web-ts-toolkit/express-json-router"));
6992
- var import_utils27 = require("@web-ts-toolkit/utils");
8512
+ var import_utils31 = require("@web-ts-toolkit/utils");
6993
8513
  var clientErrors4 = import_express_json_router7.default.clientErrors;
6994
8514
  function setOption2(parentKey, optionKey, option) {
6995
- const key = (0, import_utils27.isUndefined)(option) ? parentKey : `${parentKey}.${optionKey}`;
6996
- const value = (0, import_utils27.isUndefined)(option) ? optionKey : option;
8515
+ const key = (0, import_utils31.isUndefined)(option) ? parentKey : `${parentKey}.${optionKey}`;
8516
+ const value = (0, import_utils31.isUndefined)(option) ? optionKey : option;
6997
8517
  assertMutableRouterOption("data", key);
6998
8518
  this.runtime.setDataOption(this.dataName, key, value);
6999
8519
  return this;
@@ -7034,7 +8554,7 @@ var DataRouter = class {
7034
8554
  this.runtime = runtime;
7035
8555
  this.runtime.setDataOptions(dataName, initialOptions);
7036
8556
  this.options = this.runtime.getDataOptions(dataName);
7037
- this.fullBasePath = (0, import_utils27.normalizeUrlPath)(this.options.parentPath + this.options.basePath);
8557
+ this.fullBasePath = (0, import_utils31.normalizeUrlPath)(this.options.parentPath + this.options.basePath);
7038
8558
  this.dataName = dataName;
7039
8559
  this.router = new import_express_json_router7.default(this.options.basePath, createSetDataCore(this.runtime), accessRouterResponseHandler);
7040
8560
  this.setCollectionRoutes();
@@ -7067,8 +8587,8 @@ var DataRouter = class {
7067
8587
  req.query
7068
8588
  );
7069
8589
  const svc = this.getService(req);
7070
- const includeCount = (0, import_utils23.parseBooleanString)(include_count);
7071
- const includeExtraHeaders = (0, import_utils23.parseBooleanString)(include_extra_headers);
8590
+ const includeCount = (0, import_utils27.parseBooleanString)(include_count);
8591
+ const includeExtraHeaders = (0, import_utils27.parseBooleanString)(include_extra_headers);
7072
8592
  const result = await svc.find(
7073
8593
  {},
7074
8594
  { skip, limit, page, pageSize: page_size },
@@ -7186,11 +8706,11 @@ var DataRouter = class {
7186
8706
  });
7187
8707
  }
7188
8708
  set(keyOrOptions, value) {
7189
- if (arguments.length === 2 && (0, import_utils27.isString)(keyOrOptions)) {
8709
+ if (arguments.length === 2 && (0, import_utils31.isString)(keyOrOptions)) {
7190
8710
  assertMutableRouterOption("data", keyOrOptions);
7191
8711
  this.runtime.setDataOption(this.dataName, keyOrOptions, value);
7192
8712
  }
7193
- if (arguments.length === 1 && (0, import_utils27.isPlainObject)(keyOrOptions)) {
8713
+ if (arguments.length === 1 && (0, import_utils31.isPlainObject)(keyOrOptions)) {
7194
8714
  assertMutableRouterOptions("data", keyOrOptions);
7195
8715
  this.runtime.setDataOptions(this.dataName, keyOrOptions);
7196
8716
  }
@@ -7313,7 +8833,7 @@ function createRuntimeApi(runtime) {
7313
8833
  const wtt2 = runtimeMiddleware;
7314
8834
  wtt2.runtime = runtime;
7315
8835
  wtt2.createRouter = function(modelName, options) {
7316
- if ((0, import_utils28.isUndefined)(options)) {
8836
+ if ((0, import_utils32.isUndefined)(options)) {
7317
8837
  return new RootRouter(modelName, runtime);
7318
8838
  }
7319
8839
  if (typeof modelName === "string") {
@@ -7334,10 +8854,10 @@ function createRuntimeApi(runtime) {
7334
8854
  };
7335
8855
  wtt2.combineRoutes = combineRoutes;
7336
8856
  wtt2.set = function(keyOrOptions, value) {
7337
- if (arguments.length === 2 && (0, import_utils28.isString)(keyOrOptions)) {
8857
+ if (arguments.length === 2 && (0, import_utils32.isString)(keyOrOptions)) {
7338
8858
  return runtime.setGlobalOption(keyOrOptions, value);
7339
8859
  }
7340
- if (arguments.length === 1 && (0, import_utils28.isPlainObject)(keyOrOptions)) {
8860
+ if (arguments.length === 1 && (0, import_utils32.isPlainObject)(keyOrOptions)) {
7341
8861
  return runtime.setGlobalOptions(keyOrOptions);
7342
8862
  }
7343
8863
  };