@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/README.md +323 -2
- package/advanced.d.mts +655 -19
- package/advanced.d.ts +655 -19
- package/advanced.js +362 -22
- package/advanced.mjs +364 -23
- package/index.d.mts +5 -186
- package/index.d.ts +5 -186
- package/index.js +1783 -263
- package/index.mjs +1702 -178
- package/llms.txt +83 -2
- package/package.json +3 -3
- package/{parsers-B5dbVmjm.d.mts → parsers-BKF7VX2-.d.mts} +417 -13
- package/{parsers-B5dbVmjm.d.ts → parsers-BKF7VX2-.d.ts} +417 -13
- package/processors.d.mts +151 -1
- package/processors.d.ts +151 -1
- package/processors.js +170 -6
- package/processors.mjs +165 -6
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
|
|
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
|
|
84
|
+
var import_utils25 = require("@web-ts-toolkit/utils");
|
|
85
85
|
|
|
86
86
|
// src/core.ts
|
|
87
|
-
var
|
|
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(
|
|
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,
|
|
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,
|
|
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
|
|
1748
|
-
|
|
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,
|
|
2309
|
+
(0, import_utils13.forEach)(legacy, (inc) => {
|
|
1753
2310
|
includeLocalFields.push(inc.localField);
|
|
1754
2311
|
includePaths.push(inc.path);
|
|
1755
2312
|
});
|
|
1756
|
-
includeLocalFields = (0,
|
|
1757
|
-
includePaths = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
2407
|
+
const includes = (0, import_utils13.compact)((0, import_utils13.castArray)(include));
|
|
1816
2408
|
if (includes.length === 0) return docs;
|
|
1817
|
-
const isSingle = !(0,
|
|
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,
|
|
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,
|
|
1861
|
-
includeLocalValues.push(...this.getForeignKeyValues((0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
2236
|
-
const [subFilter,
|
|
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
|
-
|
|
3263
|
+
const subdoc = findElement(result, subFilter);
|
|
2243
3264
|
if (!subdoc) return { success: false, kind: "error", code: "not_found" /* NotFound */ };
|
|
2244
|
-
const allowedData = (0,
|
|
2245
|
-
|
|
2246
|
-
await parentDoc.save();
|
|
2247
|
-
|
|
2248
|
-
return { success: true, kind: "single", code: "success" /* Success */, data:
|
|
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,
|
|
2263
|
-
const [subFilter,
|
|
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,
|
|
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,
|
|
2274
|
-
|
|
3293
|
+
const allowedData = (0, import_utils16.pick)(tdata, subUpdateSelect);
|
|
3294
|
+
applyUpdate(subdoc, allowedData, subUpdateSelect);
|
|
2275
3295
|
});
|
|
2276
|
-
await parentDoc.save();
|
|
2277
|
-
|
|
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,
|
|
2294
|
-
const
|
|
2295
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 } =
|
|
2519
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
2619
|
-
const { includes, includeLocalFields, includePaths } =
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
2939
|
-
(0,
|
|
4027
|
+
(0, import_utils17.omit)(context.originalDocumentSnapshot, diffExcludeFields),
|
|
4028
|
+
(0, import_utils17.omit)(d.toObject({ virtuals: false }), diffExcludeFields)
|
|
2940
4029
|
) || [];
|
|
2941
|
-
context.modifiedPaths = (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,
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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:
|
|
4273
|
+
{ $match: { foreignValues: { $in: castValues } } },
|
|
3139
4274
|
{ $group: { _id: "$foreignValues", documentIds: { $addToSet: "$_id" } } }
|
|
3140
4275
|
]);
|
|
3141
|
-
const
|
|
4276
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3142
4277
|
for (const row of rows) {
|
|
3143
|
-
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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,
|
|
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,
|
|
3385
|
-
else if (!returningAll) d = (0,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
4817
|
+
var import_utils20 = require("@web-ts-toolkit/utils");
|
|
3625
4818
|
var UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
3626
|
-
|
|
4819
|
+
var DEFAULT_MAX_SLICE = 1e3;
|
|
4820
|
+
var DEFAULT_MASK_REPLACEMENT = "***";
|
|
4821
|
+
function failUnsafeSegment(path, fn = "copyAndDepopulate") {
|
|
3627
4822
|
throw new Error(
|
|
3628
|
-
|
|
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
|
|
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,
|
|
3647
|
-
(0,
|
|
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,
|
|
4884
|
+
(0, import_utils20.forEach)(segs, (seg, ind) => {
|
|
3657
4885
|
if (segs.length === ind + 1) {
|
|
3658
|
-
(0,
|
|
3659
|
-
if (!(0,
|
|
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,
|
|
4892
|
+
if ((0, import_utils20.isArray)(leaf)) {
|
|
3665
4893
|
if (leaf.length === 0) {
|
|
3666
4894
|
depopulated = [];
|
|
3667
|
-
} else if (leaf.every((item) => (0,
|
|
3668
|
-
depopulated = (0,
|
|
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,
|
|
3678
|
-
const id = (0,
|
|
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,
|
|
3687
|
-
(0,
|
|
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,
|
|
4919
|
+
if (!(0, import_utils20.isPlainObject)(target)) return ret;
|
|
3692
4920
|
const targetObject = target;
|
|
3693
4921
|
const next = targetObject[seg];
|
|
3694
|
-
if ((0,
|
|
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
|
|
5092
|
+
var import_utils22 = require("@web-ts-toolkit/utils");
|
|
3746
5093
|
|
|
3747
5094
|
// src/permission.ts
|
|
3748
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
5149
|
+
if ((0, import_utils22.isArray)(value)) {
|
|
3803
5150
|
return `[${value.map(dedupeKey).join(",")}]`;
|
|
3804
5151
|
}
|
|
3805
|
-
if ((0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
5201
|
+
if ((0, import_utils22.isFunction)(resolveIdFilter)) {
|
|
3855
5202
|
return await resolveIdFilter.call(req, id);
|
|
3856
5203
|
}
|
|
3857
|
-
if ((0,
|
|
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,
|
|
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,
|
|
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,
|
|
5259
|
+
if (!(0, import_utils22.isFunction)(globalPermissions)) return;
|
|
3913
5260
|
const permissions = await globalPermissions.call(req, req);
|
|
3914
|
-
if ((0,
|
|
3915
|
-
else if ((0,
|
|
3916
|
-
else if ((0,
|
|
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,
|
|
5269
|
+
if ((0, import_utils22.isBoolean)(routeGuard)) {
|
|
3923
5270
|
return routeGuard === true;
|
|
3924
5271
|
}
|
|
3925
|
-
if ((0,
|
|
5272
|
+
if ((0, import_utils22.isString)(routeGuard)) {
|
|
3926
5273
|
return stringHandler(routeGuard);
|
|
3927
5274
|
}
|
|
3928
|
-
if ((0,
|
|
5275
|
+
if ((0, import_utils22.isArray)(routeGuard)) {
|
|
3929
5276
|
return arrayHandler(routeGuard);
|
|
3930
5277
|
}
|
|
3931
|
-
if ((0,
|
|
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,
|
|
5284
|
+
const hooks = (0, import_utils22.castArray)(hook);
|
|
3938
5285
|
for (let x = 0; x < hooks.length; x++) {
|
|
3939
|
-
if ((0,
|
|
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,
|
|
5309
|
+
if ((0, import_utils22.isBoolean)(value)) {
|
|
3963
5310
|
if (value) fields.push(key);
|
|
3964
|
-
} else if ((0,
|
|
5311
|
+
} else if ((0, import_utils22.isString)(value)) {
|
|
3965
5312
|
if (stringHandler(value)) fields.push(key);
|
|
3966
|
-
} else if ((0,
|
|
5313
|
+
} else if ((0, import_utils22.isArray)(value)) {
|
|
3967
5314
|
if (arrayHandler(value)) fields.push(key);
|
|
3968
|
-
} else if ((0,
|
|
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
|
|
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,
|
|
5469
|
+
fields = (0, import_utils23.difference)(fields, normalizedSelect);
|
|
4123
5470
|
if (excludeid) fields.push("-_id");
|
|
4124
5471
|
} else {
|
|
4125
|
-
fields = (0,
|
|
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,
|
|
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,
|
|
5492
|
+
if ((0, import_utils24.isFunction)(resolveIdFilter)) {
|
|
4146
5493
|
return null;
|
|
4147
5494
|
}
|
|
4148
|
-
if ((0,
|
|
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,
|
|
5573
|
+
populate = (0, import_utils24.compact)(
|
|
4227
5574
|
await Promise.all(
|
|
4228
5575
|
populate.map(async (p) => {
|
|
4229
|
-
const populateAccess = !(0,
|
|
4230
|
-
const ret = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
5750
|
+
const tasks = (0, import_utils24.compact)((0, import_utils24.castArray)(task));
|
|
4404
5751
|
if (tasks.length === 0) return docObject;
|
|
4405
|
-
(0,
|
|
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,
|
|
5820
|
+
if ((0, import_utils24.isUndefined)(subOption)) {
|
|
4439
5821
|
const subFieldOption = getExactModelOption(modelName, `operationAccess.subs.${field}`);
|
|
4440
|
-
if ((0,
|
|
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,
|
|
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,
|
|
4497
|
-
if ((0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
5904
|
+
if ((0, import_utils25.isString)(cond)) {
|
|
4523
5905
|
if (stringHandler(cond)) return next();
|
|
4524
|
-
} else if ((0,
|
|
5906
|
+
} else if ((0, import_utils25.isArray)(cond)) {
|
|
4525
5907
|
if (arrayHandler(cond)) return next();
|
|
4526
|
-
} else if ((0,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4922
|
-
if (isThenable(outcome)) {
|
|
6436
|
+
if (validator.$async === true) {
|
|
4923
6437
|
try {
|
|
4924
|
-
const data = await
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
5191
|
-
return
|
|
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
|
|
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,
|
|
5649
|
-
const includeExtraHeaders = (0,
|
|
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,
|
|
5655
|
-
includePermissions: (0,
|
|
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,
|
|
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,
|
|
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,
|
|
5839
|
-
tryList: (0,
|
|
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,
|
|
5925
|
-
includePermissions: (0,
|
|
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,
|
|
5972
|
-
includePermissions: includePermissions ?? (0,
|
|
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,
|
|
6001
|
-
includePermissions: (0,
|
|
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,
|
|
6046
|
-
includePermissions: includePermissions ?? (0,
|
|
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
|
|
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,
|
|
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,
|
|
6297
|
-
const value = (0,
|
|
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,
|
|
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,
|
|
6540
|
-
logInfoMessage(`${(0,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
6996
|
-
const value = (0,
|
|
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,
|
|
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,
|
|
7071
|
-
const includeExtraHeaders = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
8860
|
+
if (arguments.length === 1 && (0, import_utils32.isPlainObject)(keyOrOptions)) {
|
|
7341
8861
|
return runtime.setGlobalOptions(keyOrOptions);
|
|
7342
8862
|
}
|
|
7343
8863
|
};
|